请问能否提供以下功能的代码: 假设有一个文件目录,其中所有文件都需要通过程序运行。该程序将输出结果到标准输出。我需要一个脚本,能够进入该目录,在每个文件上执行该命令,并将输出连接成一个大的输出文件。
例如,对于一个文件运行该命令:
$ cmd [option] [filename] > results.out
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 #将nullglob恢复到默认状态
。 - Stew-audone >results.out
会更加高效(可能您可以覆盖而不是追加,就像我在这里假设的那样)。 - tripleee这个怎么样:
find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
-maxdepth 1
参数防止find
递归进入任何子目录。(如果你希望处理此类嵌套目录,可以省略此参数。)-type -f
指定只处理普通文件。-exec cmd option {}
告诉find
对于找到的每个文件,使用指定的option
运行cmd
命令,并将文件名替换为{}
。\;
表示命令的结束。cmd
执行的输出都被重定向到results.out
。但是,如果您在意文件被处理的顺序,最好编写循环。我认为find
按照inode顺序处理文件(尽管我可能错了),这可能不是您想要的。
stat
和sort
来完成,当然这取决于排序的标准是什么。 - tuxdna我正在使用树莓派的命令行完成这个操作,方法是运行:
for i in *; do cmd "$i"; done
$i
部分之后添加任何内容,这样就会得到一个新字符串。假设有一个虚构的命令 ppp -i raw.txt -o processed.txt
,则可以使用以下命令:for i in *; do ppp -i "$i" -o "$i changed"; done
。这将对每个文件执行 ppp
命令,并且每次执行的结果文件的名称都将类似于输入文件的名称,末尾加上 " changed"。 - Aleksandar你可以使用xarg
:
ls | xargs -L 1 -d '\n' your-desired-command
-L 1
表示一次只处理一个项目
-d '\n'
根据换行符(\n
)将 ls
命令的输出分割成若干行。
-P 8
标志(最多同时运行8个进程),它允许您并行运行所需的命令。 - Nick Crews-d
选项不可用。您可以先通过 brew install findutils
安装 findutils
,然后使用 gxargs
替代 xargs
来解决此问题。 - Wit 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
样式字符串,在其中空字节用于指示字符串的结尾,因此它是正确的候选项。
所以使用shell的printf
选项来使用read
命令的-d
选项以该NULL字节分隔文件,我们可以执行以下操作
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
nullglob
和 printf
被包裹在 (..)
中,这意味着它们基本上在子 shell(child 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
这将基本上将文件输入的每次迭代的命令内容放到stdout中,当循环结束时,打开目标文件一次,将stdout的内容写入并保存。同样的find
版本为:
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
有时候完成任务的一个快速且简单的方法是:
find directory/ | xargs Command
find . | xargs wc -l
〜/ .local / share / steam
。运行steam。它删除了用户拥有的系统上的所有内容。”这样的错误报告。 - reducing activity基于 @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
find
命令中的-print0
和xargs
命令中的-0
,它们使用空字符而不是任何空格(包括换行符)。 - tuxdna我需要将一个目录中的所有 .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
生活得幸福快乐
**
生效,需要设置 globstar
shell 选项:shopt -s globstar
。 - Hubert Kario我发现它与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
我认为简单的解决方案是:
sh /dir/* > ./result.txt
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out
- Ozair Kafrayls
е‘Ҫд»ӨпјҢдҪҶжҳҜжңҖеҘҪдёҚиҰҒз”Ёе®ғжқҘй©ұеҠЁxargs
гҖӮеҰӮжһңcmd
е‘Ҫд»ӨеҶҷеҫ—еҫҲеҘҪпјҢдҪ еҸҜд»ҘзӣҙжҺҘиҫ“е…Ҙcmd <йҖҡй…Қз¬Ұ>
гҖӮ - tripleee