Come passare i file trovati da find come argomenti?


9

Prima di tagliare le risposte banali ma inapplicabili: non posso usare né il trucco find+ xargsné le sue varianti (come findcon -exec) perché ho bisogno di usare poche espressioni del genere per chiamata. Tornerò su questo alla fine.


Ora per un esempio migliore consideriamo:

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

Come posso passare quelli come argomenti a program?

Solo farlo non fa il trucco

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

fallisce poiché programottiene i seguenti argomenti:

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

Come si può vedere, il percorso con lo spazio è stato diviso e lo programconsidera come due argomenti diversi.

Cita fino a quando non funziona

Sembra che gli utenti alle prime armi come me, di fronte a tali problemi, tendano ad aggiungere casualmente le virgolette fino a quando finalmente non funziona - solo qui non sembra aiutare ...

"$(…)"

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

Poiché le virgolette impediscono la divisione delle parole, tutti i file vengono passati come un singolo argomento.

Citando percorsi individuali

Un approccio promettente:

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

Le virgolette ci sono, certo. Ma non vengono più interpretati. Sono solo una parte delle stringhe. Quindi non solo non hanno impedito la divisione delle parole, ma hanno anche litigato!

Cambia IFS

Poi ho provato a giocare con IFS. Preferirei findcon -print0e sortcon -zogni modo - in modo che non abbiano problemi sui "percorsi cablati" stessi. Quindi perché non forzare la divisione delle parole sul nullpersonaggio e avere tutto?

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

Quindi si divide ancora nello spazio e non si divide su null.

Ho provato a posizionare il IFScompito sia in $(…)(come mostrato sopra) che prima ./program. Inoltre ho provato altre sintassi del tipo \0, \x0, \x00sia citato con 'e "così come con e senza $. Nessuno di quelli sembrava fare la differenza ...


E qui sono senza idee. Ho provato qualche altra cosa, ma tutto sembrava ridursi agli stessi problemi elencati.

Cos'altro potrei fare? È fattibile affatto?

Certo, potrei far programaccettare gli schemi e fare ricerche da solo. Ma è un sacco di doppio lavoro mentre lo fissa su una sintassi specifica. (Che ne dite di fornire file grepper esempio?).

Inoltre potrei fare programaccettare un file con un elenco di percorsi. Quindi posso facilmente scaricare l' findespressione in qualche file temporaneo e fornire solo il percorso a quel file. Questo potrebbe essere supportato lungo percorsi diretti in modo che se l'utente ha solo un percorso semplice può essere fornito senza file intermedio. Ma questo non sembra carino - bisogna creare file extra e prendersene cura, per non parlare dell'implementazione extra richiesta. (Sul lato positivo, tuttavia, potrebbe essere un salvataggio per i casi in cui il numero di file come argomenti inizia a causare problemi con la lunghezza della riga di comando ...)


Alla fine, lascia che ti ricordi ancora che i trucchi find+ xargs(e simili) non funzioneranno nel mio caso. Per semplicità di descrizione sto mostrando solo un argomento. Ma il mio vero caso è più simile a questo:

$ 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

Quindi fare xargsuna ricerca da una volta mi lascia ancora come affrontare l'altra ...

Risposte:


13

Usa array.

Se non è necessario gestire la possibilità di nuove righe nei nomi dei file, è possibile cavarsela

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

poi

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

Se fare bisogno di ritorni a capo maniglia all'interno di nomi di file, e hanno bash> = 4.4, è possibile utilizzare -print0e -d ''per null-terminare i nomi durante la costruzione matrice:

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

(e similmente per il XYZ_FILES). Se non si dispone della bash più recente, è possibile utilizzare un ciclo di lettura con terminazione null per aggiungere nomi di file agli array, ad es.

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)

Eccellente! Stavo pensando alle matrici. Ma in qualche modo non ho trovato nulla su questo mapfile(o sul suo sinonimo readarray). Ma funziona!
Adam Badura,

Eppure potresti migliorarlo un po '. La versione di Bash <4.4 (che mi capita di avere ...) con un whileciclo non cancella l'array. Ciò significa che se non viene trovato alcun file l'array non è definito. Mentre se è già definito verranno aggiunti nuovi file (invece di sostituire quelli vecchi). Sembra che l'aggiunta declare -a ABC_FILES='()';prima whilefaccia il trucco. (Mentre l'aggiunta ABC_FILES='()';non lo fa.)
Adam Badura,

Inoltre cosa < <significa qui? È lo stesso di <<? Non credo che cambiarlo per <<produrre un errore di sintassi ("token inaspettato" ('"). Cos'è e come funziona?
Adam Badura,

Un altro miglioramento (lungo il mio uso particolare) è quello di costruire un altro array. Quindi abbiamo quelli ABC_FILES. Questo va bene. Ma è utile anche creare ABS_ARGSun array vuoto se ABC_FILESè vuoto oppure è un array ('--abc-files' "${ABC_FILES[@]}"). In questo modo in seguito posso usarlo in questo modo: ./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"ed essere sicuro che funzionerà correttamente indipendentemente da quale (se presente) dei gruppi è vuoto. O per dirlo diversamente: in questo modo --abc-files(e --xyz-files) verrà fornito solo se è seguito da un percorso effettivo.
Adam Badura,

1
@AdamBadura: while read ... done < <(find blah)è il normale reindirizzamento della shell <da un file speciale creato da PROCESS SUBSTITUTION . Ciò differisce dal piping find blah | while read ... doneperché la pipeline esegue il whileciclo in una subshell quindi i var impostati in esso non vengono conservati per i comandi successivi.
dave_thompson_085,

3

Puoi usare IFS = newline (supponendo che i nomi dei file non contengano newline) ma devi impostarlo nella shell esterna PRIMA della sostituzione:

$ 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

Con zshma non bashpuoi usare anche null $'\0'. Anche in bashte potresti gestire newline se c'è un personaggio sufficientemente strano che non viene mai usato come

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

Tuttavia, questo approccio non gestisce la richiesta aggiuntiva che hai fatto nei commenti sulla risposta di @ steeldriver per omettere --afiles se find a è vuoto.


Quindi, come ho capito in Bash, non c'è modo di forzare IFSa separarsi null?
Adam Badura,

@AdamBadura: sono quasi sicuro di no; bash non consente byte nulli in nessuna variabile, incluso IFS. Si noti che read -d ''nei metodi di steeldriver viene utilizzata una stringa vuota, non una contenente un byte null. (E un'opzione di comando non è comunque una var in quanto tale.)
dave_thompson_085

È inoltre necessario disabilitare globbing ( set -o noglob) prima di utilizzare quell'operatore split + glob (tranne in zsh).
Stéphane Chazelas,


@AdamBadura Sì, in bash, un null è esattamente uguale $'\0'e uguale a ''.
Isaac,

1

Non sono sicuro di capire perché hai rinunciato xargs.

Quindi fare xargsuna ricerca da una volta mi lascia ancora come affrontare l'altra ...

La stringa --xyz-filesè solo uno dei tanti argomenti e non c'è motivo di considerarla speciale prima che venga interpretata dal tuo programma. Penso che tu possa passare attraverso xargsentrambi i findrisultati:

{ 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

Hai ragione! Anche questo funziona! Tuttavia nota che ti sei perso -print0al secondo posto find. Anche se andare in questo modo avrei messo la --abc-filesquale echo, come pure - solo per la coerenza.
Adam Badura,

Questo approccio sembra più semplice e leggermente più lineare rispetto all'approccio array. Tuttavia, occorrerebbe qualche logica aggiuntiva per coprire il caso in cui se non ci sono .abcfile, allora non ci dovrebbe essere nemmeno --abc-files(lo stesso con .xyz). La soluzione basata su array di Steeldriver richiede anche una logica aggiuntiva, ma quella logica è banale lì mentre potrebbe non essere così banale qui distruggere il vantaggio principale di questa soluzione: la semplicità.
Adam Badura,

Inoltre non sono molto sicuro, ma presumo che xargsnon tenterà mai di dividere argomenti e fare pochi comandi invece di uno, a meno che non sia esplicitamente incaricato di farlo con -L, --max-lines( -l), --max-args( -n) o --max-chars( -s) argomenti. Ho ragione? O ci sono delle impostazioni predefinite? Dato che il mio programma non gestirà correttamente tale suddivisione e preferirei non riuscire a chiamarlo ...
Adam Badura,

1
@AdamBadura mancante -print0- risolto, grazie. Non conosco tutte le risposte, ma sono d'accordo che la mia soluzione rende difficile includere una logica aggiuntiva. Probabilmente andrei con gli array da solo, ora che conosco questo approccio. La mia risposta non era davvero per te. Avevi già accettato l'altra risposta e ho pensato che il tuo problema fosse risolto. Volevo solo sottolineare che puoi passare argomenti da più fonti xargs, il che non era ovvio a prima vista. Puoi trattarlo come una prova di concetto. Ora conosciamo tutti alcuni approcci diversi e possiamo scegliere consapevolmente ciò che ci si adatta in ogni caso particolare.
Kamil Maciorowski il

Sì, ho già implementato la soluzione basata su array e funziona come un fascino. Sono particolarmente orgoglioso di come si occupa in modo pulito dell'opzionalità (se non ci sono file allora no --abc-files). Ma hai ragione: è bello conoscere le tue alternative! Soprattutto che ho erroneamente pensato che non fosse possibile.
Adam Badura,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.