使用xargs调用shell函数

259

我正在尝试使用xargs并行调用一个更复杂的函数。

#!/bin/bash
echo_var(){
    echo $1
    return 0
}
seq -f "n%04g" 1 100 |xargs -n 1 -P 10 -i echo_var {} 
exit 0

这会返回错误信息

xargs: echo_var: No such file or directory
任何关于如何使用xargs来完成这个任务的想法,或者其他解决方案,都欢迎提供。

3
危险,用户1148366,危险!不要使用Bash进行并行编程,否则你会遇到很多问题。使用C/C++和pthreads,或者Java线程,或者任何让你深入思考的东西,因为并行编程需要充分思考才能正确实现。 - David Souther
38
如果任务是独立的,比如将所有这些图片文件转换为PNG格式,那就不用担心。但如果需要进行同步(超出等待所有任务完成)和通信,情况就会变得混乱。 - ctrl-alt-delor
2
@DavidSouther - 我是一名长期的Java开发人员,最近一直在使用Groovy。我也继续告诉大家:朋友们不要让朋友们编写Bash脚本。然而,我发现自己正在查看这篇帖子/解决方案,因为(难过的脸:()我正在Bash中进行并行处理。我可以轻松地在Groovy / Java中完成它。糟糕! - Christian Bongiorno
还讨论了https://unix.stackexchange.com/questions/158564/how-to-use-defined-function-with-xargs - Joshua Goldberg
6个回答

247

导出该函数应该可以解决问题(未经测试):

export -f echo_var
seq -f "n%04g" 1 100 | xargs -n 1 -P 10 -I {} bash -c 'echo_var "$@"' _ {}
您可以使用内置的printf替代外部的seq
printf "n%04g\n" {1..100} | xargs -n 1 -P 10 -I {} bash -c 'echo_var "$@"' _ {}

此外,像这样使用return 0exit 0掩盖了可能由其前面的命令产生的任何错误值。此外,如果没有错误,则是默认值,因此有些多余。

@phobic提到Bash命令可以简化为

bash -c 'echo_var "{}"'

{}直接移动到目标位置。但正如@Sasha所指出的那样,它容易受到命令注入攻击。

以下是一个示例,说明为什么不应该使用嵌入式格式:

$ echo '$(date)' | xargs -I {} bash -c 'echo_var "{}"'
Sun Aug 18 11:56:45 CDT 2019

另一个说明为什么不要的例子:

echo '\"; date\"' | xargs -I {} bash -c 'echo_var "{}"'

这是使用安全格式输出的结果

$ echo '$(date)' | xargs -I {} bash -c 'echo_var "$@"' _ {}
$(date)

这类似于使用参数化 SQL 查询语句来避免注入

我在这里使用date命令替代Sasha评论中使用的rm命令,因为它是非破坏性的,可以用于命令替换或转义引号。


17
稍微再讨论一下:xargs执行一个全新的进程实例。在这种情况下,您提供了名为“echo_var”的名称,它是此脚本中的一个函数,而不是您的PATH中的进程(程序)。Dennis的解决方案做的是将该函数导出以便子bash进程使用,然后fork到子进程并在那里执行。 - David Souther
10
下划线(_)和反斜杠(\)的重要性是什么?如果没有它们,我的工作将无法进行。 - Hashbrown
14
@Hashbrown:下划线(_)提供了一个占位符,用于代替argv[0]$0),几乎可以使用任何内容。我认为我添加反斜杠分号(\;)是因为它在终止find-exec子句中使用,但是在这里没有也可以正常工作。实际上,如果该函数使用$@而不是$1,那么它会将分号视为参数,因此应将其省略。 - Dennis Williamson
4
xargs 的 -i 参数已被弃用,请使用 -I(大写 i)替代。 - Nicolai S
13
可以通过在bash的命令字符串中包含xargs参数来简化此操作,使用bash -c 'echo_var "{}"'。因此您不需要在末尾使用_ {} - phobic
显示剩余7条评论

19

使用GNU Parallel的样子如下:

#!/bin/bash
echo_var(){
    echo $1
    return 0
}
export -f echo_var
seq -f "n%04g" 1 100 | parallel -P 10 echo_var {} 
exit 0

如果您使用的版本是20170822,只要运行了以下命令,您甚至不需要执行export -f

. `which env_parallel.bash`
seq -f "n%04g" 1 100 | env_parallel -P 10 echo_var {} 

获取以下错误 Ole sh:parallel_bash_environment:第67行:查找匹配''时意外的EOF sh:parallel_bash_environment:第79行:语法错误:意外的文件结尾 sh:导入函数定义时出错,用于“parallel_bash_environment” /usr/local/bin/bash:parallel_bash_environment:第67行:查找匹配''时意外的EOF /usr/local/bin/bash:parallel_bash_environment:第79行:语法错误:意外的文件结尾 /usr/local/bin/bash:导入函数定义时出错,用于... - Nick
你已经被Shellshock攻击影响了:Shellshock并没有直接影响GNU Parallel。然而,解决Shellshock的方法却破坏了--env和env_parallel技巧。据信这个问题已经在git版本中得到修复:http://git.savannah.gnu.org/cgit/parallel.git/snapshot/parallel-master.tar.gz - Ole Tange
1
我喜欢这个答案,因为它让我发现了并行工具。 - JR Utily

16

这样做也应该可以:

function testing() { sleep $1 ; }
echo {1..10} | xargs -n 1 | xargs -I@ -P4 bash -c "$(declare -f testing) ; testing @ ; echo @ "

1
通常情况下,这会在shell特殊字符(例如|#)上中断,并忽略输入中的空格。我建议不要让bash将输入视为代码,而是建议让xargs按原样传递它们。 echo {1..10} | xargs -n 1 -P4 bash -c "$(declare -f testing);"' testing "$@"; echo "$@";' argv0 - EndlosSchleife

4

看起来我无法发表评论:-(

我在思考关注重点的问题

bash -c 'echo_var "$@"' _ {}
vs
bash -c 'echo_var "{}"'

第一个将{}作为参数传递给bash,而第二个将其作为参数传递给函数。示例1未扩展$(date)的事实只是一个副作用。 如果不想扩展函数参数,请使用单引号而非双引号。为了避免混乱的嵌套,请使用双引号(在另一个引号上扩展参数)。
$ echo '$(date)' | xargs -0 -L1 -I {} bash -c 'printit "{}"'
Fri 11 Sep 17:02:24 BST 2020

$ echo '$(date)' | xargs -0 -L1 -I {} bash -c "printit '{}'"
$(date)

3

也许这是不太好的做法,但如果你在.bashrc或其他脚本中定义函数,你可以用allexport设置包装文件或至少函数定义:

set -o allexport

function funcy_town {
  echo 'this is a function'
}
function func_rock {
  echo 'this is a function, but different'
}
function cyber_func {
  echo 'this function does important things'
}
function the_man_from_funcle {
  echo 'not gonna lie'
}
function funcle_wiggly {
  echo 'at this point I\'m doing it for the funny names'
}
function extreme_function {
  echo 'goodbye'
}

set +o allexport

0
你可以创建自己的版本的 xargs,可以处理函数。
使用示例:
function my_echo() {
    echo "$@"
}
echo 1 2 3 | xargs my_echo
# xargs: my_echo: No such file or directory
echo 1 2 3 | xargs2 my_echo
# 1 2 3

xargs2本身:

is_function() {
  if [[ $(type -t "${1?ensure_is_a_function: provide a command}") == "function" ]]; then
    return 0
  else
    return 1
  fi
}

function xargs2 {
  if is_function "$1"; then
    export -f "${1?}" # so subprocess bash can see it
  else
    echo "xargs2: use xargs" >&2
    return 1
  fi
  ARGS=$(printf "%q " "$@") # escape
  cat </dev/stdin | xargs bash -c "$ARGS \$@" _
}

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