4

在 Bash 中假设以下内容:

declare -A ar='([one]="1" [two]="2" )'
declare -a ari='([0]="one" [1]="two")'
for i in ${!ari[@]}; do 
  echo $i ${ari[i]} ${ar[${ari[i]}]}
done
0 one 1
1 two 2

GNU Parallel 也可以这样做,确保使用关联数组的索引,而不是序列?数组无法导出的事实是否使这变得困难,如果不是不可能的话?

4

3 回答 3

3

是的,这让它变得更加棘手。但并非不可能。

您不能直接导出数组。但是,您可以使用 将数组转换为同一数组的描述declare -p,并且可以将该描述存储在可导出变量中。实际上,您可以将该描述存储在一个函数中并导出该函数,尽管这有点 hack,而且您必须处理declare在函数内部执行命令会使声明的变量成为局部变量的事实,因此您需要引入-g在生成的declare函数中添加一个标志。

更新:由于 shellshock,上述 hack 不起作用。主题上的一个小变化确实有效。因此,如果您的 bash 已更新,请跳至副标题“ShellShock 版本”。

因此,这是生成可导出函数的一种可能方法:

make_importer () {
  local func=$1; shift; 
  export $func='() {
    '"$(for arr in $@; do
          declare -p $arr|sed '1s/declare -./&g/'
        done)"'
  }'
}

现在我们可以创建我们的数组并为它们构建一个导出的导入器:

$ declare -A ar='([one]="1" [two]="2" )'
$ declare -a ari='([0]="one" [1]="two")'
$ make_importer ar_importer ar ari

看看我们建立了什么

$ echo "$ar_importer"
() {
    declare -Ag ar='([one]="1" [two]="2" )'
declare -ag ari='([0]="one" [1]="two")'
  }

好的,格式有点难看,但这与空格无关。不过,这是黑客。我们所拥有的只是一个普通的(尽管是导出的)变量,但是当它被导入子shell时,会发生一些神奇的事情[注1]:

$ bash -c 'echo "$ar_importer"'

$ bash -c 'type ar_importer'
ar_importer is a function
ar_importer () 
{ 
    declare -Ag ar='([one]="1" [two]="2" )';
    declare -ag ari='([0]="one" [1]="two")'
}

它看起来也更漂亮。现在我们可以在我们给出的命令中运行它parallel

$ printf %s\\n ${!ari[@]} |
    parallel \
      'ar_importer; echo "{}" "${ari[{}]}" "${ar[${ari[{}]}]}"'
0 one 1
1 two 2

或者,在远程机器上执行:

$ printf %s\\n ${!ari[@]} |
    parallel -S localhost --env ar_importer \
      'ar_importer; echo "{}" "${ari[{}]}" "${ar[${ari[{}]}]}"'
0 one 1
1 two 2

ShellShock 版本。

不幸的是,对 shellshock 的一系列修复使完成相同任务变得更加困难。特别是,现在需要导出一个名为foo环境变量的函数BASH_FUNC_foo%%,它是一个无效的名称(因为百分号)。但是我们仍然可以定义函数(使用eval)并导出它,如下所示:

make_importer () {
  local func=$1; shift; 
  # An alternative to eval is:
  #    . /dev/stdin <<< ...
  # but that is neither less nor more dangerous than eval.
  eval "$func"'() {
    '"$(for arr in $@; do
          declare -p $arr|sed '1s/declare -./&g/'
        done)"'
  }'
  export -f "$func"
}

如上所述,我们可以构建数组并制作一个导出器:

$ declare -A ar='([one]="1" [two]="2" )'
$ declare -a ari='([0]="one" [1]="two")'
$ make_importer ar_importer ar ari

但是现在,函数实际上作为函数存在于我们的环境中:

$ type ar_importer
ar_importer is a function
ar_importer () 
{ 
    declare -Ag ar='([one]="1" [two]="2" )';
    declare -ag ari='([0]="one" [1]="two")'
}

由于它已被导出,我们可以在我们提供的命令中运行它parallel

$ printf %s\\n ${!ari[@]} |
    parallel \
      'ar_importer; echo "{}" "${ari[{}]}" "${ar[${ari[{}]}]}"'
0 one 1
1 two 2

不幸的是,它不再适用于远程机器(至少在parallel我可用的版本中),因为parallel它不知道如何导出函数。如果这个问题得到解决,以下应该可以工作:

$ printf %s\\n ${!ari[@]} |
    parallel -S localhost --env ar_importer \
      'ar_importer; echo "{}" "${ari[{}]}" "${ar[${ari[{}]}]}"'

但是,有一个重要的警告:您不能将带有 shellshock 补丁的 bash 中的函数导出到没有补丁的 bash,反之亦然。因此,即使parallel得到修复,远程机器也必须运行与本地机器相同的 bash 版本。(或者至少,两者或两者都必须有 shellshock 补丁。)


注 1:神奇的是,将bash导出的变量标记为函数的方式是导出的值完全以 . 开头() {。因此,如果您导出一个以这些字符开头并且是语法正确函数的变量,那么bash子shell 会将其视为函数。(不过,不要指望bash非子shell 能够理解。)

于 2014-07-27T05:30:10.333 回答
1

GNU Parallel 是一个 perl 程序。如果 perl 程序无法访问变量,那么我看不到 perl 程序可以传递变量的方法。

因此,如果您想并行化循环,我会看到两个选项:

declare -A ar='([one]="1" [two]="2" )'
declare -a ari='([0]="one" [1]="two")'
for i in ${!ari[@]}; do 
  sem -j+0 echo $i ${ari[i]} ${ar[${ari[i]}]}
done

sem解决方案不会防止混合输出。

declare -A ar='([one]="1" [two]="2" )'
declare -a ari='([0]="one" [1]="two")'
for i in ${!ari[@]}; do 
  echo echo $i ${ari[i]} ${ar[${ari[i]}]}
done | parallel
于 2014-07-27T04:29:05.040 回答
1

4年发生了很多事情。GNU Parallel 20190222 带有env_parallel. 这是一个 shell 函数,可以将大部分环境导出到 GNU Parallel 运行的命令。

它在ash, bash, csh, dash, fish, ksh, mksh, pdksh, sh, tcsh, 和中受支持zsh。支持因外壳而异(请参阅https://www.gnu.org/software/parallel/env_parallel.html上的详细信息)。因为bash你会这样做:

# Load the env_parallel function
. `which env_parallel.bash`
# Ignore variables currently defined
env_parallel --session
[... define your arrays, functions, aliases, and variables here ...]
env_parallel my_command ::: values
# The environment is also exported to remote systems if they use the same shell
(echo value1; echo value2) | env_parallel -Sserver1,server2 my_command
# Optional cleanup
env_parallel --end-session

所以在你的情况下是这样的:

env_parallel --session
declare -A ar='([one]="1" [two]="2" )'
declare -a ari='([0]="one" [1]="two")'
foo() {
  for i in ${!ari[@]}; do 
    echo $i ${ari[i]} ${ar[${ari[i]}]}
  done;
}
env_parallel foo ::: dummy
env_parallel --end-session

如您所料env_parallel,它比 pure 慢一点parallel

于 2019-03-24T19:58:06.363 回答