Экспорт массива в bash script

Я не могу экспортировать массив из bash script в другой bash script следующим образом:

export myArray[0]="Hello"
export myArray[1]="World"

Когда я пишу вот так, нет проблем:

export myArray=("Hello" "World")

По нескольким причинам мне нужно инициализировать мой массив в несколько строк. У вас есть решение?

Ответ 1

Переменные массива не могут (пока) экспортироваться.

Из manpage bash версии 4.1.5 под ubuntu 10.04.

Следующее выражение от Чет Рами (текущий bash сопровождающий по состоянию на 2011 год), вероятно, является самой официальной документацией об этой "ошибке":

На самом деле нет хорошего способа кодирования переменной массива в среде.

http://www.mail-archive.com/[email protected]/msg01774.html

Ответ 2

TL; DR: экспортируемые массивы напрямую не поддерживаются вплоть до bash -4.3, но вы можете (эффективно) экспортировать массивы одним из двух способов:

  • простая модификация способа вызова дочерних скриптов
  • используйте экспортированную функцию для хранения инициализации массива с простой модификацией дочерних скриптов

Или вы можете подождать, пока не будет выпущено bash -4.3 (в состоянии разработки /RC по состоянию на февраль 2014 года, см. ARRAY_EXPORT в списке изменений). Обновление: эта функция не включена в 4.3, Если вы определяете ARRAY_EXPORT при построении, сборка завершится неудачно. Автор заявил, что это не планируется завершить.


Первое, что нужно понять, это то, что среда bash (более правильно среда выполнения команд) отличается от концепции среды POSIX, среда POSIX представляет собой набор непечатаемых пар name=value и может передаваться от процесса к его дочерним по-разному (фактически ограниченная форма IPC).

Рабочая среда bash представляет собой надмножество этого, с типизированными переменными, только для чтения и экспортируемых флагов, массивов, функций и т.д. Это отчасти объясняет, почему отличие от set (bash builtin) и env или printenv отличается.

Когда вы вызываете другую оболочку bash, вы начинаете новый процесс, вы теряете некоторое состояние bash. Однако, если вы используете dot-source a script, script запускается в той же среде; или если вы запускаете подоболочку через ( ), среда также сохраняется (поскольку bash forks, сохраняя полное состояние, а не повторно инициализируя использование среды процесса).


Ограничение, указанное в ответе @lesmana, возникает из-за того, что среда POSIX представляет собой просто пары name=value без лишнего значения, поэтому нет согласованного способа кодирования или форматирования типизированных переменных, см. ниже интересную bash quirk относительно функций и предстоящее изменение bash -4.3 (функция предлагаемого массива оставлена).

Существует несколько простых способов сделать это, используя declare -p (встроенный), чтобы вывести некоторую среду bash в виде набора из одного или нескольких операторов declare, которые могут быть использованы для восстановления типа и значение "имя". Это базовый serialisation, но с меньшей степенью сложности некоторые из других ответов подразумевают. declare -p сохраняет индексы массивов, разреженные массивы и цитирует неприятные значения. Для простой сериализации массива вы можете просто сбрасывать значения по строкам и использовать read -a myarray для его восстановления (работает со смежными 0-индексированными массивами, поскольку read -a автоматически назначает индексы).

Эти методы не требуют какой-либо модификации script (s), с которой вы передаете массивы.

declare -p array1 array2 > .bash_arrays       # serialise to an intermediate file
bash -c ". .bash_arrays; . otherscript.sh"    # source both in the same environment

Вариации в приведенной выше форме bash -c "..." иногда (неверно) используются в crontabs для установки переменных.

Альтернативы включают:

declare -p array1 array2 > .bash_arrays       # serialise to an intermediate file
BASH_ENV=.bash_arrays otherscript.sh          # non-interactive startup script

Или, как однострочный:

BASH_ENV=<(declare -p array1 array2) otherscript.sh

Последний использует замену процесса для передачи вывода команды declare как rc script. (Этот метод работает только в bash -4.0 или новее: более ранние версии безоговорочно fstat() rc файлов и используют размер, возвращенный в read() файл за один раз, FIFO возвращает размер 0, и так не будет работайте в надежде.)

В неинтерактивной оболочке (т.е. оболочке script) файл, на который указывает переменная BASH_ENV, автоматически получает. Вы должны убедиться, что bash правильно вызывается, возможно, используя shebang для вызова "bash" явно, а не #!/bin/sh, поскольку bash не будет чтить BASH_ENV в режиме истории /POSIX.

Если у всех имен массивов есть общий префикс, вы можете использовать declare -p ${!myprefix*}, чтобы развернуть их список, а не перечислять их.

Вероятно, вам не следует пытаться экспортировать и повторно импортировать всю среду bash с помощью этого метода, некоторые специальные переменные bash и массивы доступны только для чтения, и при изменении специальных переменных могут быть другие побочные эффекты.

(Вы также можете сделать что-то немного неприятное, сериализуя определение массива на экспортируемую переменную и используя eval, но не поощряйте использование eval...

$ array=([1]=a [10]="b c")
$ export scalar_array=$(declare -p array)
$ bash # start a new shell
$ eval $scalar_array
$ declare -p array
declare -a array='([1]="a" [10]="b c")'

)


Как упоминалось выше, есть интересная причуда: специальная поддержка для экспорта функций через среду:

function myfoo() {
    echo foo
}

с export -f или set +a, чтобы включить это поведение, приведет к этому в среде (процесса), видимой с помощью printenv:

myfoo=() { echo foo
}

Переменная functionname (или functioname() для обратной совместимости) и ее значение () { functionbody }. Когда последующий процесс bash запускается, он будет воссоздавать функцию из каждой такой переменной среды. Если вы заглянете в исходный файл bash -4.2 variables.c, вы увидите, что переменные, начинающиеся с () {, обрабатываются специально. (Хотя создание функции с использованием этого синтаксиса с помощью declare -f запрещено.) Обновление: проблема безопасности shellshock" связана с этой функцией, современной системы могут отключить автоматический импорт функций из среды в качестве смягчения.

Если вы продолжаете читать, вы увидите защитный код #if 0 (или #if ARRAY_EXPORT), который проверяет переменные, начинающиеся с ([ и заканчивая на ), и комментарий, в котором указано, что переменные массива могут пока не отображаться экспортироваться ". Хорошая новость заключается в том, что в текущей версии разработки bash -4.3rc2 включена возможность экспорта индексированных массивов (не ассоциативных). Эта функция вряд ли будет включена, как указано выше.

Мы можем использовать это для создания функции, которая восстанавливает любые требуемые данные массива:

% function sharearray() {
    array1=(a b c d)
}

% export -f sharearray 

% bash -c 'sharearray; echo ${array1[*]}'

Итак, аналогично предыдущему подходу, вызовите дочерний элемент script с помощью

bash -c "sharearray; . otherscript.sh"

Или вы можете условно вызвать функцию sharearray в дочернем script, добавив в некоторой подходящей точке:

[ "`type -t sharearray`" = "function" ] && sharearray

Обратите внимание, что в sharearray нет функции declare -a, если вы это сделаете, то массив неявно локален для функции, чего не требуется. bash -4.2 поддерживает declare -g, который явно делает переменную global, так что (declare -ga) можно было бы использовать тогда. (Поскольку для ассоциативных массивов требуется declare -a, вы не сможете использовать этот метод для ассоциативных массивов до bash -4.2.) Документация GNU parallel имеет полезные вариации в этом методе, см. Обсуждение --env на странице .


Ваш вопрос, как указано, также указывает на то, что у вас могут возникнуть проблемы с самим export. Вы можете экспортировать имя после его создания или изменения. "exportable" - это флаг или свойство переменной, для удобства вы также можете устанавливать и экспортировать в одном выражении. До bash -4.2 export ожидает только имя, либо простая (скалярная) переменная, либо имя функции.

Даже если бы вы могли (в будущем) экспортировать массивы, экспорт выбранных индексов (срез) может не поддерживаться (хотя, поскольку массивы разрежены, нет причин, по которым это невозможно). Хотя bash также поддерживает синтаксис declare -a name[0], индекс игнорируется, а "name" является просто нормальным индексированным массивом.

Ответ 3

Как сообщалось Lesmana, вы не можете экспортировать массивы. Поэтому вы должны их сериализовать, прежде чем проходить через среду. Эта сериализация полезно для других мест, где подходит только строка (su -c 'string', ssh host 'string'). Самый короткий способ сделать это - злоупотреблять 'getopt'

# preserve_array(arguments). return in _RET a string that can be expanded
# later to recreate positional arguments. They can be restored with:
#   eval set -- "$_RET"
preserve_array() {
    _RET=$(getopt --shell sh --options "" -- -- "[email protected]") && _RET=${_RET# --}
}

# restore_array(name, payload)
restore_array() {
   local name="$1" payload="$2"
   eval set -- "$payload"
   eval "unset $name && $name=("\[email protected]")"
}

Используйте его следующим образом:

foo=("1: &&& - *" "2: two" "3: %# abc" )
preserve_array "${foo[@]}"
foo_stuffed=${_RET}
restore_array newfoo "$foo_stuffed"
for elem in "${newfoo[@]}"; do echo "$elem"; done

## output:
# 1: &&& - *
# 2: two
# 3: %# abc

Это не относится к unset/разреженным массивам. Возможно, вы сможете уменьшить 2 'eval' вызовы в файле restore_array.

Ответ 4

Я редактировал другую запись и делал ошибку. Augh. В любом случае, возможно, это может помочь? https://stackoverflow.com/a/11944320/1594168

Обратите внимание, что поскольку формат массива оболочки недокументирован в bash или любой другой стороне оболочки, очень трудно возвратить массив оболочки независимо от платформы. Вам нужно будет проверить версию, а также создать простой script, который объединяет все массивы массивов в файл, который могут разрешить другие процессы.

Однако , если вы знаете имя массива, которое хотите вернуть домой, тогда есть способ, хотя и немного грязный.

Скажем, у меня

MyAry[42]="whatever-stuff";
MyAry[55]="foo";
MyAry[99]="bar";

Итак, я хочу взять его домой

name_of_child=MyAry
take_me_home="`declare -p ${name_of_child}`";
export take_me_home="${take_me_home/#declare -a ${name_of_child}=/}"

Мы видим, что он экспортируется, проверяя из подпроцесса

echo ""|awk '{print "from awk =["ENVIRON["take_me_home"]"]";  }'

Результат:

from awk =['([42]="whatever-stuff" [55]="foo" [99]="bar")']

Если мы абсолютно обязаны, используйте env var, чтобы сбросить его.

env > some_tmp_file

Тогда

Перед запуском другого script,

# This is the magic that does it all
source some_tmp_file

Ответ 5

вы (hi!) можете использовать это, не нужно писать файл, для ubuntu 12.04, bash 4.2.24

Кроме того, можно экспортировать массив с несколькими строками.

cat → exportArray.sh

function FUNCarrayRestore() {
    local l_arrayName=$1
    local l_exportedArrayName=${l_arrayName}_exportedArray

    # if set, recover its value to array
    if eval '[[ -n ${'$l_exportedArrayName'+dummy} ]]'; then
        eval $l_arrayName'='`eval 'echo $'$l_exportedArrayName` #do not put export here!
    fi
}
export -f FUNCarrayRestore

function FUNCarrayFakeExport() {
    local l_arrayName=$1
    local l_exportedArrayName=${l_arrayName}_exportedArray

    # prepare to be shown with export -p
    eval 'export '$l_arrayName
    # collect exportable array in string mode
    local l_export=`export -p \
        |grep "^declare -ax $l_arrayName=" \
        |sed 's"^declare -ax '$l_arrayName'"export '$l_exportedArrayName'"'`
    # creates exportable non array variable (at child shell)
    eval "$l_export"
}
export -f FUNCarrayFakeExport

проверьте этот пример на терминале bash (работает с bash 4.2.24):

source exportArray.sh
list=(a b c)
FUNCarrayFakeExport list
bash
echo ${list[@]} #empty :(
FUNCarrayRestore list
echo ${list[@]} #profit! :D

Я могу улучшить его здесь

PS: Если кто-то очищает/улучшает/makeItRunFaster, я хотел бы знать /see, спасибо!: D

Ответ 6

Для массивов со значениями без пробелов я использовал простой набор функций для итерации по каждому элементу массива и конкатенации массива:

_arrayToStr(){
    array=([email protected])

    arrayString=""
    for (( i=0; i<${#array[@]}; i++ )); do
        if [[ $i == 0 ]]; then
            arrayString="\"${array[i]}\""
        else
            arrayString="${arrayString} \"${array[i]}\""
        fi
    done

    export arrayString="(${arrayString})"
}

_strToArray(){
    str=$1

    array=${str//\"/}
    array=(${array//[()]/""})

    export array=${array[@]}
}

Первая функция с превращением массива в строку путем добавления открывающих и закрывающих круглых скобок и избежания всех двойных кавычек. Вторая функция разделит кавычки и круглые скобки и поместит их в фиктивный массив.

Чтобы экспортировать массив, вы должны передать все элементы исходного массива:

array=(foo bar)
_arrayToStr ${array[@]}

В этот момент массив экспортируется в значение $arrayString. Чтобы импортировать массив в файл назначения, переименуйте массив и выполните противоположное преобразование:

_strToArray "$arrayName"
newArray=(${array[@]})

Ответ 7

Огромное спасибо @stéphane-chazelas, который указал на все проблемы с моими предыдущими попытками, теперь это работает для сериализации массива в stdout или в переменную.

Этот метод не анализирует входные данные (в отличие от declare -a/declare -p) и поэтому защищен от вредоносной вставки метасимволов в сериализованном тексте.

Примечание: строки новой строки не экранируются, потому что read удаляет пару символов \<newlines>, поэтому -d ... вместо этого следует передавать для чтения, а затем не сохраняются символы новой строки.

Все это управляется в функции unserialise.

Используются два волшебных символа, разделитель полей и разделитель записей (так что несколько массивов могут быть сериализованы в один поток).

Эти символы могут быть определены как FS и RS, но ни один из них не может быть определен как символ newline, поскольку экранированная строка новой строки удаляется read.

Символ escape должен быть \ обратная косая черта, так как это то, что используется read, чтобы избежать распознавания символа в качестве символа IFS.

serialise будет сериализован "[email protected]" в stdout, serialise_to будет сериализоваться для переменной, названной в $1

serialise() {
  set -- "${@//\\/\\\\}" # \
  set -- "${@//${FS:-;}/\\${FS:-;}}" # ; - our field separator
  set -- "${@//${RS:-:}/\\${RS:-:}}" # ; - our record separator
  local IFS="${FS:-;}"
  printf ${SERIALIZE_TARGET:+-v"$SERIALIZE_TARGET"} "%s" "$*${RS:-:}"
}
serialise_to() {
  SERIALIZE_TARGET="$1" serialise "${@:2}"
}
unserialise() {
  local IFS="${FS:-;}"
  if test -n "$2"
  then read -d "${RS:-:}" -a "$1" <<<"${*:2}"
  else read -d "${RS:-:}" -a "$1"
  fi
}

и unserialise с:

unserialise data # read from stdin

или

unserialise data "$serialised_data" # from args

например.

$ serialise "Now is the time" "For all good men" "To drink \$drink" "At the \`party\`" $'Party\tParty\tParty'
Now is the time;For all good men;To drink $drink;At the `party`;Party   Party   Party:

(без конечной строки перевода строки)

прочитайте его:

$ serialise_to s "Now is the time" "For all good men" "To drink \$drink" "At the \`party\`" $'Party\tParty\tParty'
$ unserialise array "$s"
$ echo "${array[@]/#/$'\n'}"

Now is the time 
For all good men 
To drink $drink 
At the `party` 
Party   Party   Party

или

unserialise array # read from stdin

Bash read соответствует escape-символу \ (если вы не передаете флаг -r), чтобы удалить особое значение символов, например, для разделения поля ввода или ограничения строки.

Если вы хотите сериализовать массив вместо простого списка аргументов, просто передайте свой массив в список аргументов:

serialise_array "${my_array[@]}"

Вы можете использовать unserialise в цикле, как и вы, read, потому что это всего лишь обернутое чтение, но помните, что поток не разделен на новую строку:

while unserialise array
do ...
done

Ответ 8

Основываясь на использовании @mr.spuratic BASH_ENV, здесь я туннель [email protected] через script -f -c

script -c <command> <logfile> может использоваться для запуска команды внутри другой pty (и группы процессов), но она не может передать какие-либо структурированные аргументы в <command>.

Вместо <command> является простой строкой, которая будет аргументом для вызова библиотеки system.

Мне нужно туннелировать [email protected] внешнего bash в [email protected] из bash, вызванного script.

Поскольку declare -p не может принимать @, здесь я использую магическую переменную bash _ (с фиктивным значением первого массива, которое будет перезаписано bash). Это избавляет меня от любых важных переменных:

Доказательство концепции: BASH_ENV=<( declare -a _=("" "[email protected]") && declare -p _ ) bash -c 'set -- "${_[@]:1}" && echo "[email protected]"'

"Но," вы говорите ", вы передаете аргументы в bash - и, действительно, я, но это простая строка известного символа. Здесь используется script

SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "[email protected]") && declare -p _ && echo 'set -- "${_[@]:1}"') script -f -c 'echo "[email protected]"' /tmp/logfile

который дает мне эту функцию-обертку in_pty:

in_pty() { SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "[email protected]") && declare -p _ && echo 'set -- "${_[@]:1}"') script -f -c 'echo "[email protected]"' /tmp/logfile }

или эта обертка без функций в качестве составной строки для Make файлов:

in_pty=bash -c 'SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "[email protected]") && declare -p _ && echo '"'"'set -- "$${_[@]:1}"'"'"') script -qfc '"'"'"[email protected]"'"'"' /tmp/logfile' --

...

$(in_pty) test --verbose [email protected] $^

Ответ 9

Jeez. Я не знаю, почему другие ответы сделали это настолько сложным. Bash имеет почти встроенную поддержку для этого.

В экспортировании script:

myArray=( '  foo"bar  ' $'\n''\nbaz)' )  # an array with two nasty elements

myArray="${myArray[@]@Q}" ./importing_script.sh

(Обратите внимание: двойные кавычки необходимы для правильной обработки пробелов внутри элементов массива.)

При входе в importing_script.sh значение переменной среды myArray содержит эти 26 байт:

'  foo"bar  ' $'\n\\nbaz)'

Затем восстанавливается массив:

eval "myArray=( ${myArray} )"

ПРЕДОСТЕРЕЖЕНИЕ! Не eval, если вы не можете доверять источнику переменной среды myArray. Этот трюк демонстрирует уязвимость "Little Bobby Tables" . Представьте, чтобы кто-то установил значение myArray в ) ; rm -rf / #.