Присвоить строку, содержащую нулевой символ (\ 0), переменной в Bash - PullRequest
26 голосов
/ 04 июля 2011

При попытке правильно обработать список file- / foldernames ( см. Мои другие вопросы ), используя символ NULL в качестве разделителя, я наткнулся на странное поведение Bash, которое я не использую.t понять:

При назначении переменной, содержащей один или несколько символов NULL, NULL-символы теряются / игнорируются / не сохраняются.

Например,

echo -ne "n\0m\0k" | od -c   # -> 0000000   n  \0   m  \0   k

Но:

VAR1=`echo -ne "n\0m\0k"`
echo -ne "$VAR1" | od -c   # -> 0000000   n   m   k

Это означает, что мне нужно было бы записать эту строку в файл (например, в / tmp) и прочитать ее оттуда, если прямая передача нежелательна или невозможна.

При выполнении этих сценариев в Z shell (zsh) строки, содержащие \ 0, сохраняются в обоих случаях, но, к сожалению, я не могу предположить, что zsh присутствует в системах, работающих под управлением моегосценария, в то время как Bash должен быть.

Как строки, содержащие символы \ 0, могут эффективно храниться или обрабатываться без потери (мета) символов?

Ответы [ 4 ]

31 голосов
/ 04 июля 2011

В Bash нельзя хранить NULL-символ в переменной.

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

VAR1=`echo -ne "n\0m\0k" | xxd -p | tr -d '\n'`
echo -ne "$VAR1" | xxd -r -p | od -c   # -> 0000000    n  \0   m  \0   k
17 голосов
/ 01 июля 2014

Как уже говорили другие, вы не можете хранить / использовать NUL char :

  • в переменной
  • в аргументе командной строки.

Однако, вы можете обрабатывать любые двоичные данные (включая NUL-символ):

  • в трубах
  • в файлах

Итак, чтобы ответить на ваш последний вопрос:

может кто-нибудь подсказать, как могут быть строки, содержащие \ 0 символов хранится или обрабатывается эффективно, не теряя (мета) символов?

Вы можете использовать файлы или каналы для эффективного хранения и обработки любой строки с любыми метасимволами.

Если вы планируете обрабатывать данные, вам следует дополнительно отметить, что:

Обход ограничений

Если вы хотите использовать переменные, то вы должны избавиться от NUL-символа путем его кодирования, а различные другие решения здесь предлагают умные способы сделать это (очевидный способ - использовать, например, кодирование / декодирование base64).

Если вас беспокоит память или скорость, вы, вероятно, захотите использовать минимальный синтаксический анализатор и указывать только символ NUL (и символ цитирования). В этом случае вам помогут:

quote() { sed 's/\\/\\\\/g;s/\x0/\\x00/g'; }

Затем вы можете защитить свои данные, прежде чем сохранять их в переменных и аргумент командной строки, передавая ваши конфиденциальные данные в quote, который выведет безопасный поток данных без символов NUL. Вы можете вернуться исходная строка (с NUL-символами) с помощью echo -en "$var_quoted", которая отправит правильную строку в стандартный вывод.

* +1057 * Пример:
## Our example output generator, with NUL chars
ascii_table() { echo -en "$(echo '\'0{0..3}{0..7}{0..7} | tr -d " ")"; }
## store
myvar_quoted=$(ascii_table | quote)
## use
echo -en "$myvar_quoted"

Примечание: используйте | hd, чтобы получить чистое представление ваших данных в шестнадцатеричном и убедитесь, что вы не потеряли NUL-символы.

Смена инструментов

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

РЕДАКТИРОВАТЬ: первая реализация quote была неправильной и не будет корректно работать с \ специальными символами, интерпретируемыми echo -en. Спасибо @xhienne за то, что заметил это.

EDIT2: во второй реализации quote была ошибка из-за использования только \0, из-за чего на самом деле было бы съедено больше нулей, например \0, \00, \000 и \0000 эквивалентны. Так \0 было заменено \x00 Спасибо за @MatthijsSteen за то, что нашли это.

8 голосов

Используйте uuencode и uudecode для переносимости POSIX

xxd и base64 - это не POSIX 7 , а uuencode .

VAR="$(uuencode -m <(printf "a\0\n") /dev/stdout)"
uudecode -o /dev/stdout <(printf "$VAR") | od -tx1

Выход:

0000000 61 00 0a
0000003

К сожалению, я не вижу альтернативы POSIX 7 для процесса Bash <() расширение замещения, кроме записи в файл, и они не установлены в Ubuntu 12.04 по умолчанию (пакет sharutils).

Так что я думаю, что реальный ответ таков: не используйте Bash для этого, используйте Python или какой-то другой более разумный интерпретируемый язык.

3 голосов
/ 19 июля 2011

Я люблю ответ Джеффа . Я бы использовал кодирование Base64 вместо xxd. Это экономит немного места и было бы (я думаю) более узнаваемым относительно того, что предназначено.

VAR=$(echo -ne "foo\0bar" | base64)
echo -n "$VAR" | base64 -d | xargs -0 ...

Что касается -e, он необходим для эха буквальной строки с закодированным нулем ('\ 0'), хотя я также, кажется, вспоминаю что-то о том, что "echo -e" небезопасен, если вы повторяете какую-либо пользовательский ввод, поскольку он может вводить escape-последовательности, которые эхо будет интерпретировать и заканчиваться плохими вещами. Флаг -e не требуется при выводе закодированной сохраненной строки в декодер.

...