Як передавати файли, знайдені знаходженням, як аргументи?


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)

Відмінно! Я думав про масиви. Але я чомусь нічого не знайшов на цьому 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)нормальна Перенаправлення оболонки <зі спеціального файлу , створеного Підстановкою процесів . Це відрізняється від трубопроводу find blah | while read ... doneтим, що конвеєр запускає whileцикл у підпакеті, тому встановлені в ньому var (и) не зберігаються для наступних команд.
dave_thompson_085

3

Ви можете використовувати IFS = newline (якщо припустимо, що файли не містять 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, якщо знахідка порожня.


Отже, як я розумію, у Баша немає ніякого способу змусити IFSрозколотися null?
Адам Бадура

@AdamBadura: Я впевнений, що ні; bash не дозволяє нульовий байт в будь-якій змінній, включаючи IFS. Зауважте, що read -d ''в методах steeldriver використовується порожній рядок, не містить нульовий байт. (І варіант команд так чи інакше не вар.)
dave_thompson_085

Ви також повинні відключити globbing ( set -o noglob) перед використанням цього оператора split + glob (крім in zsh).
Стефан Шазелас


@AdamBadura Так, в баші, нуль точно такий же, як $'\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). Але ви праві - добре знати свої альтернативи! Тим більше, що я помилково подумав, що це неможливо.
Адам Бадура
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.