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