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
последовательность на самом деле очень вводит в заблуждение в $'...'
строке -стиля; он выглядит как нулевой байт внутри строки, но в итоге он не работает таким образом. В вашем первом примере ваша read
команда имеет -d $'\0'
. Это работает, но только потому, что -d ''
тоже работает! (Это явно не задокументированная особенность read
, но я предполагаю, что она работает по той же причине: ''
это пустая строка, поэтому ее завершающий нулевой байт приходит сразу. Задокументировано как использование «первого символа разделителя », и я предполагаю, что даже работает если «первый символ» находится за концом строки!)-d delim
Но , как вы знаете из find
примера, что это возможно для команды , чтобы распечатать нулевые байты, а для этого байта быть передан в другую команду , которая считывает его в качестве входных данных. Никакая часть этого не полагается на сохранение нулевого байта в строке внутри Bash . Единственная проблема с вашим вторым примером заключается в том, что мы не можем использовать $'\0'
аргумент команды; echo "$file"$'\0'
мог бы счастливо напечатать нулевой байт в конце, если бы знал, что вы этого хотите.
Таким образом, вместо использования echo
вы можете использовать printf
, который поддерживает те же виды escape-последовательностей, что и $'...'
строки -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.)
-d ''
уже означает разделение на\0
? Я нашел объяснение здесь: stackoverflow.com/questions/8677546/…