~ 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
Это баг Баша?
~ 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
Это баг Баша?
нулевой символ очень особенный и 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: POSIX и BSD/OSX find
не допускают неявный путь, а POSIX find
не поддерживает -print0
через Спецификация поиска POSIX упоминает об этом:
В других реализациях были добавлены другие способы обойти эту проблему, в частности, первичный -print0, который записывал имена файлов с нулевым символом конца байта. Здесь это рассматривалось, но не принято. Использование нулевого терминатора означало, что любая утилита, которая собиралась обрабатывать вывод find -print0, должна была добавить новую опцию для анализа нулевых терминаторов, которые она теперь будет считывать.
В спецификации POSIX xargs также отсутствует поддержка -0
(нет ссылки на это тоже), хотя он поддерживается xargs
в GNU, BSD/OSX и busybox.
Поэтому вы, вероятно, можете сделать это:
$ find . -type f -print0 |xargs -0
./c ./b ./a ./two
lines ./tabbed words ./two words
Однако вам может понадобиться массив, поэтому, возможно, я слишком подхожу к вашему упрощенному вопросу.
Вы можете использовать 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
просто демонстрирует содержимое массива. Угловые скобки обозначают начало и конец каждого элемента, поэтому вас не смущает новая строка, пробел или табуляция.
В вашей логике в обеих попытках выше много неправильных представлений. В оболочке bash
вы просто не можете сохранить значение NULL байта \x00
в переменной, будь то специальная IFS
или любая другая определяемая пользователем переменная. Таким образом, ваше требование разделить результат find
на байт NULL никогда не сработает. Из-за этого ваши результаты из find
сохраняются в массиве по первому индексу как одна длинная запись, объединенная с нулевым байтом.
Вы можете обойти проблему использования байта 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[@]}"
Если ваш xargs
поддерживает -0
и вы просто хотите выполнить итерацию по списку строк с нулевым разделителем в цикле, совместимом с Bourne (sh, dash, bash, zsh, busybox shell, …), вы можете сделать:
find . -type f -print0|xargs -0 sh -c 'while test $# -gt 0;do echo "$1";shift;done' sh
или используя цикл for:
find . -type f -print0|xargs -0 sh -c 'for i;do echo "$i";done' sh
read -a
или-A
будет определять массив, а не переменную-заполнитель. Вам нужно сделатьecho "${vars[@]}"
- person Inian   schedule 06.03.2019find -print0
в массив). - person Benjamin W.   schedule 14.03.2019