Просто делать это не имеет смысла.

Просто делать это не имеет смысла.

Сначала отсечу тривиальные, но неприменимые ответы: я не могу использовать ни трюк 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

Связанный контент