在GNU Parallel中访问关联数组

时间:2014-07-27 03:58:52

标签: arrays bash parallel-processing associative-array gnu-parallel

在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可以做同样的事情,确保使用关联数组的索引,而不是序列吗?数组无法导出的事实是否会使这很困难,如果不是不可能的话?

3 个答案:

答案 0 :(得分:3)

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

您无法直接导出数组 。但是,您可以使用declare -p将数组转换为同一数组的描述,并且可以将该描述存储在可导出变量中。实际上,您可以将该描述存储在函数中并导出该函数,尽管它有点像黑客,并且您必须处理在函数内执行declare命令使得声明的事实变量本地,所以你需要 在生成的-g函数中引入declare标记。

更新:自shellshock以来,上述黑客攻击无效。主题的一个小变化确实有效。因此,如果您的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个子壳能够理解。)

答案 1 :(得分: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

答案 2 :(得分:1)

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

ashbashcshdashfishkshmksh,{ {1}},pdkshshtcsh。外壳对支持的支持各不相同(请参见https://www.gnu.org/software/parallel/env_parallel.html的详细信息)。对于zsh,您可以这样做:

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慢一点。