Есть ли способ сериализации переменной оболочки? Предположим, у меня есть переменная $VAR
, и я хочу иметь возможность сохранить ее в файл или что-то еще, а затем прочитать позже, чтобы вернуть то же значение?
Есть ли портативный способ сделать это? (Я так не думаю)
Есть ли способ сделать это в bash или zsh?
Ответы:
Предупреждение. С любым из этих решений вы должны знать, что вы доверяете целостности файлов данных, поскольку они будут выполняться как код оболочки в вашем скрипте. Обеспечение их имеет первостепенное значение для безопасности вашего сценария!
Простая встроенная реализация для сериализации одной или нескольких переменных
Да, и в bash, и в zsh вы можете сериализовать содержимое переменной таким способом, который легко получить с помощью
typeset
встроенного-p
аргумента и аргумента. Выходной формат таков, что вы можете простоsource
вывести свои данные обратно.Вы можете получить свои вещи обратно, как это позже или в вашем сценарии или в другом сценарии в целом:
Это будет работать для bash, zsh и ksh, включая передачу данных между различными оболочками. Bash преобразует это в свою встроенную
declare
функцию, в то время как zsh реализует это с помощью,typeset
но поскольку bash имеет псевдоним, чтобы это работало в любом случае, поскольку мы используем егоtypeset
здесь для совместимости с ksh.Более сложная обобщенная реализация с использованием функций
Вышеприведенная реализация действительно проста, но если вы будете часто ее вызывать, вы можете захотеть воспользоваться утилитой для ее упрощения. Кроме того, если вы когда-нибудь попробуете включить вышеупомянутое в пользовательские функции, у вас возникнут проблемы с областью видимости переменной. Эта версия должна устранить эти проблемы.
Обратите внимание на все это, чтобы поддерживать перекрестную совместимость bash / zsh, мы будем исправлять оба случая,
typeset
иdeclare
поэтому код должен работать в одной или обеих оболочках. Это добавляет некоторую массу и беспорядок, которые можно было бы устранить, если бы вы делали это только для одной оболочки или другой.Основная проблема с использованием функций для этого (или включения кода в другие функции) состоит в том, что
typeset
функция генерирует код, который при получении обратно в сценарий из функции по умолчанию создает локальную переменную, а не глобальную.Это можно исправить одним из нескольких хаков. Моя первоначальная попытка исправить это заключалась в том, чтобы проанализировать выходные данные процесса сериализации,
sed
чтобы добавить-g
флаг, чтобы созданный код определял глобальную переменную при получении обратно.Обратите внимание, что
sed
выражение funky должно соответствовать только первому вхождению 'typeset' или 'Declare' и добавляться-g
в качестве первого аргумента. Необходимо соответствовать только первому вхождению, потому что, как справедливо указал Стефан Шазелас в комментариях, в противном случае он также будет соответствовать случаям, когда в сериализованной строке содержатся буквенные символы новой строки, за которыми следует слово Declare или typeset.В дополнении к исправлению моей первоначальному разбора бестактности , Stéphane также предложил менее хрупкий способ взломать это , что не только подножку вопросов с разбором строк , но может быть полезным крючком , чтобы добавить дополнительную функциональность, используя функцию - обертки , чтобы пересмотреть действия при получении данных обратно. Предполагается, что вы не играете ни в какие другие игры с командами объявления или набора текста, но этот метод будет проще реализовать в ситуации, когда вы включаете эту функцию как часть другой собственной функции или Вы не контролировали записываемые данные и
-g
добавляли ли они флаг. Нечто подобное можно сделать и с псевдонимами, см . Ответ Жиля для реализации.Чтобы сделать результат еще более полезным, мы можем перебрать несколько переменных, переданных нашим функциям, предполагая, что каждое слово в массиве аргументов является именем переменной. Результат становится примерно таким:
При любом решении использование будет выглядеть так:
источник
declare
являетсяbash
эквивалентомksh
'stypeset
.bash
,zsh
Также поддерживаютtypeset
так и в связи с этим ,typeset
более компактен.export -p
это POSIX, но он не принимает никаких аргументов, и его вывод зависит от оболочки (хотя он хорошо указан для оболочек POSIX, например, когда bash или ksh называется assh
). Не забудьте процитировать ваши переменные; использование оператора split + glob здесь не имеет смысла.-E
встречается только в некоторых BSDsed
. Значения переменных могут содержать символы новой строки, поэтому работаsed 's/^.../.../'
не гарантируется.a=$'foo\ndeclare bar' bash -c 'declare -p a'
для установки выведет строку, которая начинается сdeclare
. Вероятно, это лучше сделатьdeclare() { builtin declare -g "$@"; }
перед звонкомsource
(и отключить после этого)shopt -s expandalias
когда не интерактивно. С помощью функций вы также можете улучшитьdeclare
оболочку, чтобы она только восстанавливала указанные вами переменные.Используйте перенаправление, подстановку команд и расширение параметров. Двойные кавычки необходимы для сохранения пробелов и специальных символов. Трейлинг
x
сохраняет завершающие символы новой строки, которые в противном случае были бы удалены в подстановке команд.источник
Сериализировать все - POSIX
В любой оболочке POSIX вы можете сериализовать все переменные окружения с помощью
export -p
. Это не включает неэкспортированные переменные оболочки. Вывод правильно указан в кавычках, так что вы можете прочитать его обратно в той же оболочке и получить точно такие же значения переменных. Вывод может быть недоступен для чтения в другой оболочке, например, ksh использует$'…'
синтаксис не POSIX .Сериализация некоторых или всех - ksh, bash, zsh
Ksh (как pdksh / mksh, так и ATT ksh), bash и zsh обеспечивают лучшее средство с помощью
typeset
встроенной функции.typeset -p
печатает все определенные переменные и их значения (zsh опускает значения переменных, которые были скрытыtypeset -H
). Выходные данные содержат правильное объявление, так что переменные среды экспортируются при обратном чтении (но если переменная уже экспортирована при обратном чтении, она не будет экспортироваться), так что массивы считываются как массивы и т. Д. Здесь также вывод правильно указан, но гарантированно будет читаемым только в той же оболочке. Вы можете передать набор переменных для сериализации в командной строке; если вы не передадите какую-либо переменную, то все будут сериализованы.В bash и zsh восстановление не может быть сделано из функции, потому что
typeset
операторы внутри функции ограничены этой функцией. Вам нужно работать. ./some_vars
в контексте, где вы хотите использовать значения переменных, следя за тем, чтобы переменные, которые были глобальными при экспорте, были объявлены как глобальные. Если вы хотите прочитать значения внутри функции и экспортировать их, вы можете объявить временный псевдоним или функцию. В зш:В Bash (который использует,
declare
а неtypeset
):В ksh
typeset
объявляет локальные переменные в функциях, определенных с,function function_name { … }
и глобальные переменные в функциях, определенных сfunction_name () { … }
.Сериализация некоторых - POSIX
Если вы хотите больше контроля, вы можете экспортировать содержимое переменной вручную. Чтобы напечатать содержимое переменной точно в файл, используйте
printf
встроеннуюecho
функцию ( имеет несколько особых случаев, например,echo -n
для некоторых оболочек, и добавляет новую строку):Вы можете прочитать это обратно
$(cat VAR.content)
, за исключением того, что подстановка команд удаляет завершающие символы новой строки. Чтобы избежать этой складки, сделайте так, чтобы вывод никогда не заканчивался символом новой строки.Если вы хотите напечатать несколько переменных, вы можете заключить их в одинарные кавычки и заменить все встроенные одинарные кавычки
'\''
. Эту форму цитирования можно прочитать обратно в любую оболочку в стиле Bourne / POSIX. Следующий фрагмент работает в любой оболочке POSIX. Он работает только для строковых переменных (и числовых переменных в оболочках, в которых они есть, хотя они будут считываться как строки), он не пытается работать с переменными массива в оболочках, в которых они есть.Вот еще один подход, который не разветвляется на подпроцесс, но тяжелее при работе со строками.
Обратите внимание, что в оболочках, которые допускают переменные только для чтения, вы получите ошибку, если попытаетесь прочитать переменную, которая доступна только для чтения.
источник
$PWD
и$_
- пожалуйста, смотрите ваши собственные комментарии ниже.typeset
псевдонима дляtypeset -g
?Большое спасибо @ stéphane-chazelas, который указал на все проблемы с моими предыдущими попытками, теперь кажется, что это работает для сериализации массива в stdout или в переменную.
Этот метод не выполняет синтаксический анализ ввода (в отличие от
declare -a
/declare -p
) и поэтому безопасен от злонамеренной вставки метасимволов в сериализованный текст.Примечание. Символы новой строки не экранируются, поскольку
read
удаляют\<newlines>
пару символов, поэтому-d ...
вместо этого они должны быть переданы для чтения, а затем сохраненные символы новой строки сохраняются.Все это управляется в
unserialise
функции.Используются два магических символа: разделитель полей и разделитель записей (чтобы несколько массивов можно было сериализовать в один поток).
Эти символы могут быть определены как
FS
и,RS
но ни один из них не может быть определен какnewline
символ, потому что экранированная новая строка удаленаread
.Экранирующий символ должен быть
\
обратной косой чертой, так как именно он используется,read
чтобы избежать распознаванияIFS
символа как символа.serialise
будет сериализовать"$@"
в стандартный вывод,serialise_to
сериализировать в переменную, названную в$1
и десериализация с:
или
например
(без завершающей строки)
прочитайте это назад:
или
Bash
read
уважает управляющий символ\
(если вы не передаете флаг -r), чтобы удалить специальное значение символов, такое как разделение поля ввода или разделение строк.Если вы хотите сериализовать массив вместо простого списка аргументов, просто передайте ваш массив в качестве списка аргументов:
Вы можете использовать
unserialise
в цикле, как если быread
это было, потому что это просто обернутое чтение, но помните, что поток не разделен символом новой строки:источник
bash
иzsh
отображают их как$'\xxx'
. Попробуйте сbash -c $'printf "%q\n" "\t"'
илиbash -c $'printf "%q\n" "\u0378"'
$IFS
является ли оно неизменным, и теперь не может правильно восстановить пустые элементы массива. На самом деле, было бы более разумно использовать другое значение IFS и использовать его,-d ''
чтобы избежать перехода на новую строку. Например, используйте:
в качестве разделителя полей и экранируйте его только от обратной косой черты и используйтеIFS=: read -ad '' array
для импорта.read
. backslash-newline дляread
- это способ продолжить логическую строку на другую физическую строку. Редактировать: ах, я вижу, вы уже упоминали проблему с переводом строки.Вы можете использовать
base64
:источник
Еще один способ сделать это, чтобы убедиться, что вы обрабатываете все
'
жесткие цитаты, как это:Или с
export
:Первый и второй параметры работают в любой оболочке POSIX, при условии, что значение переменной не содержит строку:
Третий вариант должен работать для любой оболочки POSIX, но может пытаться определить другие переменные, такие как
_
илиPWD
. Правда в том, что единственные переменные, которые он может попытаться определить, устанавливаются и поддерживаются самой оболочкой - и поэтому, даже если вы делаете значение импортаexport
для любого из них -$PWD
например, - оболочка просто сбросит их на правильное значение сразу в любом случае - попробуйте сделатьPWD=any_value
и убедитесь сами.И поскольку - по крайней мере с GNU
bash
- выходные данные отладки автоматически помещаются в безопасные кавычки для повторного ввода в оболочку, это работает независимо от количества'
жестких кавычек в"$VAR"
:$VAR
позже можно установить сохраненное значение в любом сценарии, в котором следующий путь действителен для:источник
$$
это PID запущенной оболочки, вы получили неправильное цитирование и что-\$
то в этом роде? Можно использовать базовый подход к использованию документа здесь, но это хитрый, а не однострочный материал: что бы вы ни выбрали в качестве конечного маркера, вы должны выбрать что-то, что не появляется в строке.$VAR
содержит%
. Третья команда не всегда работает со значениями, содержащими несколько строк (даже после добавления явно пропущенных двойных кавычек).env
. Мне все еще любопытно, что вы имеете в виду по поводу нескольких строк -sed
удаляет каждую строку до встречиVAR=
до последней - так что все строки$VAR
передаются дальше. Можете ли вы привести пример, который ломает его?VAR
) не изменяетсяPWD
или_
или , возможно , другие , что некоторые оболочки определяют. Второй метод требует bash; выходной формат-v
не стандартизирован (ни один из dash, ksh93, mksh и zsh не работает).Почти такой же, но немного другой:
Из вашего скрипта:
Это время выше проверено.
источник
'
,*
и т.д.echo "$LVALUE=\"$RVALUE\""
Предполагается также сохранить символы новой строки, и результат в файле cfg_file должен быть таким: MY_VAR1 = "Line1 \ nLine 2". Таким образом, когда eval MY_VAR1, он также будет содержать новые строки. Конечно, у вас могут возникнуть проблемы, если ваше сохраненное значение содержит сам"
символ. Но об этом тоже можно позаботиться.