我经常有一个处理一个文件的命令,我想在目录中的每个文件上运行它。有没有内置的方法来做到这一点?
例如,假设我有一个程序data
,它输出一个关于文件的重要数字:
./data foo
137
./data bar
42
我想以某种方式在目录中的每个文件上运行它:
map data `ls *`
ls * | map data
产生这样的输出:
foo: 137
bar: 42
答案 0 :(得分:15)
如果您只是尝试在一堆文件上执行data
程序,最简单/最简单的方法是在-exec
中使用find
。
假设您要对当前目录(和子目录)中的所有txt文件执行data
。这就是你所需要的:
find . -name "*.txt" -exec data {} \;
如果要将其限制在当前目录中,可以执行以下操作:
find . -maxdepth 1 -name "*.txt" -exec data {} \;
find
有很多选项。
答案 1 :(得分:8)
如果您只想在每个文件上运行命令,可以执行以下操作:
for i in *; do data "$i"; done
如果您还希望显示当前正在处理的文件名,则可以使用:
for i in *; do echo -n "$i: "; data "$i"; done
答案 2 :(得分:7)
看起来你想要xargs
:
find . --maxdepth 1 | xargs -d'\n' data
要先打印每个命令,它会变得更复杂一些:
find . --maxdepth 1 | xargs -d'\n' -I {} bash -c "echo {}; data {}"
答案 3 :(得分:5)
你应该避免parsing ls
:
find . -maxdepth 1 | while read -r file; do do_something_with "$file"; done
或
while read -r file; do do_something_with "$file"; done < <(find . -maxdepth 1)
后者不会在while循环中创建子shell。
答案 4 :(得分:3)
常用方法是:
ls * | while read file; do data "$file"; done
for file in *; do data "$file"; done
如果文件名中有空格,第二个可能会遇到问题;在这种情况下,您可能希望确保它在子shell中运行,并设置IFS:
( IFS=$'\n'; for file in *; do data "$file"; done )
您可以轻松地将第一个包装在脚本中:
#!/bin/bash
# map.bash
while read file; do
"$1" "$file"
done
可以按照您的要求执行 - 请注意不要随意执行任何愚蠢的操作。使用循环结构的好处是,您可以轻松地将多个命令放在其中作为单行的一部分,与xargs不同,您必须将它们放在可执行脚本中才能运行。
当然,您也可以使用实用程序xargs
:
find -maxdepth 0 * | xargs -n 1 data
请注意,如果您通常使用指示符,则应确保指示符已关闭(ls --indicator-style=none
),或者附加到符号链接的@
会将指示符变为不存在的文件名。
答案 5 :(得分:3)
GNU Parallel专门制作这些映射:
parallel data ::: *
它将在每个CPU核心上并行运行一个作业。
GNU Parallel是一个通用的并行程序,可以很容易地在同一台机器上或在你有ssh访问权限的多台机器上并行运行作业。
如果要在4个CPU上运行32个不同的作业,并行化的直接方法是在每个CPU上运行8个作业:
GNU Parallel会在完成后生成一个新进程 - 保持CPU处于活动状态,从而节省时间:
<强>安装强>
如果没有为您的发行版打包GNU Parallel,您可以进行个人安装,不需要root访问权限。这可以在10秒内完成:
(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash
有关其他安装选项,请参阅http://git.savannah.gnu.org/cgit/parallel.git/tree/README
了解详情
查看更多示例:http://www.gnu.org/software/parallel/man.html
观看介绍视频:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
完成教程:http://www.gnu.org/software/parallel/parallel_tutorial.html
注册电子邮件列表以获得支持:https://lists.gnu.org/mailman/listinfo/parallel
答案 6 :(得分:2)
由于你在“地图”方面特别问过这个问题,我想我会在我的个人shell库中分享这个功能:
# map_lines: evaluate a command for each line of input
map_lines()
{
while read line ; do
$1 $line
done
}
我以你解决方案的方式使用它:
$ ls | map_lines ./data
我将它命名为map_lines而不是map,因为我假设有一天我可以实现map_args,你可以像这样使用它:
$ map_args ./data *
该功能如下所示:
map_args()
{
cmd="$1" ; shift
for arg ; do
$cmd "$arg"
done
}
答案 7 :(得分:1)
试试这个:
for i in *; do echo ${i}: `data $i`; done
答案 8 :(得分:0)
您可以像这样创建一个shell脚本:
#!/bin/bash
cd /path/to/your/dir
for file in `dir -d *` ; do
./data "$file"
done
循环遍历/ path /到/ your / dir中的每个文件,并在其上运行“数据”脚本。一定要chmod上面的脚本,以便它是可执行的。
答案 9 :(得分:0)
您也可以使用PRLL。
答案 10 :(得分:0)
ls
不处理文件名中的空格,换行和其他时髦的东西,应该尽可能避免。
find
仅在您想要潜入子目录时,或者如果您想使用其他选项(mtime,size,您的名字)时才有用。
但是很多命令自己处理多个文件,所以不需要for循环:
for d in * ; do du -s $d; done
但是
du -s *
md5sum e*
identify *jpg
grep bash ../*.sh
答案 11 :(得分:0)
我刚刚写了这个脚本来专门解决同样的需求。
http://gist.github.com/kindaro/4ba601d19f09331750bd
它使用find
构建一个文件集来进行转置,这样可以更精细地选择要映射的文件,但也允许窗口出现更难的错误。
我设计了两种操作模式:第一种模式使用&#34;源文件&#34;运行命令。和&#34;目标文件&#34;参数,而第二种模式将源文件内容作为stdin 提供给命令,并将其stdout写入目标文件。
我们可能进一步考虑添加支持并行执行,并且可能将自定义查找参数集限制为一些最必要的参数。我不确定这是否是正确的事情。