Доступ к ассоциативным массивам в 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
,