Bash: 为在xargs中使用而导出函数

3

当我的bash脚本变得复杂时,我通常会将它们拆分成函数。这尤其适用于复杂的管道,因为一系列复杂的管道命令(例如包含while循环)很快就会变得难以阅读。特别是在需要并行化时,xargs非常有帮助。

我知道可以使用export -f将函数导出到子shell中,在简单情况下,我可以这样做:

export -f myfunction 
some-command | xargs -Iline bash -c "myfunction 'line'"

但是如果myfunction依赖于其他函数,这将变得难以维护——每当函数发生更改,使得子shell用于执行myfunction的所需函数发生更改时,导出语句都必须更改——这似乎相当容易出错。
有没有一种通用的方法可以导出函数供子shell使用?我在思考类似于“导出所有定义的函数”的命令,然后允许像下面这样的代码结构:
main() { ... }
func1 () { ... }
func2 () { ... }
<export all functions>
main "$@"
4个回答

3
您的问题只问如何导出函数。在bash中很容易,见下面。
您的问题标题/主题暗示可以在xargs中使用函数,就好像它们是脚本一样。 我不知道xargs是否可以直接“调用”bash函数,但您当然可以将导出的函数的使用包装在一个由xargs调用的脚本中,见下面。
首先,一个列出函数的函数。默认情况下是用户函数,并使用-v列出所有函数:
lsfns () {
   case "$1" in
      -v | v*)
         # verbose:
         set | grep '()' --color=always
         ;;
      *)
         declare -F | cut -d" " -f3 | egrep -v "^_"
         ;;
   esac
}

下面是导出所有用户函数的函数:
exportfns () { export -f $(lsfns); }

或者只需将export -f $(lsfns)放置于您的.bashrc中。

示例脚本doit.sh:

#!/bin/bash
lsfns "$@" # make use of function exported by parent shell :)
chmod a+rx doit.sh之后的示例命令行如下:
echo -v | xargs doit.sh

与之相比
echo "" | xargs doit.sh

编辑1:回复kdb下面的评论/答案(“遇到无法导出函数的情况”):

导出shell函数不与Posix兼容,即仅适用于Bash和可能的其他Shell,例如Zsh、Ksh等。

也就是说,在Dash和“标准”的Posix Shell中不提供“export -f”,我们无法导出函数。如果我们在Bash中导出一个函数,然后运行一个以sh-bang开头的脚本,例如“#!/bin/dash”,那么该脚本将无法使用父Shell中“导出”的函数,因为由Bash导出到“进程环境”的函数不被Dash识别。


编辑2:回复OP的评论“但如果myfunction依赖于其他函数,则这将变得难以维护”:

这可能是可以充分利用shell源命令(别名“。”)的情况,例如:

. ~/etc/my-functions.sh; myMain ...

同样地,如果您更喜欢在函数中“生活”,而不是在脚本文件中,例如,当您需要时调用myMain,那么这个函数的第一行可以是引用您的函数库的源代码;
由于在“定期运行脚本”的情况下,这将是多余的开销,因此myMain成为命令行存根函数,用于(重新)加载您的函数库,并调用actuallyDoit函数(如果您有一个脚本文件,则也会从该脚本内部调用该函数)。
祝愉快 Zenaan

实际上我的问题包括使用xargs的示例,所以我不明白你在第二段中的意思。虽然如此,我很感激你的其余回答(exportfns会有所帮助)。 - kdb
我是在为未来的读者澄清,以防有人尝试直接使用xargs调用一个函数,将该函数视为脚本,例如find dir/ | xargs aBashFn这样是行不通的-必须将该函数包装在脚本中,或者像你第一个例子中那样生成一个bash子shell。但分叉bash是一种相当沉重的调用函数的方式,除非你只需要它一次。希望这能解释我为什么写这个的原因... - zenaan
"导出shell函数不兼容Posix - 也就是说,它只能在Bash中使用。谢谢。我之前用的是xargs sh -c,当我将其替换为xargs bash -c时,所有我的export -f函数都可以正常工作了!" - Noah Sussman

1
这个代码似乎可以打印出所有函数名称。它感觉有点脆弱,所以请测试一下。
declare -f | grep -oP '^\S+(?=\s*\(\))'

0

自从得到答案以来,我发现很多情况下都可以使用不同的技术来证明更好:让脚本调用自身。一个简单的例子是

# Print hash and disk usage for each argument
if [[ $1 == run ]]; then
  shift 1
  printf "%s\0" "$@" | xargs -0 -n 1 -P "$NUMBER_OF_PROCESSORS" -- "$0" printpar
elif [[ $1 == printpar ]]; then
  echo ":: $(cat "$2" | sha1sum) $(du -sh "$2")"
else
  echo "Invalid first parameter '$1'"
  exit 1
fi

在现实世界的例子中,我可能会对参数进行假设(例如使用__SUCH__形状来进行自调用关键字),或者将递归调用隐藏在未记录的--command-line-switch后面。
导出函数通常更加优雅,但对于大量函数来说,它可能会变得难以承受,并且我记得曾经遇到过失败的问题。

0
export -f $(compgen -A function)

虽然我最初认为这是一个非常简单易懂的答案,但很遗憾,我发现它会破坏xargs。我收到了“xargs:环境对于exec来说太大”的错误提示。这可能是cygwin特有的问题,因为Windows似乎限制环境变量的长度。 - kdb

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