在find -exec调用中执行用户定义的函数


25

我在Solaris 10上,并且已经使用ksh(88),bash(3.00)和zsh(4.2.1)测试了以下内容。

以下代码不会产生任何结果:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

这一发现不匹配多个文件(如更换-exec ...-print),并从外面叫时功能完美的作品find调用。

这是该man find页面所说的-exec

 -exec命令如果执行的命令返回a,则为true。
                     零值作为退出状态。结尾
                     命令必须由逃逸者标点
                     分号(;)。命令参数{}是
                     替换为当前路径名。如果
                     -exec的最后一个参数是{},而您
                     指定+而不是分号(;),
                     该命令被调用的次数更少,
                     {}替换为路径名组。如果
                     该命令的任何调用都会返回一个
                     非零值作为退出状态,找到
                     返回非零退出状态。

我可能会逃脱做这样的事情:

for f in $(find somedir); do
    foo
done

但是我担心要处理字段分隔符问题。

是否可以从调用中调用Shell函数(在同一脚本中定义,让我们不用担心范围问题)find ... -exec ...

我尝试了两者/usr/bin/find/bin/find并得到了相同的结果。


声明后是否尝试导出函数?export -f foo
h3rrmiller 2012年

2
您将需要使函数成为外部脚本并将其放入PATH。或者,使用sh -c '...'和都...在位中定义和运行函数。可能有助于理解功能和脚本之间差异
jw013 2012年

Answers:


27

A function是外壳程序的局部find -exec变量,因此您需要生成一个外壳程序并在该外壳程序中定义该功能,然后才能使用它。就像是:

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bash允许使用导出环境中的函数export -f,因此您可以执行(以bash 格式):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88必须typeset -fx导出功能(不通过环境),但是只能由她来敲打更少由执行的脚本ksh,因此不能与一起使用ksh -c

另一种选择是:

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

也就是说,用于typeset -f转储foo内联脚本中函数的定义。请注意,如果foo使用其他功能,则还需要转储它们。


2
您能解释一下为什么在命令中出现ksh或两次吗?我了解第一次出现,但不了解第二次出现。bash-exec
丹尼尔·库尔曼

6
@danielkullmann在bash -c 'some-code' a b c$0a$1b......,所以,如果你想$@成为a, b, c你需要插入之前的东西。因为$0在显示错误消息时也会用到它,所以最好使用外壳程序的名称,或者在这种情况下有意义的名称。
斯特凡Chazelas

@StéphaneChazelas,谢谢您这么好的回答。
User9102d82

5

这并不总是适用,但是当它适用时,这是一个简单的解决方案。设置globstar选项(set -o globstar在ksh93中,bash≥4 shopt -s globstar;在zsh中默认为启用)。然后使用**/递归地匹配当前目录及其子目录。

例如,代替find . -name '*.txt' -exec somecommand {} \;,您可以运行

for x in **/*.txt; do somecommand "$x"; done

代替find . -type d -exec somecommand {} \;,您可以运行

for d in **/*/; do somecommand "$d"; done

代替find . -newer somefile -exec somecommand {} \;,您可以运行

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

**/不适用于您时(因为您的外壳没有外壳,或者因为您需要一个find没有外壳类似物的选项),请find -exec参数中定义函数


我似乎找不到我正在使用globstar的ksh(ksh88)版本的选项。
rahmu 2012年

@rahmu确实,它是ksh93中的新功能。Solaris 10某处没有ksh93吗?
吉尔(Gilles)'所以

根据此博客文章, ksh93已在Solaris 11中引入,以代替Bourne Shell和ksh88 ...
rahmu 2012年

@rahmu。Solaris的ksh93已有一段时间dtksh(带有一些X11扩展的ksh93),但是它是旧版本,并且可能是可选软件包的一部分,其中CDE是可选的。
斯特凡Chazelas

应当注意的是,递归findglobing的不同之处在于它不包含点文件,并且不归类为dotdirs,并且对文件列表进行排序(二者均可通过globlob限定符在zsh中作为地址)。与**/*相对./**/*,文件名也可以以开头-
斯特凡Chazelas

4

使用\ 0作为分隔符,并从生成的命令中将文件名读取到当前进程中,如下所示:

foo() {
  printf "Hello {%s}\n" "$1"
}

while read -d '' filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

这里发生了什么:

  • read -d '' 读取直到下一个\ 0字节,因此您不必担心文件名中的奇怪字符。
  • 同样,-print0使用\ 0终止每个生成的文件名而不是\ n。
  • cmd2 < <(cmd1)cmd1 | cmd2cmd2在主外壳程序而非子外壳程序中运行相同。
  • 会将对foo的调用重定向到,/dev/null以确保不会意外从管道读取它。
  • $filename 用引号引起来,因此shell不会尝试拆分包含空格的文件名。

现在,read -d<(...)在zsh中时,bash和ksh 93u,但我不知道早期版本KSH。


4

如果您希望从脚本生成的子进程使用预定义的shell函数,则需要使用 export -f <function>

注意:export -f特定于bash

因为只有一个shell才能运行shell函数:

find / -exec /bin/bash -c 'function "$1"' bash {} \;

编辑:本质上您的脚本应类似于此:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;

1
export -f是中的语法错误ksh,并在中将函数定义打印到屏幕上zsh。在所有的kshzsh并且bash,它并没有解决问题。
rahmu 2012年

1
find / -exec /bin/bash -c 'function' \;
h3rrmiller 2012年

现在可以使用了,但仅适用于bash。谢谢!
rahmu 2012年

4
永远不要嵌入{}shell代码!文件名被解释为shell代码这意味着这样是很危险的
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.