Как передать найденные файлы как аргументы?

9

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

Затем я попытался поиграть с 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одного поиска все еще оставляет меня с тем, как иметь дело с другим ...

Адам Бадура
источник

Ответы:

13

Используйте массивы.

Если вам не нужно обрабатывать возможность перехода на новую строку в ваших именах файлов, то вы можете сойти с рук

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[@]}"

Если делать нужно обрабатывать переводы строк в именах файлов, и имеет Баш> = 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)
steeldriver
источник
Отлично! Я думал о массивах. Но почему-то я ничего не нашел по этому поводу mapfile(или его синониму readarray). Но это работает!
Адам Бадура
Тем не менее, вы могли бы немного улучшить его. Версия Bash <4.4 (которая у меня есть ...) с whileциклом не очищает массив. Это означает, что если файлы не найдены, массив не определен. Хотя, если он уже определен, будут добавляться новые файлы (вместо замены старых). Кажется, что добавление declare -a ABC_FILES='()';раньше whileделает свое дело. (Пока просто добавлять ABC_FILES='()';не надо.)
Адам Бадура
И что это < <значит здесь? Это так же, как <<? Я не думаю, что изменение этого слова <<приводит к синтаксической ошибке («неожиданный токен» («»). Так что же это такое и как это работает?
Адам Бадура
Еще одно улучшение (наряду с моим конкретным использованием) заключается в создании еще одного массива. Итак, у нас есть те ABC_FILES. Это хорошо. Но полезно также сделать ABS_ARGSпустой массив, если ABC_FILESон пустой или это массив ('--abc-files' "${ABC_FILES[@]}"). Таким образом, позже я могу использовать его следующим образом: ./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"и быть уверенным, что он будет работать правильно, независимо от того, какая (если есть) группа пуста. Или сформулировать это по-другому: этот способ --abc-files--xyz-files) будет предоставлен, только если за ним следует какой-то фактический путь.
Адам Бадура
1
@AdamBadura: while read ... done < <(find blah)это обычное перенаправление оболочки <из специального файла, созданного PROCESS SUBSTITUTION . Это отличается от конвейера, find blah | while read ... doneпоскольку конвейер запускает whileцикл в подоболочке, поэтому установленные в нем переменные не сохраняются для последующих команд.
dave_thompson_085
3

Вы можете использовать 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вы можете использовать ноль, $'\0'а также. Даже внутри bashвы можете обрабатывать перевод строки, если есть один достаточно странный символ, который никогда не используется как

 IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...

Однако этот подход не обрабатывает дополнительный запрос, который вы сделали в комментариях к ответу @ steeldriver, чтобы опустить --afiles, если find пуст.

dave_thompson_085
источник
Так что, как я понимаю, в Bash нет способа заставить IFSразделиться на null?
Адам Бадура
@AdamBadura: я уверен, что нет; bash не допускает нулевой байт в любой переменной, включая IFS. Обратите внимание, что read -d ''в методах steeldriver используется пустая строка, а не строка с нулевым байтом. (И параметр команды в любом случае не является
переменной
Вы также должны отключить globbing ( set -o noglob) перед использованием этого оператора split + glob (кроме как в zsh).
Стефан Шазелас
@AdamBadura Да, в bash нуль - это то же самое, что $'\0'и ''.
Исаак
1

Я не уверен, что понимаю, почему ты отказался 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
Камиль Мачоровски
источник
Вы правы! Это работает также! Однако обратите внимание, что вы пропустили -print0в секунду find. Кроме того, если идти по этому пути , я бы поставил --abc-filesкак echoи - только для последовательности.
Адам Бадура
Этот подход кажется более простым и несколько более однострочным, чем массивный подход. Однако потребуется некоторая дополнительная логика, чтобы охватить случай, что если .abcфайлов нет, то их тоже не должно быть --abc-files(то же самое с .xyz). Решение на основе массива от Steeldriver также требует дополнительной логики, но эта логика здесь тривиальна, в то время как здесь может быть не так уж тривиально разрушать главное преимущество этого решения - простоту.
Адам Бадура
Также я не совсем уверен, но я предполагаю, что xargsникогда не попытаюсь разделить аргументы и сделать несколько команд вместо одной, если только это явно не указано для аргументов -L, --max-lines( -l), --max-args( -n) или --max-chars( -s). Я прав? Или есть какие-то значения по умолчанию? Поскольку моя программа не справилась бы с таким разделением правильно, и я предпочел бы не назвать его ...
Адам Бадура
1
@AdamBadura Отсутствует -print0- исправлено, спасибо. Я не знаю ответы на все вопросы, но я согласен, что мое решение затрудняет включение дополнительной логики. Я бы, наверное, сам пошел с массивами, теперь, когда я знаю этот подход. Мой ответ был не совсем для тебя. Вы уже приняли другой ответ, и я предположил, что ваша проблема решена. Я просто хотел отметить, что вы можете передавать аргументы из нескольких источников xargs, что на первый взгляд было неочевидным. Вы можете рассматривать это как доказательство концепции. Теперь мы все знаем несколько разных подходов и можем сознательно выбирать то, что подходит нам в каждом конкретном случае.
Камиль Мачоровски
Да, я уже реализовал решение на основе массива, и оно работает как шарм. Я особенно горжусь тем, насколько чисто он имеет дело с дополнительностью (если нет файлов, то нет --abc-files). Но вы правы - хорошо знать ваши альтернативы! Тем более, что я ошибочно подумал, что это невозможно.
Адам Бадура