Чому $ '\ 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 з порожнім рядком завжди закінчується нулем?


Посилаючись на "наївний" спосіб, чи є кращий спосіб зробити це?
— iruvar

2
До речі, якщо ви хочете зробити безпечні операції ітерації над набором файлів - використовуйте for f in *замість розбору ls.

@htor Я знаю, що for i in $(ls)це жахливо дурно - я майже соромлюсь, що я використав це як поганий приклад тут.
— slhck

@ChandraRavoori Так, наприклад, використовуючи find … -execзамість того, щоб обробляти файли, що працює в більшості випадків, коли ви використовуєте такий цикл. Ось, findдбає про все за вас.
— slhck

@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? З тих днів, коли я навчався С у школі, звичайно, є сенс вважати це; Я тільки перевіряв.
— slhck

Але можна вважати, що будь-який рядок містить довільно багато порожніх рядків, наприклад, якщо ви об'єднали '' і "X", ви отримаєте "X". Таким чином, ви можете стверджувати, що перша базова підрядка баш - порожній рядок. Наприклад, якщо ви використовуєте порожній рядок у JavaScript, split()він розділиться між кожним символом. Я підозрюю, що "з історичних причин" може бути найкращим поясненням, яке ми можемо отримати.
— доношено успішно

Ну, не зовсім тому, що "об'єднання" стилю '\0'С 'X\0'повинно вам дати 'X\0', якщо зробити все правильно. Це не має великого відношення до функцій високого рівня в таких мовах, як JavaScript @don
— slhck

Дякую, міха, за додавання джерела. delim = *list_optarg;дає зрозуміти, чому це саме так.
— slhck

@slhck: Вибачте, я не зрозумів себе. Ви запитували "чому ''і $'\0'те саме?", Michas дав найближче пояснення "ось що робить код". Я окреслив альтернативний спосіб обробки порожнього рядка, який вважав однаково розумним, і висловив думку, що вибір того чи іншого є просто питанням конвенції чи випадковості.
— доношено успішно

6

У баші є два недоліки, які компенсують один одного.

Коли ви пишете $'\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.
— Лрі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.