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

Posted

技术标签:

【中文标题】对目录中的所有文件执行命令【英文标题】:Execute command on all files in a directory 【发布时间】:2012-05-18 09:37:04 【问题描述】:

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

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

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

【问题讨论】:

我想补充一下这个问题。可以使用 xargs 完成吗?例如,ls <directory> | xargs cmd [options] filenames put in here automatically by xargs [more arguments] > results.out 可以,但你可能don't want to use ls 开车xargs。如果cmd 写得很好,也许你可以简单地写cmd <wildcard> 【参考方案1】:

这个怎么样:

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 顺序(尽管我可能错了),这可能不是什么 你想要的。

【讨论】:

这是处理文件的正确方法。由于许多原因,使用 for 循环很容易出错。也可以使用其他命令进行排序,例如 statsort,这当然取决于排序标准。 如果我想运行两个命令,我将如何在-exec 选项之后链接它们?我是否必须将它们用单引号或其他东西括起来? find 始终是最佳选择,因为您可以使用选项 -name 按文件名模式进行过滤,并且可以在单个命令中完成。 @frei 您的问题的答案在这里:***.com/a/6043896/1243247 但基本上只需添加-exec 选项:find . -name "*.txt" -exec echo \; -exec grep banana \; 如何引用文件名作为选项?【参考方案2】:

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

【讨论】:

如果/dir/ 中不存在文件,则循环仍然运行一次,$file 的值为 '*',这可能是不可取的。为避免这种情况,请在循环期间启用 nullglob。在循环shopt -s nullglob 之前添加这一行,在循环shopt -u nullglob #revert nullglob back to it's normal default state 之后添加这一行。 如果循环内的输出文件相同,那么在循环外重定向done >results.out 会更有效(然后你可以覆盖而不是追加,就像我在这里假设的那样)。 如何获得自定义命名为其输入文件的单个结果文件? @TimothySwan ***.com/questions/28725333/… 小心使用此命令处理目录中的大量文件。请改用 find -exec。【参考方案3】:

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

【讨论】:

如果文件名称中有换行符,这将不起作用 @HubertKario 您可能想了解更多关于-print0find-0xargs 使用空字符而不是任何空格(包括换行符)。 是的,使用-print0 是有帮助的,但是整个管道需要使用这样的东西,而sort 不是【参考方案4】:

我在 Raspberry Ri 上通过命令行运行:

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

【讨论】:

虽然this answer 可能是在生产环境中执行此操作的“正确”方式,但为了方便日常使用,这种单行方式胜出! fwiw,我认为分号后的空格会更易读,但也许这就是我!【参考方案5】:

我需要将所有 .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 shell 选项:shopt -s globstar【参考方案6】:

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

find directory/ | xargs  Command 

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

find . | xargs wc -l

【讨论】:

@Hubert 为什么你的文件名中有换行符?! 这不是“为什么”的问题,而是正确性的问题——文件名不必包含可打印字符,它们甚至不必是有效的 UTF-8 序列。此外,什么是换行符非常依赖于编码,一个编码 ♀ 是另一个的换行符。参见代码页 437 cmon,真的吗?这在 99.9% 的情况下确实有效,而且他确实说“又快又脏” 我不喜欢“又快又脏”(又名“破碎”)的 Bash 脚本。它迟早会以著名的“已移动~/.local/share/steam。Ran steam。它删除了用户拥有的系统上的所有内容”之类的东西结束。错误报告。 这也不适用于名称中包含空格的文件。【参考方案7】:

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

当使用* 运行 shell glob 扩展时,如果目录中存在 no 文件并且未扩展的 glob 字符串将传递给要运行的命令,这可能会产生不良结果。 bash shell 为此使用nullglob 提供了扩展的shell 选项。所以循环基本上在包含您的文件的目录中如下所示

 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 样式字符串,其中空字节用于指示字符串的结尾,因此它是正确的候选对象。

所以使用shell的printf选项,使用read命令的-d选项来分隔带有这个NULL字节的文件,我们可以这样做

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

nullglobprintf 包裹在 (..) 周围,这意味着它们基本上在子 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)

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

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

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

【讨论】:

+1 用于检查文件是否存在。如果在不存在的目录中搜索,$file 包含的正则表达式字符串“/invald_dir/*”不是有效的文件名。【参考方案8】:

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

sh /dir/* > ./result.txt

【讨论】:

您的问题理解正确吗?这只会尝试通过 shell 运行目录中的每个文件 - 就好像它是一个脚本一样。【参考方案9】:

最大深度

我发现它可以很好地与 Jim Lewis's answer 一起使用,只需添加如下内容:

$ 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

【讨论】:

【参考方案10】:

你可以使用xarg:

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

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

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

【讨论】:

以上是关于对目录中的所有文件执行命令的主要内容,如果未能解决你的问题,请参考以下文章

Shell / Terminal:使用绝对路径为目录中的所有文件执行命令

修改linux文件权限命令

Linux命令:修改文件权限命令chmodchgrpchown详解

详解Linux命令:改变文件或目录的访问权限

每日一个linux 命令-修改linux文件权限命令:chmod

linux文件权限命令chmod学习