Присвоить строку, содержащую нулевой символ (\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, без потери (мета) символов?
5 ответов
В 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
Как уже говорили другие, вы не можете хранить / использовать NUL char:
- в переменной
- в аргументе командной строки.
Однако вы можете обрабатывать любые двоичные данные (включая NUL-символ):
- в трубах
- в файлах
Итак, чтобы ответить на ваш последний вопрос:
Кто-нибудь может дать мне подсказку, как строки, содержащие символы \0, могут эффективно храниться или обрабатываться без потери (мета) символов?
Вы можете использовать файлы или каналы для эффективного хранения и обработки любой строки с любыми метасимволами.
Если вы планируете обрабатывать данные, вам следует дополнительно отметить, что:
- Только NUL-символ будет съеден переменной и аргументом командной строки, вы можете проверить это.
- Будьте осторожны, что подстановка команд (как
$(command..)
или же`command..`
) имеет дополнительный поворот над тем, чтобы быть переменной, так как она съест ваши новые строки.
Обход ограничений
Если вы хотите использовать переменные, то вы должны избавиться от NUL-символа, кодируя его, и различные другие решения здесь предлагают умные способы сделать это (очевидный способ - использовать, например, кодирование / декодирование base64).
Если вас беспокоит память или скорость, вы, вероятно, захотите использовать минимальный синтаксический анализатор и указывать только символ NUL (и символ цитирования). В этом случае это поможет вам:
quote() { sed 's/\\/\\\\/g;s/\x0/\\0/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
чтобы получить четкое представление ваших данных в шестнадцатеричном формате и убедиться, что вы не потеряли NUL-символов.
Смена инструментов
Помните, что вы можете пойти довольно далеко с каналами без использования переменных или аргументов в командной строке, не забудьте, например, <(command ...)
конструкция, которая создаст именованный канал (вид временного файла).
РЕДАКТИРОВАТЬ: первая реализация quote
был неверным и не справился бы правильно с \
специальные символы, интерпретируемые echo -en
, Спасибо @xhienne за то, что заметил это.
использование 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 или какой-то другой более разумный интерпретируемый язык.
Я люблю ответ Джеффа. Я бы использовал кодирование Base64 вместо xxd. Это экономит немного места и было бы (я думаю) более узнаваемым относительно того, что предназначено.
VAR=$(echo -n "foo\0bar" | base64)
echo -n $VAR | base64 -d | xargs -0 ...
Что касается -e, он не нужен, потому что оболочка уже интерпретирует escape до того, как она доходит до эха. Я также, кажется, вспоминаю что-то о том, что "echo -e" небезопасно, если вы повторяете какие-либо пользовательские данные, поскольку они могут вводить escape-последовательности, которые echo будет интерпретировать и приводить к плохим вещам.
Вот максимально эффективное по памяти решение, которое просто экранирует байты NULL с помощью .
(Поскольку я не был доволен base64 или подобным. :)
esc0() { sed 's/\xFF/\xFF\xFF/g; s/\x00/\xFF0/g'; }
cse0() { sed 's/\xFF0/\xFF\x00/g; s/\xFF\(.\)/\1/g'; }
Он, конечно же, экранирует любой фактический, удваивая его, поэтому он работает точно так же, как когда для экранирования используется обратная косая черта. По этой же причине нельзя использовать простое сопоставление и требуется ссылка на совпадение в замене.
Вот пример, который рисует градиенты в буфере кадра (не работает в X), используя переменные для предварительного рендеринга блоков и линий для ускорения:
width=7680; height=1080; # Set these to your framebuffer’s size.
blocksPerLine=$(( $width / 256 ))
block="$( for i in 0 1 2 3 4 5 6 7 8 9 A B C D E F; do for j in 0 1 2 3 4 5 6 7 8 9 A B C D E F; do echo -ne "\x$i$j"; done; done | esc0 )"
line="$( for ((b=0; b < blocksPerLine; b++)); do echo -en "$block"; done )"
for ((l=0; l <= $height; l++)); do echo -en "$line"; done | cse0 > /dev/fb0
Обратите внимание, как$block
содержит экранированные значения NULL (плюс\xFF
s), и в конце, перед записью всего во фреймбуфер,cse0
не ускользает от них.