Установка IFS в нулевой байт неправильно разделяет строки в командной строке - PullRequest
5 голосов
/ 06 марта 2019
~ ls
A B C

На bash (выглядит неправильно)

~IFS=$'\x00' read -a vars < <(find -type f -print0); echo "${vars}"
ABC

На зш (выглядит хорошо)

~IFS=$'\x00' read -A vars < <(find -type f -print0); echo "${vars}"
A B C

Это ошибка в bash?

Ответы [ 2 ]

4 голосов
/ 14 марта 2019

нулевой символ очень особенный, и POSIX и bash не разрешают его внутри строк (это определение конца строки, поэтому $'\x00' и $'\000' почти никогда не работают; ответ Иниана здесь даже ссылается на обходной путь для ввода нулевого символа , но, опять же, вы не можете ожидать, что он будет должным образом сохранен, когда вы назначить его переменной). Похоже, что zsh не против, но bash.

Вот тест, который иллюстрирует проблемы, представляющие символы пробела, табуляции и новой строки в именах файлов:

$ touch 'two words' tabbed$'\t'words "two
lines"
$ ls            # GNU coreutils ls displays using bash's $'string' notation
'tabbed'$'\t''words'  'two'$'\n''lines'  'two words'
$ ls |cat       # … except when piped elsewhere
tabbed  words
two
lines
two words
$ find *        # GNU findutils find displays tabs & newlines as questions
tabbed?words
two?lines
two words
$ find * |cat   # … except when piped elsewhere
tabbed  words
two
lines
two words
$ touch a b c   # (more tests for later)

Инструменты GNU очень умны и знают, что это проблема, поэтому они придумывают творческие пути, но они даже не последовательны. ls предполагает, что вы используете bash или zsh (синтаксис $'…' для литерала не присутствует в POSIX), а find дает вам знак вопроса (сам по себе допустимый символ имени файла, но это глобус файла, который соответствует любому символу, например, rm two?lines tabbed?words удалит оба файла, как rm 'two'$'\n''lines' 'tabbed'$'\t''words'). Оба представляют правду, когда переданы другой команде, например cat.

GNU / BSD / MacOSX / Busybox find и xargs

Я вижу, что вы используете расширения GNU: POSIX и BSD / OSX find не допускают неявный путь, а POSIX find не поддерживает -print0, хотя POSIX find spec упоминает это:

Другие реализации добавили другие способы обойти эту проблему, в частности, -print0 , который записывал имена файлов с нулевым байтовым терминатором. Это было рассмотрено здесь, но не принято. Использование нулевого терминатора означало, что любая утилита, которая собиралась обработать вывод * -print0 , должна была добавить новую опцию для анализа нулевых терминаторов, которые она теперь будет читать.

Спецификация POSIX xargs также не поддерживает -0 (на нее также нет ссылок), хотя она поддерживается xargs в GNU, BSD / OSX и busybox.

Следовательно, вы, вероятно, можете сделать это:

$ find . -type f -print0 |xargs -0
./c ./b ./a ./two
lines ./tabbed  words ./two words

Однако вам может понадобиться массив, так что, возможно, я перебираю ваш упрощенный вопрос.

1058 * файл проект * Вы можете использовать mapfile в Bash 4.4 и более поздних версиях: $ mapfile -d '' vars < <(find . -type f -print0) $ printf '<%s>\n' "${vars[@]}" <./c> <./b> <./a> <./two lines> <./tabbed words> <./two words> Некоторые команды, включая mapfile, read и readarray (синоним mapfile), принимают -d '', как если бы это было -d $'\0', вероятно [требуется цитата] как обходной путь для вышеупомянутой неспособности оболочки POSIX работать с нулевыми символами в строках. Эта команда mapfile просто считывает входной файл (в данном случае стандартный ввод) в массив $vars, разделенный нулевыми символами. Стандартный ввод заполняется через конвейер с помощью файлового дескриптора, созданного подстановкой процесса <(…) в конце строки, который обрабатывает вывод нашей команды find. Небольшое отступление: вы могли бы подумать, что можете просто сделать find … |mapfile …, но это изменит область, и все переменные, которые вы установили или изменили там, будут потеряны после завершения команды конвейера. Трюк с заменой процесса не подстерегает вас таким же образом. Команда printf просто демонстрирует содержимое массива. Угловые скобки обозначают начало и конец каждого элемента, поэтому вас не смущает перевод строки, пробел или табуляция.

4 голосов
/ 06 марта 2019

В вашей логике есть много неправильных представлений в обеих попытках выше.В bash оболочке вы просто не можете сохранить значение NULL байта \x00 в переменной, будь то специальная IFS или любая другая пользовательская переменная.Таким образом, ваше требование разделить результат find на байт NULL никогда не сработает.По этой причине ваши результаты из find сохраняются в массиве при первом индексе как одна длинная запись, соединенная с байтом NULL.

Вы можете обойти проблему использования байта NULL в переменной с помощьюнесколько трюков, определенных в Как передать \x00 в качестве аргумента программе? .Вы можете использовать любой другой пользовательский символ для вашего IFS просто как

IFS=: read -r -a splitList <<<"foo:bar:dude" 
declare -p splitList

. Идеальным способом чтения файлов с ограничением NULL было бы установить поле разделителя в команде read для чтения до тех пор, покаОбнаружен нулевой байт.

Но тогда, если вы просто выполните

IFS= read -r -d '' -a files < <(find -type f -print0)

, вы прочитаете только первый файл, за которым следует байт NULL, а массив "${files[@]}" будет содержать только одно имя файла.Вы должны читать в цикле, пока не будет прочитан последний NULL-байт и больше нет символов для чтения

declare -a array=()
while IFS= read -r -d '' file; do
    array+=( "$file" )
done < <(find -type f -print0)

, который генерирует результаты, содержащие каждый файл в отдельной записи массива

printf '%s\n' "${array[@]}"
...