使用find -exec执行多个命令

625

我正在尝试使用find -exec来执行多个命令,但没有成功。 有人知道以下命令是否可行吗?

find *.txt -exec echo "$(tail -1 '{}'),$(ls '{}')" \;

基本上,我正在尝试打印当前目录中每个txt文件的最后一行,并在该行末尾打印逗号,然后是文件名。


4
当使用find命令时,如何执行多个命令?可以使用-exec参数来在find命令中执行命令。例如,要在每个找到的文件上运行chmod 755并更改所有目录的拥有者,请运行以下命令:find /path/to/search -type f -exec chmod 755 {} \; -exec chown user:group {} \; -type d -exec chmod 775 {} \; -exec chown user:group {} \;在这个例子中,-type选项用于区分文件类型,{}代表当前找到的文件名,;指示-exec参数的结束。请注意,在最后一个-exec参数之后也需要使用-type选项来匹配目录类型。 - Ignacio Vazquez-Abrams
1
在检查命令是否可行方面,你没有在你的系统上尝试过吗? - Sriram
5
find 手册页面上可以看到:使用 -exec 选项存在无法避免的安全问题,你应该使用 -execdir 选项来代替。详见:http://unixhelp.ed.ac.uk/CGI/man-cgi?find - JVE999
1
相关:https://unix.stackexchange.com/questions/156008/is-it-possible-to-use-find-exec-sh-c-safely - Kusalananda
2
@JVE999的链接已经失效,可以在https://ss64.com/bash/find.html找到替代链接。 - Keith M
14个回答

928

find 命令可以多次使用 -exec 参数。例如:

find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;
请注意,在这种情况下,第二个命令只有在第一个命令成功返回时才会运行,正如 @Caleb 所提到的。如果您想让两个命令无论成功或失败都运行,您可以使用这个结构:
find . -name "*.txt" \( -exec echo {} \; -o -exec true \; \) -exec grep banana {} \;

1
如何进行两次grep?以下命令执行失败:find ./* -exec grep -v 'COLD,' {} ; -exec egrep -i "my_string" {} ; - rajeev
73
如果第一个命令返回成功的话,第二个命令才会执行,否则它将被跳过。这应该在答案中加以说明。 - Caleb
1
请注意其他答案中使用 -n 来抑制 echo 生成的换行符,如果您的第二个命令只产生一行输出并且您希望它们更易于阅读,则这非常方便。 - William Turrell
把第一个-exec的结果传递给grep吗? find . -iname "*.srt" -exec xattr -l {} | grep "@type" \; > text.txt - John
以下是一种方法,仅在第一个命令(echo)失败时运行第二个命令(grep banana):find . -iname '*.zip' \( -exec unzip {} \; -o -exec 7z x {} \; \)。我的用例是我想尝试使用unzip解压缩*.zip文件,如果unzip失败(或找不到),则使用7z - CDuv

143
find . -type d -exec sh -c "echo -n {}; echo -n ' x '; echo {}" \;

6
如果你想要运行Bash而不是Bourne shell,你也可以使用... -exec bash -c ...代替... -exec sh -c ... - Kenny Evitt
19
不要在shell代码中嵌入{}。参见https://unix.stackexchange.com/questions/156008/is-it-possible-to-use-find-exec-sh-c-safely - Kusalananda
7
+1 @Kusalananda。插入文件名是脆弱且不安全的。使用参数。 参见SC2156 - pambda
1
find . -type d -exec sh -c 'echo -n $0; echo -n " x "; echo $0' "{}" \; 解决了上述评论中提出的问题。 - Jamie Pate
我讨厌不得不生成子shell来完成这个任务,因为这会使变量替换变得太复杂;你需要两次转义变量,因为简单地执行sh -c "command ${variable}"很可能会出错。 - Константин Ван

75

以下内容之一:

find *.txt -exec awk 'END {print $0 "," FILENAME}' {} \;

find *.txt -exec sh -c 'echo "$(tail -n 1 "$1"),$1"' _ {} \;

find *.txt -exec sh -c 'echo "$(sed -n "\$p" "$1"),$1"' _ {} \;

20
下划线 {} 前面的下划线是用来做什么的? - qed
7
这是一个代替 $0 的临时值。你可以尝试将下划线 _ 替换为 "foobar" 并执行以下命令: find /usr/bin -name find -exec sh -c 'echo "[$0] [$1]"' foobar {} \;,输出结果为 "[foobar] [/usr/bin/find]"。 - Dennis Williamson
1
@XuWang:是的,我会这么说。就像你所知道的那样,$0通常是程序名称(ARGV[0])。 - Dennis Williamson
5
对于这种方法来说,关键在于传递给 sh -c 命令的脚本要用单引号而不是双引号。否则,$1 变量会出现错误作用域。 - Nick
3
即使您用双引号括起来,只要转义变量(\$1),就可以写成"$1"。您还可以转义其他字符("\"")。@Nick的引用与此无关。 - Camilo Martin
显示剩余9条评论

35

另一种方法是这样的:

multiple_cmd() { 
    tail -n1 $1; 
    ls $1 
}; 
export -f multiple_cmd; 
find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;

一句话概括

multiple_cmd() { tail -1 $1; ls $1 }; export -f multiple_cmd; find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
  • "multiple_cmd()" 是一个函数
  • "export -f multiple_cmd" - 将其导出,以便任何其他子shell都可以看到它
  • "find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;" - 找到并在您的示例上执行该函数

通过这种方式,multiple_cmd可以像您需要的那样长和复杂。

希望这可以帮助您。


完美,正是我所需要的! - Anentropic
@Thomas,它确实可以,但尝试这个一行代码,在OSX上测试过。我创建了一个名为“aaa”的目录,并在其中放置了一些文件/目录,然后CDd到该目录。然后,〜/ aaa $ acmd(){echo x“$ 1”x;}; export-f acmd; find。-exec bash-c'acmd {}'\; - barlop
它停留在“>”上。 - Sandburg

24

有更简单的方法:

find ... | while read -r file; do
    echo "look at my $file, my $file is amazing";
done

或者:

while read -r file; do
    echo "look at my $file, my $file is amazing";
done <<< "$(find ...)"

2
文件名中可以有换行符,这就是为什么find命令有-print0参数,而xargs命令有-0参数的原因。 - abasterfield
3
@abasterfield 我总是希望不要在野外找到它们lol。 - Camilo Martin
1
我想做的是 "find ... -exec zcat {} | wc -l ;",但它没有起作用。然而,find ... | while read -r file; do echo "$file: zcat $file | wc -l"; done 可以工作,所以谢谢! - Greg Dougherty
在上面的评论中,我在“zcat $file | wc -l”周围使用了“反引号”。不幸的是,SO会将其转换为格式,因此我已将其作为实际答案添加,并显示了正确的代码。 - Greg Dougherty
1
@GregDougherty 你可以通过转义反引号\``来实现,方法是使用反斜杠`,像这样:\​\``(不过,这也是使用$()`的另一个好理由)。 - Camilo Martin

12

在扩展@Tinker的答案时,

在我的情况下,我需要在-exec中创建一个command | command | command来打印包含某些文本的文件的文件名和找到的文本。

我使用以下命令实现了这个目标:

find . -name config -type f \( -exec  grep "bitbucket" {} \; -a -exec echo {} \;  \) 

结果是:

    url = git@bitbucket.org:a/a.git
./a/.git/config
    url = git@bitbucket.org:b/b.git
./b/.git/config
    url = git@bitbucket.org:c/c.git
./c/.git/config

3
通过使用一个-exec参数和在grep命令中将/dev/null作为第二个参数传递,您还可以在单行上打印文件名和经过grep筛选的内容: find . -name config -type f -exec grep "bitbucket" {} /dev/null \; - Bill Feth
在这种情况下,您可以执行以下操作:$ find . -name config -type f -exec grep -nl "bitbucket" {} \;它只会打印与之匹配的文件名。 - Paulo Henrique Lellis Gonalves
FYI:grep -H 正是如此。它会打印出文件名和匹配的行。 - Emsi

9

我不知道你是否可以使用find来完成这个任务,但另一种解决方法是创建一个shell脚本,并使用find来运行此脚本。

lastline.sh:

echo $(tail -1 $1),$1

使脚本可执行

chmod +x lastline.sh

使用find
find . -name "*.txt" -exec ./lastline.sh {} \;

8
反引号已过时,请鼓励使用$(...),这种写法更易读、不受字体影响,并且容易嵌套。谢谢。 - user unknown

7
感谢Camilo Martin的帮助,我得以回答一个相关问题:
我想要做的是:
find ... -exec zcat {} | wc -l \;

这并没有起作用。然而,

find ... | while read -r file; do echo "$file: `zcat $file | wc -l`"; done

能够正常工作,所以谢谢你!


5

Denis的第一个答案是解决问题的答案。但实际上,它不再是一个只有一个exec中的几个命令可以找到的东西,就像标题所示。为了回答带有多个命令的单个exec的问题,我们将不得不寻找其他方法来解决。以下是一个例子:

使用一个exec命令并使用多个{}引用,保留最近7天内已修改的.log文件的最后10000行。

1)查看该命令将在哪些文件上执行:

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "echo tail -10000 {} \> fictmp; echo cat fictmp \> {} " \;

2) 执行它:(注意不再有"\>",只有">"是需要的)

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "tail -10000 {} > fictmp; cat fictmp > {} ; rm fictmp" \;


但是我认为,如果其中一个文件名包含空格,这将会出错。 - Camilo Martin

4

我通常将find命令嵌入一个小的for循环一行代码中,其中find命令在使用$()子命令执行。

然后您的命令将如下所示:

for f in $(find *.txt); do echo "$(tail -1 $f), $(ls $f)"; done

好消息是,你不再需要使用{},而是直接使用$f,并且不再需要使用-exec …,而是把所有命令写在do; done之间。

不确定你实际想要做什么,但可能是像这样的东西?

for f in $(find *.txt); do echo $f; tail -1 $f; ls -l $f; echo; done

4
值得注意的是,根据ShellCheck的建议,这并不是最佳解决方案 - SC2044: For loops over find output are fragile. Use find -exec or a while read loop. 在ShellCheck的维基 https://github.com/koalaman/shellcheck/wiki/Sc2044 上有一个很好的示例和描述。 - Alex Baranowski
1
这也正是BashPitfalls#1所警告的。 - Charles Duffy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接