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


289

有人可以提供代码来执行以下操作:假设有一个文件目录,所有文件都需要通过程序运行。程序将结果输出到标准输出。我需要一个脚本,该脚本将进入目录,对每个文件执行命令,并将输出合并为一个大输出文件。

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

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

3
我想补充一个问题。可以使用xargs完成吗?例如, ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out
Ozair Kafray 2012年

2
可以,但是您可能不想使用ls drive xargs。如果cmd完全能胜任写作,也许您可​​以简单地做到cmd <wildcard>
人间

Answers:


423

以下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

23
如果中没有文件/dir/,则循环仍将使用'*'的值运行一次$file,这可能是不希望的。为避免这种情况,请在循环期间启用nullglob。在循环之前添加此行,在循环shopt -s nullglob之后添加此行shopt -u nullglob #revert nullglob back to it's normal default state
Stew-au

43
+1,这只是花了我整个墙纸收藏费。跟在我后面的每个人都使用双引号。“ $ file”
Behrooz13年

如果循环内的输出文件相同,则在循环外重定向的效率要高得多done >results.out(可能然后您可以覆盖而不是像我在这里假设的追加)。
人间

如何获得自定义命名为其输入文件的单个结果文件?
蒂莫西·斯旺

1
使用此命令处理dir中的大量文件时要小心。使用find -exec代替。
kolisko '19

181

这个怎么样:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • -maxdepth 1参数防止find递归地降级到任何子目录中。(如果要处理此类嵌套目录,则可以省略。)
  • -type -f 指定仅将处理纯文件。
  • -exec cmd option {}告诉它以找到的每个文件cmd的指定名称运行option,并用文件名替换{}
  • \; 表示命令的结尾。
  • 最后,所有单个cmd执行的输出都重定向到 results.out

但是,如果您关心文件的处理顺序,则最好编写循环。我认为find按inode顺序处理文件(尽管我可能对此有误),这可能不是您想要的。


1
这是处理文件的正确方法。由于许多原因,使用for循环容易出错。还可以通过使用其他命令(例如stat和)来完成排序sort,哪个过程取决于什么是排序标准。
tuxdna

1
如果我想运行两个命令,在-exec选项后如何链接它们?我是否必须将它们用单引号引起来?
弗雷

find始终是最好的选择,因为您可以使用option按文件名模式进行过滤,-name并且可以在单个命令中进行操作。
若奥·皮门特尔·费雷拉

3
@frei的回答你的问题就在这里:stackoverflow.com/a/6043896/1243247但基本上只需要添加-exec选项:find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;
若奥·皮门特尔·费雷拉

2
如何引用文件名作为选项?
Toskan

54

我正在通过命令行在树莓派上执行以下操作:

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

7

公认的/高投票的答案很好,但是它们缺少一些实质性的细节。这篇文章介绍了以下情况:如何更好地处理shell路径名扩展(glob)失败,文件名包含嵌入式换行符/破折号以及将结果输出重定向到for循环时(将结果写入a时)的情况。文件。

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

 shopt -s nullglob

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

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

或以POSIX兼容的方式(nullglobbash特定的)

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

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

还要注意--在传递文件名参数之前的用法。这是必需的,因为如前所述,shell文件名可以在文件名中的任何地方包含短划线。一些shell命令会对此进行解释,并在名称正确引用时将其视为命令选项,并考虑是否提供了标志来执行命令。

--信号在这种情况下,该装置的命令行选项结束时,命令不能解析超过此点作为命令标志但仅作为文件名的任何字符串。


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

因此,使用printfshell -d选项通过read命令option 使用此NULL字节定界文件,我们可以执行以下操作

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

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

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

对于find不支持的实现-print0(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

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

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

1
+1用于检查文件是否存在。如果搜索不存在的目录,则$ file包含正则表达式字符串“ / invald_dir / *”,而不是有效的文件名。
cdalxndr

3

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

find directory/ | xargs  Command 

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

find . | xargs wc -l

8
@Hubert为什么文件名中包含换行符?
musicin3d

2
这不是“为什么”的问题,而是正确性的问题–文件名不必包括可打印的字符,它们甚至不必是有效的UTF-8序列。另外,什么是换行符很大程度上取决于编码,一种编码♀是另一种换行符。参见代码页437
Hubert Kario,

2
科蒙,真的吗?这确实有99.9%的时间有效,而且他确实说过“快速又脏”
Edoardo

我不喜欢Bash脚本的“快速又肮脏”(又称“残破”)。迟早它以诸如著名的“ Moved。Ran ~/.local/share/steamsteam。它删除了用户拥有的系统上的所有内容”之类的东西结束。错误报告。
减少活动

这也不适用于名称中包含空格的文件。
莎玛斯S-恢复莫妮卡

2

我需要将所有.md文件从一个目录复制到另一个目录,所以这就是我所做的。

for i in **/*.md;do mkdir -p ../docs/"$i" && rm -r ../docs/"$i" && cp "$i" "../docs/$i" && echo "$i -> ../docs/$i"; done

这很难读,所以让我们分解一下。

首先将CD与文件一起放入目录,

for i in **/*.md; 对于模式中的每个文件

mkdir -p ../docs/"$i"在包含文件的文件夹之外的docs文件夹中创建该目录。这将创建一个与该文件同名的额外文件夹。

rm -r ../docs/"$i" 删除由于以下原因而创建的多余文件夹 mkdir -p

cp "$i" "../docs/$i" 复制实际文件

echo "$i -> ../docs/$i" 回声你做了什么

; done 从此过上幸福的生活


注意:**要工作,globstar需要设置外壳选项:shopt -s globstar
Hubert Kario

2

您可以使用 xarg

ls | xargs -L 1 -d '\n' your-desired-command

-L 1 导致一次通过1个项目

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


1

基于@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


如果文件名称中包含换行符,则无法使用
Hubert Kario

1
@HubertKario你可能想了解更多有关-print0find-0xargs它们使用空字符,而不是任何空白(包括换行)。
tuxdna

是的,使用-print0是有帮助的,但是整个管道都需要使用类似的东西,而sort不是
Hubert Kario

1

我认为简单的解决方案是:

sh /dir/* > ./result.txt

2
您是否正确理解了这个问题?这只会尝试通过shell运行目录中的每个文件-就像它是一个脚本一样。
rdas

1

最大深度

我发现它与Jim Lewis的答案配合得很好,只需添加如下所示:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
$ find . -maxdepth 1 -type f -name '*.sh' -exec {} \; > results.out

排序

如果要按排序顺序执行,请按如下所示进行修改:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -maxdepth 2 -type f -name '*.sh' | sort | bash > results.out

仅作为示例,将按以下顺序执行:

bash: 1: ./assets/main.sh
bash: 2: ./builder/clean.sh
bash: 3: ./builder/concept/compose.sh
bash: 4: ./builder/concept/market.sh
bash: 5: ./builder/concept/services.sh
bash: 6: ./builder/curl.sh
bash: 7: ./builder/identity.sh
bash: 8: ./concept/compose.sh
bash: 9: ./concept/market.sh
bash: 10: ./concept/services.sh
bash: 11: ./product/compose.sh
bash: 12: ./product/market.sh
bash: 13: ./product/services.sh
bash: 14: ./xferlog.sh

无限深度

如果要通过特定条件无限深度执行,可以使用以下命令:

export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -type f -name '*.sh' | sort | bash > results.out

然后放在子目录中的每个文件的顶部,如下所示:

#!/bin/bash
[[ "$(dirname `pwd`)" == $DIR ]] && echo "Executing `realpath $0`.." || return

在父文件正文中的某处:

if <a condition is matched>
then
    #execute child files
    export DIR=`pwd`
fi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.