Сериализация переменной оболочки в bash или zsh

Сериализация переменной оболочки в bash или zsh

Есть ли способ сериализовать переменную оболочки? Предположим, у меня есть переменная $VAR, и я хочу сохранить ее в файле или где-то еще, а затем прочитать ее позже, чтобы получить то же самое значение?

Есть ли портативный способ сделать это? (Я так не думаю)

Есть ли способ сделать это в bash или zsh?

решение1

Предупреждение:При любом из этих решений вам нужно знать, что вы доверяете целостности файлов данных, поскольку они будут выполняться как шелл-код в вашем скрипте. Их защита имеет первостепенное значение для безопасности вашего скрипта!

Простая встроенная реализация для сериализации одной или нескольких переменных

Да, и в bash, и в zsh вы можете сериализовать содержимое переменной таким образом, чтобы его было легко получить с помощью typesetвстроенной функции и -pаргумента. Формат вывода таков, что вы можете просто получить sourceвывод, чтобы получить обратно свои данные.

 # You have variable(s) $FOO and $BAR already with your stuff
 typeset -p FOO BAR > ./serialized_data.sh

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

# Load up the serialized data back into the current shell
source serialized_data.sh

Это будет работать для bash, zsh и ksh, включая передачу данных между различными оболочками. Bash переведет это в свою встроенную declareфункцию, в то время как zsh реализует это с помощью, typesetно поскольку у bash есть псевдоним для этого, чтобы это работало в любом случае, мы используем его typesetздесь для совместимости с ksh.

Более сложная обобщенная реализация с использованием функций

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

Обратите внимание, что для поддержания кросс-совместимости bash/zsh мы будем исправлять оба случая typesetи, declareпоэтому код должен работать в любой или обеих оболочках. Это добавляет некоторый объем и беспорядок, которые можно было бы устранить, если бы вы делали это только для одной или другой оболочки.

Основная проблема с использованием функций для этой цели (или включением кода в другие функции) заключается в том, что функция typesetгенерирует код, который при передаче обратно в скрипт изнутри функции по умолчанию создает локальную переменную, а не глобальную.

Это можно исправить одним из нескольких хаков. Моя первая попытка исправить это состояла в том, чтобы проанализировать вывод процесса сериализации, sedчтобы добавить -gфлаг, чтобы созданный код определял глобальную переменную при обратном входе.

serialize() {
    typeset -p "$1" | sed -E '0,/^(typeset|declare)/{s/ / -g /}' > "./serialized_$1.sh"
}
deserialize() {
    source "./serialized_$1.sh"
}

Обратите внимание, что забавное sedвыражение должно соответствовать только первому вхождению «typeset» или «declare» и добавлять-g в качестве первого аргумента. Необходимо соответствовать только первому вхождению, поскольку, какСтефан Шазеласправильно отмечено в комментариях, в противном случае это также будет соответствовать случаям, когда сериализованная строка содержит литеральные символы новой строки, за которыми следует слово declare или typeset.

В дополнение к исправлению моего первоначального анализабестактность, Стефан такжепредложенныйменее хрупкий способ взломать это, который не только обходит проблемы с анализом строк, но и может быть полезным приемом для добавления дополнительной функциональности с помощью функции-обертки для переопределения действий, предпринимаемых при обратном получении данных. Это предполагает, что вы не играете ни в какие другие игры с командами declare или typeset, но этот метод было бы проще реализовать в ситуации, когда вы включаете эту функциональность как часть другой собственной функции или вы не контролируете записываемые данные и имеют ли они-g . Нечто подобное можно сделать и с псевдонимами, см.Ответ Жилядля реализации.

Чтобы сделать результат еще более полезным, мы можем перебрать несколько переменных, переданных нашим функциям, предположив, что каждое слово в массиве аргументов является именем переменной. Результат становится примерно таким:

serialize() {
    for var in $@; do
        typeset -p "$var" > "./serialized_$var.sh"
    done
}

deserialize() {
    declare() { builtin declare -g "$@"; }
    typeset() { builtin typeset -g "$@"; }
    for var in $@; do
        source "./serialized_$var.sh"
    done
    unset -f declare typeset
}

При любом решении использование будет выглядеть следующим образом:

# Load some test data into variables
FOO=(an array or something)
BAR=$(uptime)

# Save it out to our serialized data files
serialize FOO BAR

# For testing purposes unset the variables to we know if it worked
unset FOO BAR

# Load  the data back in from out data files
deserialize FOO BAR

echo "FOO: $FOO\nBAR: $BAR"

решение2

Используйте перенаправление, подстановку команд и расширение параметров. Двойные кавычки нужны для сохранения пробелов и специальных символов. Завершающий символ xсохраняет завершающие переводы строк, которые в противном случае были бы удалены при подстановке команд.

#!/bin/bash
echo "$var"x > file
unset var
var="$(< file)"
var=${var%x}

решение3

Сериализовать все — POSIX

В любой оболочке POSIX вы можете сериализовать все переменные окружения с помощьюexport -p. Это не включает неэкспортированные переменные оболочки. Вывод правильно заключен в кавычки, чтобы вы могли прочитать его обратно в той же оболочке и получить точно такие же значения переменных. Вывод может быть нечитаемым в другой оболочке, например, ksh использует не-POSIX синтаксис $'…'.

save_environment () {
  export -p >my_environment
}
restore_environment () {
  . ./my_environment
}

Сериализовать некоторые или все — ksh, bash, zsh

Ksh (pdksh/mksh и ATT ksh), bash и zsh предоставляют лучшие возможности для работы сtypesetвстроенный. typeset -pвыводит все определенные переменные и их значения (zsh пропускает значения переменных, которые были скрыты с помощью typeset -H). Вывод содержит правильное объявление, так что переменные окружения экспортируются при обратном чтении (но если переменная уже экспортирована при обратном чтении, она не будет неэкспортирована), так что массивы считываются обратно как массивы и т. д. Здесь также вывод правильно заключен в кавычки, но гарантируется, что он будет читаемым только в той же оболочке. Вы можете передать набор переменных для сериализации в командной строке; если вы не передадите ни одной переменной, то все будут сериализованы.

save_some_variables () {
  typeset -p VAR OTHER_VAR >some_vars
}

В bash и zsh восстановление невозможно выполнить из функции, поскольку typesetоператоры внутри функции ограничены этой функцией. Вам нужно запустить . ./some_varsв контексте, в котором вы хотите использовать значения переменных, позаботившись о том, чтобы переменные, которые были глобальными при экспорте, были переопределены как глобальные. Если вы хотите считать значения внутри функции и экспортировать их, вы можете объявить временный псевдоним или функцию. В zsh:

restore_and_make_all_global () {
  alias typeset='typeset -g'
  . ./some_vars
  unalias typeset
}

В bash (который использует declareвместо typeset):

restore_and_make_all_global () {
  alias declare='declare -g'
  shopt -s expand_aliases
  . ./some_vars
  unalias declare
}

В ksh typesetобъявляет локальные переменные в функциях, определенных с помощью , function function_name { … }и глобальные переменные в функциях, определенных с помощью function_name () { … }.

Сериализовать некоторые — POSIX

Если вам нужен больший контроль, вы можете экспортировать содержимое переменной вручную. Чтобы распечатать содержимое переменной точно в файл, используйте printfвстроенную функцию ( echoимеет несколько особых случаев, например, echo -nв некоторых оболочках, и добавляет новую строку):

printf %s "$VAR" >VAR.content

Вы можете прочитать это обратно с помощью $(cat VAR.content), за исключением того, что подстановка команды обрезает завершающие переводы строк. Чтобы избежать этой проблемы, сделайте так, чтобы вывод никогда не заканчивался новой строкой.

VAR=$(cat VAR.content && echo a)
if [ $? -ne 0 ]; then echo 1>&2 "Error reading back VAR"; exit 2; fi
VAR=${VAR%?}

Если вы хотите вывести несколько переменных, вы можете заключить их в одинарные кавычки и заменить все встроенные одинарные кавычки на '\''. Эта форма кавычек может быть прочитана обратно в любой оболочке в стиле Bourne/POSIX. Следующий фрагмент работает в любой оболочке POSIX. Он работает только для строковых переменных (и числовых переменных в оболочках, которые их имеют, хотя они будут прочитаны обратно как строки), он не пытается работать с переменными массива в оболочках, которые их имеют.

serialize_variables () {
  for __serialize_variables_x do
    eval "printf $__serialize_variables_x=\\'%s\\'\\\\n \"\$${__serialize_variables_x}\"" |
    sed -e "s/'/'\\\\''/g" -e '1 s/=.../=/' -e '$ s/...$//'
  done
}

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

serialize_variables () {
  for __serialize_variables_var do
    eval "__serialize_variables_tail=\${$__serialize_variables_var}"
    while __serialize_variables_quoted="$__serialize_variables_quoted${__serialize_variables_tail%%\'*}"
          [ "${__serialize_variables_tail%%\'*}" != "$__serialize_variables_tail" ]; do
      __serialize_variables_tail="${__serialize_variables_tail#*\'}"
      __serialize_variables_quoted="${__serialize_variables_quoted}'\\''"
    done
    printf "$__serialize_variables_var='%s'\n" "$__serialize_variables_quoted"
  done
}

Обратите внимание, что в оболочках, которые допускают переменные только для чтения, вы получите ошибку, если попытаетесь считать переменную, доступную только для чтения.

решение4

printf 'VAR=$(cat <<\'$$VAR$$'\n%s\n'$$VAR$$'\n)' "$VAR" >./VAR.file

Другой способ сделать это — убедиться, что вы обрабатываете все 'жесткие кавычки следующим образом:

sed '"s/'"'/&"&"&/g;H;1h;$!d;g;'"s/.*/VAR='&'/" <<$$VAR$$ >./VAR.file
$VAR
$$VAR$$

Или с export:

env - "VAR=$VAR" sh -c 'export -p' >./VAR.file 

Первый и второй варианты работают в любой оболочке POSIX, при условии, что значение переменной не содержит строку:

"\n${CURRENT_SHELLS_PID}VAR${CURRENT_SHELLS_PID}\n" 

Третий вариант должен работать для любой оболочки POSIX, но может попытаться определить другие переменные, такие как _или PWD. Правда в том, что единственные переменные, которые он может попытаться определить, устанавливаются и поддерживаются самой оболочкой — и поэтому даже если вы импортируете exportзначение для любой из них — например, такой $PWD— оболочка просто немедленно сбросит их до правильного значения — попробуйте сделать это PWD=any_valueи убедитесь сами.

И поскольку — по крайней мере в GNU bash— отладочный вывод автоматически заключается в безопасные кавычки для повторного ввода в оболочку, это работает независимо от количества 'жестких кавычек в "$VAR":

 PS4= VAR=$VAR sh -cx 'VAR=$VAR' 2>./VAR.file

$VARможет быть впоследствии установлено сохраненное значение в любом скрипте, в котором допустим следующий путь:

. ./VAR.file

Связанный контент