Bash脚本对目录中的所有文件执行命令

the*_*tro 257 bash scripting

有人可以提供代码来执行以下操作:假设有一个文件目录,所有这些都需要通过程序运行.程序将结果输出到标准输出.我需要一个脚本进入目录,在每个文件上执行命令,并将输出连接成一个大输出文件.

例如,要在1个文件上运行该命令:

$ cmd [option] [filename] > results.out
Run Code Online (Sandbox Code Playgroud)

And*_*nov 380

以下bash代码将$ file传递给命令,其中$ file将表示/ dir中的每个文件

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done
Run Code Online (Sandbox Code Playgroud)

el@defiant ~/foo $ touch foo.txt bar.txt baz.txt
el@defiant ~/foo $ for i in *.txt; do echo "hello $i"; done
hello bar.txt
hello baz.txt
hello foo.txt
Run Code Online (Sandbox Code Playgroud)

  • +1,它只是花了我整个壁纸集合.我之后的所有人都使用双引号."$文件" (39认同)
  • 如果`/ dir /`中不存在文件,那么循环仍然运行一次,值为'*'表示`$ file`,这可能是不合需要的.要避免这种情况,请在循环期间启用nullglob.在循环`shopt -s nullglob`之前添加这一行,并在循环`shopt -u nullglob #revert nullglob之后回到它的正常默认状态`. (20认同)
  • 对目录中的大量文件使用此命令时要小心。请改用 find -exec。 (2认同)
  • “对目录中的大量文件使用此命令时要小心。请改用 find -exec”。但为什么? (2认同)

Jim*_*wis 160

这个怎么样:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
Run Code Online (Sandbox Code Playgroud)
  • -maxdepth 1参数阻止查找以递归方式递减到任何子目录中.(如果要处理这样的嵌套目录,可以省略它.)
  • -type -f 指定仅处理纯文本.
  • -exec cmd option {}告诉它cmd使用指定option的每个文件运行,并替换文件名{}
  • \; 表示命令的结束.
  • 最后,所有单个cmd执行的输出都被重定向到 results.out

但是,如果您关心文件的处理顺序,那么编写循环可能会更好.我认为find以inode顺序处理文件(虽然我可能错了),这可能不是你想要的.

  • 这才是处理文件的正确方法。由于多种原因,使用 for 循环很容易出错。还可以使用其他命令(例如“stat”和“sort”)来完成排序,这当然取决于排序标准是什么。 (2认同)
  • 如果我想运行两个命令,我将如何在 `-exec` 选项后链接它们?我是否必须将它们用单引号或其他东西括起来? (2认同)
  • @frei您问题的答案在这里:/sf/answers/423072751/,但基本上只是添加`-exec`选项:`find。-name“ * .txt” -exec echo {} \; -exec grep banana {} \;` (2认同)
  • 如何引用文件名作为选项? (2认同)

rob*_*ves 47

我通过运行命令行在我的覆盆子pi上执行此操作:

for i in *;do omxplayer "$i";done
Run Code Online (Sandbox Code Playgroud)

  • 虽然[这个答案](/sf/answers/736644471/)可能是在生产环境中执行此操作的“正确”方法,但为了日常使用方便,这一行胜出! (6认同)
  • fwiw,我认为分号后面有空格会更具可读性,但也许这只是我的想法! (2认同)

Ini*_*ian 10

接受/高投票的答案很好,但它们缺乏一些细节。这篇文章介绍了如何更好地处理 shell 路径名扩展 (glob) 失败、文件名包含嵌入的换行符/破折号以及将命令输出重定向移出 for 循环时将结果写入到文件。

使用运行 shell glob 扩展时*,如果目录中没有文件,则扩展可能会失败,并且未扩展的 glob 字符串将传递给要运行的命令,这可能会产生不良结果。所述bash外壳提供了用于此使用扩展壳选项nullglob。所以循环在包含文件的目录中基本上变成如下

 shopt -s nullglob

 for file in ./*; do
     cmdToRun [option] -- "$file"
 done
Run Code Online (Sandbox Code Playgroud)

这使您可以在表达式./*不返回任何文件时安全地退出 for 循环(如果目录为空)

或以符合 POSIX 的方式(nullglobbash特定的)

 for file in ./*; do
     [ -f "$file" ] || continue
     cmdToRun [option] -- "$file"
 done
Run Code Online (Sandbox Code Playgroud)

这使您可以在表达式失败一次时进入循环,并且条件[ -f "$file" ]检查未扩展的字符串./*是否是该目录中的有效文件名,而事实并非如此。因此,在这种情况下失败时,使用continue我们恢复到for随后不会运行的循环。

还要注意--传递文件名参数之前的用法。这是必需的,因为如前所述,shell 文件名可以在文件名的任何位置包含破折号。当名称正确引用并执行命令时,某些 shell 命令会解释它并将它们视为命令选项,并考虑是否提供了标志。

--在这种情况下,该信号表示命令行选项的结束,这意味着该命令不应将超出此点的任何字符串解析为命令标志,而只能解析为文件名。


双引号正确地解决了文件名包含全局字符或空格的情况。但是 *nix 文件名中也可以包含换行符。因此,我们使用唯一不能作为有效文件名一部分的字符(空字节 ( \0))来限制文件名。由于bash内部使用C样式字符串,其中空字节用于指示字符串的结尾,因此它是正确的候选者。

所以使用printfshell的-d选项使用readcommand的选项来用这个NULL字节分隔文件,我们可以在下面做

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done
Run Code Online (Sandbox Code Playgroud)

nullglobprintf被缠(..)他们基本上在一个子shell(子shell)运行,这意味着,避免因为nullglob反思父shell,一旦命令退出选项。命令的-d ''选项符合 POSIX 标准,因此需要一个shell 来完成。使用命令可以这样做readbashfind

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
Run Code Online (Sandbox Code Playgroud)

对于find不支持的实现-print0(除了 GNU 和 FreeBSD 实现),这可以使用printf

find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Run Code Online (Sandbox Code Playgroud)

另一个重要的修复是将重定向移出 for 循环以减少大量文件 I/O。当在循环内使用时,shell 必须为 for 循环的每次迭代执行两次系统调用,一次用于打开,一次用于关闭与文件关联的文件描述符。这将成为运行大型迭代的性能瓶颈。推荐的建议是将它移到循环之外。

使用此修复程序扩展上述代码,您可以这样做

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done > results.out
Run Code Online (Sandbox Code Playgroud)

这基本上会将文件输入的每次迭代的命令内容放入标准输出,当循环结束时,打开目标文件一次以写入标准输出的内容并保存它。find相同的等效版本是

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
Run Code Online (Sandbox Code Playgroud)


Al *_*mun 8

您可以使用xarg

ls | xargs -L 1 -d '\n' your-desired-command 
Run Code Online (Sandbox Code Playgroud)
  • -L 1 导致一次通过 1 个项目

  • -d '\n'ls根据新行拆分输出。

  • 使用 xargs 很好,因为如果添加“-P 8”标志(最多同时运行 8 个进程),它允许您并行运行所需的命令。 (2认同)
  • 对于 macOS,“-d”选项不可用。您可以先通过“brew install findutils”修复它,然后使用“gxargs”而不是“xargs” (2认同)

Rah*_*hul 5

有时完成工作的一种快速而肮脏的方法是:

find directory/ | xargs  Command 
Run Code Online (Sandbox Code Playgroud)

例如要查找当前目录中所有文件的行数,您可以执行以下操作:

find . | xargs wc -l
Run Code Online (Sandbox Code Playgroud)

  • @Hubert 为什么你的文件名中有换行符?! (8认同)
  • 这不是“为什么”的问题,而是正确性的问题——文件名不必包含可打印的字符,它们甚至不必是有效的 UTF-8 序列。此外,什么是换行符非常依赖于编码,一个编码 ♀ 是另一个的换行符。参见代码页 437 (2认同)
  • 真的吗?这在 99.9% 的情况下都有效,而且他确实说“又快又脏” (2认同)