Сериализируйте переменную оболочки в ударе или zsh

if [ -r "$dir"/sysconfig.out ];

Заметьте пробел между t и ].

12
15.06.2014, 02:19
7 ответов

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

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

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

 # 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"
}

Обратите внимание, что выражение funky sed должно соответствовать только первому вхождению либо 'typeset', либо 'declare' и добавлять -g в качестве первого аргумента. Необходимо сопоставлять только первое вхождение, потому что, как Стефан Шазелас правильно указал в комментариях, в противном случае он также будет соответствовать случаям, когда сериализованная строка содержит буквальные символы новой строки, за которыми следует слово 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"
14
27.01.2020, 19:55

Почти то же самое, но немного отличается:

От вашего скрипта:

#!/usr/bin/ksh 

save_var()
{

    (for ITEM in $*
    do
        LVALUE='${'${ITEM}'}'
        eval RVALUE="$LVALUE"
        echo "$ITEM=\"$RVALUE\""  
    done) >> $cfg_file
}

restore_vars()
{
    . $cfg_file
}

cfg_file=config_file
MY_VAR1="Test value 1"
MY_VAR2="Test 
value 2"

save_var MY_VAR1 MY_VAR2
MY_VAR1=""
MY_VAR2=""

restore_vars 

echo "$MY_VAR1"
echo "$MY_VAR2"

На этот раз проверяется.

-2
27.01.2020, 19:55

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

#!/bin/bash
echo "$var"x > file
unset var
var="$(< file)"
var=${var%x}
3
27.01.2020, 19:55
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
-2
27.01.2020, 19:55

Несколько мыслей о создании сценария:

  • Использование manpath для определения местоположения страниц. Если я добавлю /home/graeme/.cabal/bin в мой PATH , manpath man ) найдут страницы-мужчины в /home/graeme/.cabal/share/man .

  • Используйте самого человека для декомпрессии и форматирования страниц перед поиском, таким образом вы просто ищете сам текст человека, а не какие-либо комментарии и т.д. в необработанном файле. Использование man потенциально может иметь дело с несколькими форматами.

  • Сохранение отформатированных страниц в файле tempfile позволит избежать многократных распаковок и значительно ускорить процесс.

Далее (с помощью bash и GNU find):

#!/bin/bash

set -f; IFS=:
trap 'rm -f "$temp"' EXIT
temp=$(mktemp --tmpdir search_man.XXXXXXXXXX)

while IFS= read -rd '' file; do
  man "$file" >"$temp" 2>/dev/null

  unset fail
  for arg; do
    if ! grep -Fq -- "$arg" "$temp"; then
      fail=true
      break
    fi
  done

  if [ -z "$fail" ]; then
    file=${file##*/}
    printf '%s\n' "${file%.gz}"
  fi
done < <(find $(manpath) -type d ! -name 'man*' -prune -o -type f -print0)
-121--35639-

Try

rsync --protect-args --size-only -avzPe ssh  "/mnt/xlses/split/v2/name with space/ "root@myserver.com:/mnt/xlses/split/v2/name with space"

From man rsync :

-s, --protect-args

Этот параметр отправляет все имена файлов и большинство параметров в удаленный rsync без разрешения удаленной оболочки Это означает, что места не разделяются по именам, а любые специальные символы без подстановочных знаков не переводятся (например, ~, $,;, & и т.д.). Подстановочные символы расширяются на удаленном хосте с помощью rsync (вместо оболочки, выполняющей это действие). [...]

-121--5233-

Сериализация всех - POSIX

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

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

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

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

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

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

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

In bash (который использует declare , а не typeset ):

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

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

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

Если требуется дополнительный контроль, можно экспортировать содержимое переменной вручную. Чтобы напечатать содержимое переменной точно в файл, используйте printf builtin ( 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
}

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

2
27.01.2020, 19:55

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

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

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

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

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

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

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

serialise будет сериализовать "$@" в 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 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's read уважает символ escape \ (если вы не передадите флаг -r) для удаления специального значения символов, например, для разделения полей ввода или разграничения строк.

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

serialise_array "${my_array[@]}"

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

while unserialise array
do ...
done
0
27.01.2020, 19:55

Вы можете использоватьbase64:

$ VAR="1/ 
,x"
$ echo "$VAR" | base64 > f
$ VAR=$(cat f | base64 -d)
$ echo "${VAR}X"
1/ 
,xX
0
27.01.2020, 19:55

Теги

Похожие вопросы