Як використовувати нульові байти в Bash?


33

Я читав це, оскільки файлові шляхи в Bash можуть містити будь-який символ, окрім нульового байта (нульовий байт, $'\0'), що найкраще використовувати нульовий байт як роздільник. Наприклад, якщо вихідний findфайл буде надісланий іншій програмі, рекомендується використовувати -print0параметр (для версій find, які його мають).

Але хоч щось подібне працює чудово (друкуючи файлові шляхи, розділені новими рядками - не хвилюйтесь, це лише демонстрація, я насправді не роблю це в реальних сценаріях):

find -print0 \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

щось подібне не працює:

for file in * ; do echo -n "$file"$'\0' ; done \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

Коли я пробую лише forчастину -loop, я виявляю, що вона просто друкує всі назви файлів разом, без нульового байту між ними.

Чому це? Що відбувається?

Відповіді:


43

Bash використовує внутрішньо рядки в стилі C, які закінчуються нульовими байтами. Це означає, що рядок Bash (наприклад, значення змінної або аргумент команди) ніколи насправді не може містити нульовий байт. Наприклад, цей міні-сценарій:

foobar=$'foo\0bar'    # foobar='foo' + null byte + 'bar'
echo "${#foobar}"     # print length of $foobar

насправді друкує 3, бо $foobarнасправді справедливо 'foo': barприходить після закінчення рядка.

Так само echo $'foo\0bar'просто друкує foo, бо echoне знає про \0barдеталь.

Як бачите, \0послідовність насправді дуже вводить в оману в $'...'рядку -style; це виглядає як нульовий байт всередині рядка, але він не закінчується таким чином. У вашому першому прикладі ваша readкоманда має -d $'\0'. Це працює, але тільки тому, що -d ''також працює! (Це не явно задокументована функція read, але я вважаю, що вона працює з тієї ж причини: ''це порожній рядок, тому його завершальний нульовий байт надходить негайно. Документовано як використання "Перший символ delim ", і я здогадуюсь, що навіть працює якщо "перший символ" минулий кінець рядка!)-d delim

Але , як ви знаєте з findприкладу, що це можливо для команди , щоб роздрукувати нульові байти, а для цього байта бути переданий в іншу команду , яка зчитує його в якості вхідних даних. Жодна частина цього не покладається на збереження нульового байта в рядку всередині Bash . Єдина проблема у вашому другому прикладі полягає в тому, що ми не можемо використовувати $'\0'аргумент до команди; echo "$file"$'\0'міг із задоволенням надрукувати нульовий байт наприкінці, якби тільки знав, що ти цього хочеш.

Таким чином, замість використання echoви можете використовувати printf, що підтримує ті ж види послідовностей втечі, що й $'...'рядки -style. Таким чином, ви можете надрукувати нульовий байт, не маючи нульового байта всередині рядка. Це виглядатиме так:

for file in * ; do printf '%s\0' "$file" ; done \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

або просто це:

printf '%s\0' * \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

(Примітка. echoНасправді також є -eпрапор, який дозволить йому обробляти \0та друкувати нульовий байт; але тоді він також намагатиметься обробити будь-які спеціальні послідовності у вашому імені файлу. Отже, printfпідхід є більш надійним.)


Між іншим, є деякі оболонки, які дозволяють нульові байти всередині рядків. Ваш приклад прекрасно працює, наприклад, у Zsh (за умови, що налаштування за замовчуванням). Однак, незалежно від вашої оболонки, Unix-подібні операційні системи не надають способу включити нульові байти всередину аргументів до програм (оскільки програмні аргументи передаються у вигляді рядків у стилі C), тому завжди будуть деякі обмеження. (Ваш приклад може працювати в Zsh лише тому echo, що це вбудована оболонка, тому Zsh може викликати його, не покладаючись на підтримку ОС для виклику інших програм. Якщо ви використовували command echoзамість цього echo, щоб він обійшов вбудовану програму та використав автономну echoпрограму на $PATH, ви побачите таку саму поведінку в Zsh, як і в Bash.)


2
Чому IFS не встановлений нічим, якщо -d ''вже означає обмежувати \0? Я знайшов пояснення тут: stackoverflow.com/questions/8677546 / ...
CMCDragonkai
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.