
Сначала отсечу тривиальные, но неприменимые ответы: я не могу использовать ни трюк find
с + xargs
, ни его варианты (например, find
с -exec
), потому что мне нужно использовать несколько таких выражений за вызов. Я вернусь к этому в конце.
Теперь для лучшего примера давайте рассмотрим:
$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc
Как мне передать их в качестве аргументов program
?
Просто делать это не имеет смысла.
$ ./program $(find -L some/dir -name \*.abc | sort)
терпит неудачу, так как program
получает следующие аргументы:
[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc
Как видно, путь с пробелом разделился и program
рассматривает его как два разных аргумента.
Цитата, пока не заработает
Похоже, начинающие пользователи, такие как я, сталкиваясь с такими проблемами, склонны беспорядочно добавлять кавычки до тех пор, пока это, наконец, не сработает — только здесь это, похоже, не помогает…
"$(…)"
$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc
Поскольку кавычки предотвращают разбиение слов, все файлы передаются как один аргумент.
Цитирование отдельных путей
Многообещающий подход:
$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"
Кавычки есть, конечно. Но они больше не интерпретируются. Они просто часть строк. Так что они не только не предотвратили разделение слов, но и стали причиной споров!
Изменить ИФС
Затем я попробовал поиграться с IFS
. Я бы предпочел find
с -print0
и sort
с -z
в любом случае - так что у них не будет проблем с "проводными путями" самих по себе. Так почему бы не заставить персонажа разделить слова null
и не иметь все это?
$ ./program $(IFS=$'\0' find -L some/dir -name \*.abc -print0 | sort -z)
[0]: ./program
[1]: some/dir/1.abcsome/dir/2.abcsome/dir/a
[2]: space.abc
Таким образом, он по-прежнему разделяется на пространстве и не разделяется на null
.
Я пробовал размещать IFS
назначение как в $(…)
(как показано выше), так и перед ./program
. Также я пробовал другой синтаксис, такой как \0
, \x0
, \x00
как в кавычках с '
и , "
так и с и без $
. Ни один из них, похоже, не имел никакого значения…
И вот у меня закончились идеи. Я попробовал еще несколько вещей, но все, похоже, свелось к тем же проблемам, что перечислены.
Что еще я могу сделать? Это вообще осуществимо?
Конечно, я мог бы заставить program
принимать шаблоны и выполнять поиск самостоятельно. Но это требует много двойной работы при исправлении его на определенный синтаксис. (А как насчет предоставления файлов, например grep
?).
Также я мог бы сделать так, чтобы program
принимался файл со списком путей. Тогда я мог бы легко выгрузить find
выражение в какой-нибудь временный файл и предоставить путь только к этому файлу. Это могло бы поддерживаться по прямым путям, так что если у пользователя есть только простой путь, он может быть предоставлен без промежуточного файла. Но это не кажется хорошим — нужно создавать дополнительные файлы и заботиться о них, не говоря уже о дополнительной требуемой реализации. (Однако с положительной стороны, это может быть спасением в случаях, когда количество файлов в качестве аргументов начинает вызывать проблемы с длиной командной строки…)
В конце позвольте мне еще раз напомнить, что трюки find
с + xargs
(и им подобными) в моем случае не сработают. Для простоты описания я показываю только один аргумент. Но мой истинный случай выглядит скорее так:
$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES
Таким образом, выполнение xargs
одного поиска все еще оставляет меня нерешенным, как поступить с другим...
решение1
Используйте массивы.
Если вам не нужно обрабатывать возможные переносы строк в именах файлов, то вы можете обойтись без
mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)
затем
./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"
Если выделатьнеобходимо обрабатывать символы новой строки в именах файлов, и если у вас bash >= 4.4, вы можете использовать -print0
и -d ''
для завершения имен нулем во время построения массива:
mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)
(и аналогично для XYZ_FILES
). Если вынеесли у вас более новая версия bash, то вы можете использовать цикл чтения с нулевым завершением для добавления имен файлов в массивы, например
ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
решение2
Вы можете использовать IFS=newline (при условии, что имена файлов не содержат символ новой строки), но вы должны установить его во внешней оболочке ПЕРЕД заменой:
$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker
С zsh
но не bash
вы можете использовать null $'\0'
также. Даже в bash
вы могли бы обработать новую строку, если есть один достаточно странный символ, который никогда не используется, как
IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...
Однако этот подход не обрабатывает дополнительный запрос, который вы сделали в комментариях к ответу @steeldriver, чтобы исключить --afiles, если find a пуст.
решение3
Я не совсем понимаю, почему вы отказались от xargs
.
Таким образом, выполнение
xargs
одного поиска все еще оставляет меня нерешенным, как поступить с другим...
Строка --xyz-files
— это всего лишь один из многих аргументов, и нет причин считать ее особенной, пока она не будет интерпретирована вашей программой. Я думаю, вы можете передать ее xargs
между обоими find
результатами:
{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files