Jak przekazać pliki znalezione przez find jako argumenty?

9

Najpierw odetnij banalne, ale nie dające się zastosować odpowiedzi: nie mogę użyć ani sztuczki find+, xargsani jej wariantów (podobnie jak w findprzypadku -exec), ponieważ muszę użyć kilku takich wyrażeń na rozmowę. Wrócę do tego na końcu.


Teraz dla lepszego przykładu zastanówmy się:

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Jak przekazać je jako argumenty program?

Samo robienie tego nie załatwia sprawy

$ ./program $(find -L some/dir -name \*.abc | sort)

kończy się niepowodzeniem, ponieważ programotrzymuje następujące argumenty:

[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc

Jak widać, ścieżka z przestrzenią została podzielona i programuważa, że ​​to dwa różne argumenty.

Cytuj, aż to zadziała

Wydaje się, że początkujący użytkownicy tacy jak ja, w obliczu takich problemów, mają tendencję do losowego dodawania cudzysłowów, aż w końcu to działa - tylko tutaj nie wydaje się, aby to pomogło…

"$(…)"

$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Ponieważ cudzysłowy uniemożliwiają dzielenie słów, wszystkie pliki są przekazywane jako pojedynczy argument.

Cytując poszczególne ścieżki

Obiecujące podejście:

$ ./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"

Oczywiście są tam cytaty. Ale nie są już interpretowane. Są tylko częścią strun. Więc nie tylko nie zapobiegli podziałowi słów, ale także wdali się w kłótnie!

Zmień IFS

Potem próbowałem się bawić IFS. Wolałbym findsię -print0i sortze -zi tak - tak, że będą mieli żadnych problemów na „wired” samych ścieżkach. Dlaczego więc nie wymusić podziału słów na nullpostać i mieć to wszystko?

$ ./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

Więc nadal dzieli się na przestrzeń i nie dzieli na null.

Próbowałem umieścić IFSzadanie zarówno w $(…)(jak pokazano powyżej), jak i wcześniej ./program. Próbowałem również inne składni jak \0, \x0, \x00zarówno cytowana 'i "jak również i bez $. Żadne z nich nie miało znaczenia…


I tutaj brakuje mi pomysłów. Próbowałem jeszcze kilku rzeczy, ale wszystkie wydawały się sprowadzać do tych samych problemów, co wymienione.

Co jeszcze mogłem zrobić? Czy to w ogóle wykonalne?

Jasne, mógłbym programzaakceptować wzorce i sam przeszukać. Ale jest to dużo podwójnej pracy przy ustalaniu określonej składni. (A co grepna przykład z udostępnianiem plików ?).

Mógłbym również programzaakceptować plik z listą ścieżek. Następnie mogę łatwo zrzucić findwyrażenie do pliku tymczasowego i podać ścieżkę tylko do tego pliku. Może to być obsługiwane wzdłuż bezpośrednich ścieżek, dzięki czemu jeśli użytkownik ma tylko prostą ścieżkę, można ją podać bez pośredniego pliku. Ale to nie wydaje się miłe - trzeba stworzyć dodatkowe pliki i się nimi zająć, nie wspominając o dodatkowej implementacji. (Na plus można jednak ratować przypadki, w których liczba plików jako argumentów zaczyna powodować problemy z długością wiersza poleceń…)


Na koniec jeszcze raz przypomnę, że sztuczki find+ xargs(i podobne) nie będą działać w moim przypadku. Dla uproszczenia opisu pokazuję tylko jeden argument. Ale mój prawdziwy przypadek wygląda mniej więcej tak:

$ 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

Więc wykonanie xargsjednego wyszukiwania wciąż pozostawia mi sposób radzenia sobie z drugim…

Adam Badura
źródło

Odpowiedzi:

13

Użyj tablic.

Jeśli nie musisz obsługiwać możliwości nowej linii w nazwach plików, możesz uciec

mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)

następnie

./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"

Jeśli zrobić potrzebę nowej linii klamek wewnątrz nazwach i mają bash> = 4.4, można użyć -print0i -d ''null-wypowiedzieć nazwiska w trakcie budowy tablicy:

mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)

(i podobnie w przypadku XYZ_FILES). Jeśli nie masz nowszej wersji bash, możesz użyć zakończonej zerem pętli odczytu, aby dołączyć nazwy plików do tablic, np.

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
steeldriver
źródło
Świetny! Myślałem o tablicach. Ale jakoś nie znalazłem nic na tym mapfile(ani jego synonimie readarray). Ale to działa!
Adam Badura
Ale możesz to trochę poprawić. Wersja Bash <4.4 (którą akurat mam ...) z whilepętlą nie czyści tablicy. Co oznacza, że ​​jeśli nie zostaną znalezione żadne pliki, tablica jest niezdefiniowana. Chociaż jeśli jest już zdefiniowany, nowe pliki zostaną dodane (zamiast zastępować stare). Wygląda na to, że dodawanie declare -a ABC_FILES='()';wcześniej whiledziała. (Podczas gdy tylko dodawanie ABC_FILES='()';tego nie robi.)
Adam Badura
Co również < <oznacza tutaj? Czy to to samo co <<? Nie wydaje mi się, żeby zmiana na <<zwróciła błąd składniowy („nieoczekiwany token” („”). Więc co to jest i jak działa?
Adam Badura,
Kolejnym ulepszeniem (zgodnie z moim szczególnym zastosowaniem) jest zbudowanie kolejnej tablicy. Więc mamy te ABC_FILES. W porządku. Przydatne jest jednak także określenie, ABS_ARGSktóra jest pustą tablicą, jeśli ABC_FILESjest pusta lub inaczej jest tablicą ('--abc-files' "${ABC_FILES[@]}"). W ten sposób później mogę go używać w następujący sposób: ./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"i upewnij się, że będzie działał poprawnie bez względu na to, która grupa (jeśli w ogóle) jest pusta. Lub inaczej: to ten sposób --abc-files(i --xyz-files) będzie zapewniony tylko wtedy, gdy będzie po nim jakaś rzeczywista ścieżka.
Adam Badura
1
@AdamBadura: while read ... done < <(find blah)jest normalnym przekierowaniem powłoki <ze specjalnego pliku utworzonego przez PROCES SUBSTITUTION . Różni się to od pipingu, find blah | while read ... doneponieważ potok uruchamia whilepętlę w podpowłoce, więc ustawione w nim zmienne nie są zachowywane dla kolejnych poleceń.
dave_thompson_085
3

Możesz użyć IFS = nowa linia (zakładając, że żadne nazwy plików nie zawierają nowej linii), ale musisz ustawić ją w zewnętrznej powłoce PRZED podmiany:

$ 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

Z, zshale nie bashmożesz również użyć null $'\0'. Nawet w bashtobie poradzisz sobie z nową linią, jeśli istnieje wystarczająco dziwna postać, która nigdy nie jest używana

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

Jednak to podejście nie obsługuje dodatkowego żądania złożonego w komentarzach do odpowiedzi @ steeldriver, aby pominąć --afiles, jeśli find jest pusty.

dave_thompson_085
źródło
Tak więc, jak rozumiem w Bash, nie ma sposobu, aby zmusić się IFSdo podziału null?
Adam Badura
@AdamBadura: Jestem pewien, że nie; bash nie zezwala na bajt zerowy w żadnej zmiennej, w tym IFS. Zauważ, że read -d ''w metodach steeldriver użyto pustego ciągu, który nie zawiera bajtu zerowego. (A zresztą opcja polecenia nie jest var jako taka.)
dave_thompson_085 24.10.16
Musisz także wyłączyć globbing ( set -o noglob) przed użyciem operatora split + glob (z wyjątkiem in zsh).
Stéphane Chazelas
@AdamBadura Tak, w bash null jest dokładnie taki sam jak $'\0'i również ''.
Izaak
1

Nie jestem pewien, czy rozumiem, dlaczego się poddałeś xargs.

Więc wykonanie xargsjednego wyszukiwania wciąż pozostawia mi sposób radzenia sobie z drugim…

Ciąg --xyz-filesjest tylko jednym z wielu argumentów i nie ma powodu, aby uważać go za specjalny, zanim zostanie zinterpretowany przez twój program. Myślę, że można to przełożyć xargsmiędzy oba findwyniki:

{ 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
Kamil Maciorowski
źródło
Masz rację! To też działa! Zauważ jednak, że przegapiłeś -print0w sekundę find. Także jeśli pójdę w ten sposób, postaram się --abc-filesto echorównież - dla zachowania spójności.
Adam Badura
To podejście wydaje się prostsze i nieco bardziej jednoliniowe niż podejście macierzowe. Wymagałoby to jednak dodatkowej logiki, aby uwzględnić przypadek, że jeśli nie ma .abcplików, to nie powinno też być --abc-files(tak samo z .xyz). Rozwiązanie oparte na tablicy przez steeldriver również wymaga dodatkowej logiki dla niego jednak, że logika nie jest trywialny, gdy może być nie tak trywialne tutaj niszcząc Główną zaletą tego rozwiązania - prostota.
Adam Badura
Nie jestem też do końca pewien, ale zakładam, że xargsnigdy nie spróbuję podzielić argumentów i wykonać kilku poleceń zamiast jednego, chyba że zostanie to wyraźnie polecone, aby użyć argumentów -L, --max-lines( -l), --max-args( -n) lub --max-chars( -s). Czy mam rację? Czy są jakieś domyślne? Ponieważ mój program nie poradziłby sobie z takim podziałem poprawnie i wolałbym nie nazwać go ...
Adam Badura
1
@AdamBadura Missing -print0- naprawiono, dziękuję. Nie znam wszystkich odpowiedzi, ale zgadzam się, że moje rozwiązanie utrudnia włączenie dodatkowej logiki. Prawdopodobnie sam bym poszedł z tablicami, teraz, kiedy znam to podejście. Moja odpowiedź nie była dla ciebie. Przyjąłeś już drugą odpowiedź i założyłem, że twój problem został rozwiązany. Chciałem tylko zaznaczyć, że można przekazywać argumenty z wielu źródeł xargs, co na pierwszy rzut oka nie było oczywiste. Możesz potraktować to jako dowód koncepcji. Teraz wszyscy znamy kilka różnych podejść i możemy świadomie wybrać to, co pasuje do nas w każdym konkretnym przypadku.
Kamil Maciorowski
Tak, już zaimplementowałem rozwiązanie oparte na macierzy i działa jak urok. Jestem szczególnie dumny z tego, jak czysto radzi sobie z opcjonalnością (jeśli nie ma plików, to nie --abc-files). Ale masz rację - dobrze jest znać swoje alternatywy! Zwłaszcza, że ​​błędnie myślałem, że to niemożliwe.
Adam Badura,