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

时间:2012-05-09 20:12:42

标签: linux bash scripting

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

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

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

10 个答案:

答案 0 :(得分:337)

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

答案 1 :(得分:143)

这个怎么样:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • -maxdepth 1参数阻止查找以递归方式递减到 任何子目录。 (如果您希望处理这样的嵌套目录,可以省略它。)
  • -type -f指定只处理普通文件。
  • -exec cmd option {}告诉它为找到的每个文件运行带有指定cmd的{​​{1}},文件名替换为option
  • {}表示命令的结束。
  • 最后,所有单个\;执行的输出都被重定向到 cmd

但是,如果您关心文件的处理顺序,那么就是您 写一个循环可能会更好。我认为results.out处理文件 在inode顺序(虽然我可能是错的),这可能不是什么 你想要的。

答案 2 :(得分:44)

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

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

答案 3 :(得分:2)

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

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

这很难理解,所以让我们分解。

首先使用您的文件进入目录,

对于模式中的每个文件,

for i in **/*.md;

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

rm -r ../docs/"$i"删除因mkdir -p

而创建的额外文件夹

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

echo "$i -> ../docs/$i"回应你的所作所为

; done

之后过上幸福的生活

答案 4 :(得分:2)

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

find directory/ | xargs  Command 

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

find . | xargs wc -l

答案 5 :(得分: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

答案 6 :(得分:1)

最大深度

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

import { createMuiTheme, responsiveFontSizes } from '@material-ui/core/styles';

let theme = createMuiTheme();
theme = responsiveFontSizes(theme);

排序顺序

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

$ 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

答案 7 :(得分:0)

已接受/高投票的答案很好,但是它们缺少一些实质性的细节。这篇文章介绍了以下情况:当文件名包含嵌入式换行符/破折号并将命令输出方向移出for循环时,shell路径名扩展(glob)失败时如何更好地处理。

使用*运行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命令将其解释并将其视为命令选项,并考虑是否提供了标志来执行命令。

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


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

因此,使用外壳程序的printf选项通过-d命令的read选项使用此NULL字节分隔文件,我们可以执行以下操作

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

nullglobprintf包裹在(..)周围,这意味着它们基本上在子外壳程序(子外壳程序)中运行,因为避免了nullglob选项一旦命令退出,就可以反映在父外壳上。 -d ''命令的read选项与POSIX不兼容,因此需要一个bash shell。使用find命令可以做到

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

对于不支持find的{​​{1}}实现(GNU和FreeBSD实现除外),可以使用-print0

来模拟
printf

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

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

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

基本上会将每次文件输入迭代的命令内容放入stdout,然后在循环结束时打开目标文件一次,以写入stdout的内容并保存。相同版本的等效( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do cmdToRun [option] -- "$file" done > results.out

find

答案 8 :(得分:0)

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

sh /dir/* > ./result.txt

答案 9 :(得分:0)

您可以使用xarg

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

-L 1一次通过1个项目

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

相关问题