Почему $ '\ 0' совпадает с ''?

10

Распространенный способ сделать что-то с парой файлов - и не бейте меня за это:

for f in $(ls); do 

Теперь, чтобы обезопасить себя от файлов с пробелами или другими странными символами, наивным способом было бы сделать:

find . -type f -print0 | while IFS= read -r -d '' file; 

Здесь -d ''кратко для установки ASCII NUL, как в -d $'\0'.

Но почему это так? Почему ''и так $'\0'же? Это из-за того, что C-корни Bash с пустой строкой всегда заканчиваются нулем?

slhck
источник
Ссылаясь на «наивный» способ, есть ли лучший способ сделать это?
Ирувар
2
Кстати, если вы хотите выполнять безопасные операции, повторяя набор файлов - используйте for f in *вместо разбора ls.
@htor, я знаю, for i in $(ls)это ужасно глупо - мне почти стыдно, что я использовал это как плохой пример.
slhck
@ChandraRavoori Да, например, используя find … -execвместо циклического обхода файлов, что работает в большинстве случаев, когда вместо этого вы используете такой цикл for. Здесь findпозаботится обо всем для вас.
Slhck
@ спасибо, спасибо. А как насчет ситуаций, связанных с многошаговыми операциями над каждым файлом, когда цикл может быть предпочтительным для удобства чтения? Есть ли лучший вариант петли, чем «наивный путь» выше?
iruvar

Ответы:

10

man page of bashГласит:

          -d delim
                 The first character of delim is  used  to  terminate  the
                 input line, rather than newline.

Поскольку строки обычно заканчиваются нулем, первым символом пустой строки является нулевой байт. - Имеет смысл для меня. :)

Источник гласит:

static unsigned char delim;
[...]
    case 'd':
      delim = *list_optarg;
      break;

Для пустой строки delimэто просто нулевой байт.

Михась
источник
Когда вы говорите «строки обычно заканчиваются нулем», разве это не так в среде POSIX? С тех дней, когда я изучал C для школы, конечно, есть смысл предполагать это; Я просто проверял.
Slhck
Но можно рассматривать любую строку как содержащую произвольно много пустых строк, например, если вы объединяете '' и "X", вы получаете "X". Таким образом, вы можете утверждать, что первая подстрока bash встречается с пустой строкой. Например, если вы используете пустую строку в JavaScript, split()она будет разделена между каждым символом. Я подозреваю, что «по историческим причинам» может быть лучшим объяснением, которое мы можем получить.
успешно с
Ну, не совсем , потому что «конкатенации» С-стиль '\0'с 'X\0'должен дать вам 'X\0', если все сделано правильно. Это не имеет ничего общего с функциями высокого уровня в таких языках, как JavaScript @don
slhck
Спасибо, Михас, за добавление источника. delim = *list_optarg;дает понять, почему это так.
Slhck
@slhck: Извините, я не прояснил себя. Вы спросили «почему ''и $'\0'то же самое?», Михас дал приблизительное объяснение «это то, что делает код». Я обрисовал альтернативный способ обработки пустой строки, который я считаю столь же разумным, и предположил, что выбор одного или другого был просто условностью или случайностью.
успешно завершено
6

В bash есть два недостатка, которые компенсируют друг друга.

Когда вы пишете $'\0', это внутренне обрабатывается идентично пустой строке. Например:

$ a=$'\0'; echo ${#a}
0

Это связано с тем, что внутренне bash хранит все строки как строки C , которые заканчиваются нулем - нулевой байт отмечает конец строки. Bash молча обрезает строку до первого нулевого байта (который не является частью строки!).

# a=$'foo\0bar'; echo "$a"; echo ${#a}
foo
3

Когда вы передаете строку в качестве аргумента -dопции readвстроенной функции, bash просматривает только первый байт строки. Но на самом деле он не проверяет, что строка не пуста. Внутренне, пустая строка представлена ​​как байтовый массив из 1 элемента, который содержит только нулевой байт. Поэтому вместо чтения первого байта строки bash читает этот нулевой байт.

Затем, внутренне, механизм позади readвстроенного работает хорошо с нулевыми байтами; он продолжает читать побайтово, пока не найдет разделитель.

Другие оболочки ведут себя по-другому. Например, ash и ksh игнорируют нулевые байты при чтении ввода. С ksh ksh -d ""читает до новой строки. Оболочки предназначены для того, чтобы хорошо справляться с текстом, а не с двоичными данными. Zsh является исключением: он использует строковое представление, которое справляется с произвольными байтами, включая нулевые байты; в zsh $'\0'- строка длиной 1 (но read -d '', как ни странно, ведет себя как read -d $'\0').

Жиль "ТАК - перестань быть злым"
источник
Поведение readизменилось в bash 4.3, так что теперь оно пропускает нулевые байты. Например , read x< <(printf a\\0a)наборы , xчтобы aaвместо a.
Lri