Доступ к ассоциативным массивам в 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, убедившись, что используется индекс ассоциативного массива, а не последовательность? Разве тот факт, что массивы не могут быть экспортированы, затрудняет, если не делает невозможным?

2 ответа

Решение

Да, это делает это сложнее. Но не невозможно.

Вы не можете экспортировать массив напрямую. Тем не менее, вы можете превратить массив в описание этого же массива, используя declare -pи вы можете сохранить это описание в экспортируемой переменной. Фактически, вы можете сохранить это описание в функции и экспортировать функцию, хотя это немного хакер, и вам приходится иметь дело с тем фактом, что выполнение declare Команда внутри функции делает объявленные переменные локальными, поэтому вам нужно ввести -g флаг в сгенерированный declare функции.

ОБНОВЛЕНИЕ: начиная с снарядов, вышеупомянутый взлом не работает. Небольшая вариация на тему работает. Поэтому, если ваш 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")'
  }

Хорошо, форматирование немного уродливо, но это не про пробелы. Вот хак, хотя. Все, что у нас есть, это обычная (хотя и экспортируемая) переменная, но когда она импортируется в подоболочку, случается немного волшебства [Примечание 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.

К сожалению, из-за целого ряда исправлений в скорлупе выполнить ту же задачу немного сложнее. В частности, теперь необходимо экспортировать функцию с именем 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[{}]}]}"'

Однако есть одно важное предостережение: вы не можете экспортировать функцию из bash с исправлением shellshock в bash без исправления или наоборот. Так что даже если parallel После исправления на удаленной машине должна быть установлена ​​та же версия bash, что и на локальной машине. (Или, по крайней мере, либо оба, либо ни один не должен иметь патчи от шелушения.)


Примечание 1: магия в том, что путь bash помечает экспортируемую переменную как функцию в том, что экспортируемое значение начинается именно с () {, Так что если вы экспортируете переменную, которая начинается с этих символов и является синтаксически правильной функцией, то bash подоболочки будут воспринимать это как функцию. (Не ожидайте, чтоbash Подшарики, чтобы понять, хотя.)

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

Много произошло за 4 года. GNU Parallel 20190222 поставляется с env_parallel, Это функция оболочки, которая позволяет экспортировать большую часть среды в команды, запускаемые 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 немного медленнее, чем чистый parallel,

Другие вопросы по тегам