Aku*_*eru 5 performance find shell-script files
我想以最有效的方式递归更改数百个文件的第一行。我想要做的一个例子是更改#!/bin/bash为#!/bin/sh,所以我想出了这个命令:
find ./* -type f -exec sed -i '1s/^#!\/bin\/bash/#!\/bin\/sh/' {} \;
Run Code Online (Sandbox Code Playgroud)
但是,据我所知,这样做 sed 必须读取整个文件并替换原始文件。有没有更有效的方法来做到这一点?
ilk*_*chu 19
是的,完全sed -i读取和重写文件,并且由于行长度发生变化,因此必须移动所有其他行的位置。
...但在这种情况下,行长实际上不需要改变。我们可以#!/bin/sh??用两个尾随空格代替hashbang 行。操作系统将在解析 hashbang 行时删除那些。(或者,使用两个换行符或一个换行符 + 哈希符号,这两者都会创建 shell 最终会忽略的额外行。)
我们需要做的就是从一开始就打开文件进行写入,而不是截断它。通常的重定向>并>>不能这样做,但在 Bash 中,读写重定向<>似乎有效:
echo '#!/bin/sh ' 1<> foo.sh
Run Code Online (Sandbox Code Playgroud)
或使用dd(这些应该是标准的 POSIX 选项):
echo '#!/bin/sh ' | dd of=foo.sh conv=notrunc
Run Code Online (Sandbox Code Playgroud)
请注意,严格来说,这两个都重写了行尾的换行符,但这并不重要。
当然,上面的内容会无条件地覆盖给定文件的开头。添加一个检查,原来的文件已经在正确的hashbang留作练习......无论如何,我可能不会在生产中做到这一点,很显然,如果你需要将线更改为这是不行的再一个.
优化将使用{} +而不是{} \;.
find . -type f -exec sed -i '1s|^#!/bin/bash|#!/bin/sh|' {} +
Run Code Online (Sandbox Code Playgroud)
不是为每个找到的文件调用一个 sed 进程,而是将文件作为参数提供给单个 sed 进程。
find 的 POSIX 规范{} +(我的粗体):
如果主表达式由 <加号> 标点,则主表达式应始终评估为真,并且评估主表达式的路径名应聚合为集合。实用程序utility_name 应为每组聚合路径名调用一次。
我会做:
#! /bin/zsh -
LC_ALL=C # work with bytes instead of characters.
shebang_to_replace=$'#!/bin/bash\n'
new_shebang=$'#!/bin/sh -\n'
length=$#shebang_to_replace
ret=0
for file in **/*(N.L+$((length - 1)));do
if
read -u0 -k $length shebang < $file &&
[[ $shebang = $shebang_to_replace ]]
then
print -rn -- $new_shebang 1<> $file || ret=$?
fi
done
exit $ret
Run Code Online (Sandbox Code Playgroud)
就像@ilkkachu 的方法一样,文件被一个大小完全相同的字符串覆盖就地。区别在于:
.git例如考虑一个),因为您不太可能考虑那些(您使用的find ./*会跳过当前目录的隐藏文件和目录,而不是子目录的隐藏文件和目录)。D如果确实需要,请添加glob 限定符。.相当于-type f,因此我们已经从文件中检索 inode 信息,因此我们不妨检查那里的大小)。zsh因为其他shell 无法处理任意字节值)。#!/bin/sh -作为替代,这是/bin/sh脚本的正确shebang (顺便说一下,这#!/bin/bash -将是脚本的正确shebang /bin/bash)。请参阅为什么“#!/bin/sh -”shebang 中的“-”?详情。覆盖文件的错误在退出状态中报告,但不是遍历目录树的错误,也不是读取文件的错误,尽管可以添加。
在anycase,它只替换了shebangs准确 #!/bin/bash,没有其他shebangs在使用bash像翻译#! /bin/bash,#! /bin/bash -Oextglob,#! /usr/bin/env bash,#! /bin/bash -efu。对于那些,你需要决定做什么。-efu是sh选项,但例如-Oextglob没有sh等效项。
您可以扩展它以支持最简单的情况,例如:
#! /bin/zsh -
LC_ALL=C # work with bytes instead of characters.
zmodload zsh/system || exit
minlength=11 # length of "#!/bin/bash"
maxlength=1024 # arbitrary here.
ret=0
for file in **/*(N.L+$minlength);do
if
sysread -s $maxlength buf < $file &&
[[ $buf =~ $'(^#![\t ]*((/usr)?/bin/env[ \t]+bash|/bin/bash)([ \t]+-([aCefux]*))?[ \t]*)\n' ]]
then
shebang=$match[1] newshebang="#!/bin/sh -$match[5]"
print -r -- ${(r[$#shebang])newshebang} 1<> $file || ret=$?
fi
done
exit $ret
Run Code Online (Sandbox Code Playgroud)
这里允许许多不同的 shebangs 和许多受支持的选项,这些选项在新的/bin/shshebang中复制,右填充(使用r[length]参数扩展标志)与原始大小相同。