xargs con reindirizzamento stdin / stdout


21

Vorrei correre:

./a.out < x.dat > x.ans

per ogni * .dat file nella directory A .

Certo, potrebbe essere fatto con bash / python / qualunque script, ma mi piace scrivere sexy one-liner. Tutto quello che ho potuto raggiungere è (ancora senza alcuno stdout):

ls A/*.dat | xargs -I file -a file ./a.out

Ma -a in xargs non comprende il 'file' replace-str.

Grazie per il tuo aiuto.


Oltre alle altre buone risposte qui, puoi usare l'opzione -a di GNU xargs.
James Youngman,

Risposte:


30

Prima di tutto, non utilizzare l' lsoutput come elenco di file . Utilizzare l'espansione della shell o find. Vedi sotto per le potenziali conseguenze dell'uso improprio di ls + xargs e un esempio di xargsuso corretto .

1. Modo semplice: per loop

Se vuoi elaborare solo i file sotto A/, un semplice forciclo dovrebbe essere sufficiente:

for file in A/*.dat; do ./a.out < "$file" > "${file%.dat}.ans"; done

2. pre1 Perché no   ls | xargs ?

Ecco un esempio di come le cose difettose possono girare se si utilizza lscon xargsper il lavoro. Considera uno scenario seguente:

  • prima creiamo alcuni file vuoti:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • vedere i file e che non contengono nulla:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • esegui un comando magico usando xargs:

    $ ls A/*.dat | xargs -I file sh -c "echo TRICKED > file.ans"
    
  • il risultato:

    $ cat A/mypreciousfile.dat
    TRICKED with junk at the end.dat.ans
    
    $ cat A/mypreciousfile.dat.ans
    TRICKED
    

Quindi sei appena riuscito a sovrascrivere sia mypreciousfile.date mypreciousfile.dat.ans. Se ci fosse del contenuto in quei file, sarebbe stato cancellato.


2. Utilizzo   xargs : nel modo corretto con  find 

Se desideri insistere sull'uso xargs, usa -0(nomi con terminazione null):

find A/ -name "*.dat" -type f -print0 | xargs -0 -I file sh -c './a.out < "file" > "file.ans"'

Nota due cose:

  1. in questo modo creerai file con .dat.ansfinale;
  2. questo si interromperà se il nome di un file contiene un segno di virgolette ( ").

Entrambi i problemi possono essere risolti con diversi modi di invocazione della shell:

find A/ -name "*.dat" -type f -print0 | xargs -0 -L 1 bash -c './a.out < "$0" > "${0%dat}ans"'

3. Tutto fatto all'interno find ... -exec

 find A/ -name "*.dat" -type f -exec sh -c './a.out < "{}" > "{}.ans"' \;

Questo, di nuovo, produce .dat.ansfile e si romperà se i nomi dei file contengono ". Per fare ciò, usa bashe cambia il modo in cui viene invocato:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;

+1 per aver menzionato di non analizzare l'output di ls.
Rahmu,

2
L'opzione 2 si interrompe quando il nome file contiene ".
thiton

2
Ottimo punto, grazie! Aggiornerò di conseguenza.
rozcietrzewiacz,

Voglio solo menzionare che se zshviene usato come shell (e SH_WORD_SPLIT non è impostato), tutti i cattivi casi speciali (spazi bianchi, "nel nome del file ecc.) Non devono essere considerati. Il banale for file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; donefunziona in tutti i casi.
jofel

-1 perché sto cercando di capire come fare xargs con stdin e la mia domanda non ha nulla a che fare con i file o find.
Mehrdad,

2

Prova a fare qualcosa del genere (la sintassi può variare leggermente a seconda della shell che usi):

$ for i in $(find A/ -name \*.dat); do ./a.out < ${i} > ${i%.dat}.ans; done


Non funzionerebbe. Tenterebbe di operare su cose come somefile.dat.date reindirizzare tutto l'output su un singolo file.
rozcietrzewiacz,

Hai ragione. Ho modificato la soluzione per correggerla.
Rahmu,

OK - Quasi buono :) L' somefile.dat.ansoutput di roba non sarebbe così bello.
rozcietrzewiacz,

1
Modificato! Non sapevo di '%'. Funziona come un fascino, grazie per la punta.
Rahmu,

1
Aggiungere un -type filesarebbe bello (impossibile < directory), e questo rende triste l'insolito nome-file-fata.
Mat

2

Per modelli semplici, il ciclo for è appropriato:

for file in A/*.dat; do
    ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Per i casi più complessi in cui è necessaria un'altra utilità per elencare i file (zsh o bash 4 hanno schemi abbastanza potenti che raramente è necessario trovare, ma se si desidera rimanere all'interno della shell POSIX o utilizzare la shell veloce come dash, è necessario trovare qualsiasi cosa non banale), mentre read è il più appropriato:

find A -name '*.dat' -print | while IFS= read -r file; do
   ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Questo gestirà gli spazi, perché read è (di default) orientato alla linea. Non gestirà le nuove righe e non gestirà le barre rovesciate, perché per impostazione predefinita interpreta le sequenze di escape (che in realtà ti consente di passare a una nuova riga, ma find non può generare quel formato). Molte shell hanno l' -0opzione di leggere, quindi in quelle è possibile gestire tutti i caratteri, ma sfortunatamente non è POSIX.



1

Penso che tu abbia bisogno di almeno un'invocazione della shell negli xargs:

ls A/*.dat | xargs -I file sh -c "./a.out < file > file.ans"

Modifica: Va notato che questo approccio non funziona quando i nomi dei file contengono spazi bianchi. Non posso lavorare Anche se hai usato find -0 e xargs -0 per far capire correttamente agli xargs gli spazi, la chiamata della shell -c graccherebbe su di essi. Tuttavia, l'OP ha chiesto esplicitamente una soluzione xargs, e questa è la migliore soluzione xargs che mi è venuta in mente. Se lo spazio bianco nei nomi dei file potrebbe essere un problema, utilizzare find -exec o un loop shell.



@rozcietrzewiacz: In generale, certo, ma presumo che qualcuno stia cercando di fare xargs voodoo lo sa. Poiché questi nomi di file subiscono un'altra espansione della shell, devono comunque essere ben educati.
thiton,

1
Ti sbagli sull'espansione della shell. lsproduce cose come gli spazi senza scappare e questo è il problema.
rozcietrzewiacz,

@rozcietrzewiacz: Sì, ho capito quel problema. Supponiamo ora che sfuggirai correttamente a questi spazi e li inserirai in xargs: vengono sostituiti nella stringa -c di sh, sh tokenizza la stringa -c e tutto si rompe.
thiton,

Sì. Vedi ora, l'analisi lsnon va bene. Ma xargs può essere tranquillamente utilizzato con find - vedere il suggerimento n. 2 nella mia risposta.
rozcietrzewiacz,

1

Non è necessario complicarlo. Potresti farlo con un forciclo:

for file in A/*.dat; do
  ./a.out < "$file" >"${file%.dat}.ans"
done

il ${file%.dat}.ansbit rimuoverà il .datsuffisso del nome file dal nome file in $filee invece lo aggiungerà .ansalla fine.

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.