bash“map”等效:对每个文件运行命令

时间:2010-04-14 19:25:11

标签: bash shell map

我经常有一个处理一个文件的命令,我想在目录中的每个文件上运行它。有没有内置的方法来做到这一点?

例如,假设我有一个程序data,它输出一个关于文件的重要数字:

./data foo
137
./data bar
42

我想以某种方式在目录中的每个文件上运行它:

map data `ls *`
ls * | map data

产生这样的输出:

foo: 137
bar: 42

12 个答案:

答案 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个作业:

Simple scheduling

GNU Parallel会在完成后生成一个新进程 - 保持CPU处于活动状态,从而节省时间:

GNU Parallel scheduling

<强>安装

如果没有为您的发行版打包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写入目标文件。

我们可能进一步考虑添加支持并行执行,并且可能将自定义查找参数集限制为一些最必要的参数。我不确定这是否是正确的事情。