Назначьте строку, содержащую нулевой символ (\ 0), переменной в Bash

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

При назначении переменной, содержащей один или несколько 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 (zsh) строки, содержащие \0, сохраняются в обоих случаях, но, к сожалению, я не могу предположить, что zsh присутствует в системах, где выполняется мой сценарий, в то время как Bash должен быть.

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

Ответ 1

В 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

Ответ 2

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

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

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

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

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

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

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

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

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

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

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

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

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

Пример:

## 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 | hd чтобы получить чистое представление ваших данных в шестнадцатеричном формате и убедиться, что вы не потеряли NUL-символы.

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

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

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

РЕДАКТИРОВАТЬ 2: во второй реализации quote была ошибка из-за использования только \0 чем фактически потребляло бы больше нулей, поскольку \0, \00, \000 и \0000 эквивалентны. Таким образом, \0 был заменен на \x00. Спасибо за @MatthijsSteen за то, что обнаружили это.

Ответ 3

Используйте 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 process <(), кроме записи в файл, и они не установлены в Ubuntu 12.04 по умолчанию (sharutils package).

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

Ответ 4

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

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

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