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

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

$ cmd [option] [filename] > results.out

当前回答

公认的/高投票的答案是很好的,但他们缺乏一些本质的细节。这篇文章涵盖了如何更好地处理当shell路径名展开(glob)失败时,当文件名包含嵌入的换行符/破折号时,以及在将结果写入文件时将命令输出重定向移出for循环的情况。

当使用*运行shell glob扩展时,如果目录中没有文件,那么扩展可能会失败,并且将未展开的glob字符串传递给要运行的命令,这可能会产生不希望看到的结果。bash shell使用nullglob提供了一个扩展的shell选项。因此,在包含文件的目录中,循环基本上如下所示

 shopt -s nullglob

 for file in ./*; do
     cmdToRun [option] -- "$file"
 done

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

或者以POSIX兼容的方式(nullglob是特定于bash的)

 for file in ./*; do
     [ -f "$file" ] || continue
     cmdToRun [option] -- "$file"
 done

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

还要注意在传递文件名参数之前使用的——。这是必需的,因为如前所述,shell文件名可以在文件名中的任何位置包含破折号。一些shell命令对此进行解释,并在名称未正确引用时将其视为命令选项,并在提供标志时执行命令思维。

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


文件名的双引号可以正确地解决名称包含glob字符或空格的情况。但是*nix文件名也可以包含换行符。因此,我们用唯一不能成为有效文件名一部分的字符来限制文件名——空字节(\0)。由于bash内部使用C样式字符串,其中使用null字节表示字符串的结束,因此它是合适的候选对象。

因此,使用shell的printf选项使用read命令的-d选项用这个NULL字节分隔文件,我们可以执行以下操作

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

nullglob和printf被包裹在(..)周围,这意味着它们基本上是在子shell(子shell)中运行的,因为为了避免一旦命令退出,nullglob选项就会反映到父shell上。read命令的-d "选项不符合POSIX,因此需要bash shell来完成此操作。使用find命令可以这样做

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

对于不支持-print0的find实现(GNU和FreeBSD实现除外),可以使用printf来模拟

find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --

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

您可以使用此修复扩展上面的代码

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

这将基本上将您的文件输入的每次迭代的命令内容放到标准输出中,当循环结束时,打开目标文件一次以写入标准输出的内容并保存它。等价的查找版本是

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

其他回答

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

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done

例子

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

这个怎么样:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out

-maxdepth 1参数防止find函数递归降为 任何子目录。(如果你想处理这样的嵌套目录,你可以省略这个。) -type -f指定只处理普通文件。 -exec CMD选项{}告诉它对找到的每个文件使用指定选项运行CMD,文件名替换为{} \;表示命令的结束。 最后,所有单个cmd执行的输出被重定向到 results.out

但是,如果您关心文件处理的顺序,则可以使用 也许写个循环会更好。我认为find处理文件 在inode顺序(虽然我可能是错的),这可能不是什么 你想要的。

基于@Jim Lewis的方法:

下面是一个使用find并按修改日期对文件进行排序的快速解决方案:

$ find  directory/ -maxdepth 1 -type f -print0 | \
  xargs -r0 stat -c "%y %n" | \
  sort | cut -d' ' -f4- | \
  xargs -d "\n" -I{} cmd -op1 {} 

排序参见:

http://www.commandlinefu.com/commands/view/5720/find-files-and-list-them-sorted-by-modification-time

我在树莓派的命令行中运行:

for i in *; do cmd "$i"; done

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

find directory/ | xargs  Command 

例如,要查找当前目录中所有文件的行数,您可以这样做:

find . | xargs wc -l