382

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

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

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

10 回答 10

543

以下 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
于 2012-05-09T20:18:24.807 回答
223

这个怎么样:

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 顺序处理文件(尽管我可能错了),这可能不是您想要的。

于 2012-05-09T20:17:40.690 回答
94

我通过运行以下命令在我的 Raspberry Ri 上执行此操作:

for i in *; do cmd "$i"; done
于 2016-05-19T21:03:33.880 回答
15

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

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

 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 命令会解释它并将它们视为命令选项,并在考虑是否提供标志的情况下执行命令。

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


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

因此,使用命令选项使用 shell 的选项printf来分隔带有这个 NULL 字节的文件,我们可以在下面做-dread

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

Thenullglob和 theprintf被环绕(..),这意味着它们基本上在子 shell(子 shell)中运行,因为nullglob一旦命令退出,为了避免反射到父 shell 的选项。command的-d ''选项符合 POSIX 标准,因此需要一个shell 来完成。使用命令可以这样做readbashfind

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

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

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
于 2019-04-16T07:32:25.333 回答
9

您可以使用xarg

ls | xargs -L 1 -d '\n' your-desired-command 
  • -L 1导致一次通过 1 个项目

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

于 2019-11-26T13:51:43.630 回答
5

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

find directory/ | xargs  Command 

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

find . | xargs wc -l
于 2018-02-09T18:03:14.560 回答
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

于 2013-12-25T08:13:17.837 回答
1

我需要将所有 .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从此过上幸福的生活

于 2017-11-01T21:57:44.470 回答
1

最大深度

我发现它与吉姆刘易斯的答案很好地配合,只需添加如下内容:

$ 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
于 2019-06-11T12:35:49.820 回答
0

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

sh /dir/* > ./result.txt
于 2019-04-16T07:37:34.843 回答