Почему $ '\ 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 с пустой строкой всегда заканчиваются нулем?


Ссылаясь на «наивный» способ, есть ли лучший способ сделать это?
Ирувар

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.