Чому $ '\ 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.