在GNU Parallel中访问关联数组

问题描述 投票:2回答:3

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

arrays bash parallel-processing associative-array gnu-parallel
3个回答
1
投票

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

它在ashbashcshdashfishkshmkshpdkshshtcshzsh中得到支持。支持因shell而异(请参阅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比纯parallel慢一点。


3
投票

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

您无法直接导出数组。但是,您可以使用declare -p将数组转换为相同数组的描述,并且可以将该描述存储在可导出变量中。实际上,你可以将该描述存储在一个函数中并导出该函数,虽然这有点像黑客,你必须处理这样一个事实,即在函数内执行declare命令会使声明的变量成为局部,所以你需要在生成的-g函数中引入declare标志。

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

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

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
投票

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
© www.soinside.com 2019 - 2024. All rights reserved.