xargs mit stdin / stdout Weiterleitung

21

Ich würde gerne laufen:

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

für jeden * .dat - Datei im Verzeichnis A .

Sicher, es könnte durch Bash / Python / was auch immer Skript getan werden, aber ich schreibe gerne sexy Einzeiler. Alles was ich erreichen konnte ist (immer noch ohne Standard):

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

Aber -a in xargs versteht replace-str 'file' nicht.

Danke für die Hilfe.

Nikolay Vyahhi
quelle
Zusätzlich zu den anderen guten Antworten können Sie hier die Option -a von GNU xargs verwenden.
James Youngman

Antworten:

30

Zunächst einmal nicht verwendet lsAusgabe als Dateiliste . Verwenden Sie die Shell-Erweiterung oder find. Nachfolgend finden Sie mögliche Folgen des Missbrauchs von ls + xargs und ein Beispiel für die ordnungsgemäße xargsVerwendung.

1. Einfacher Weg: for loop

Wenn Sie nur die Dateien unter verarbeiten möchten, sollte A/eine einfache forSchleife ausreichen:

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

2. pre1 Warum nicht   ls | xargs ?

Hier ist ein Beispiel dafür , wie schlecht die Dinge, wenn Sie verwenden lsmit xargsfür den Job. Stellen Sie sich folgendes Szenario vor:

  • Lassen Sie uns zuerst einige leere Dateien erstellen:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • siehe die Dateien und dass sie nichts enthalten:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • Führe einen magischen Befehl aus mit xargs:

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

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

Sie haben es also gerade geschafft, beide mypreciousfile.datund zu überschreiben mypreciousfile.dat.ans. Wenn sich Inhalte in diesen Dateien befunden hätten, wären sie gelöscht worden.


2. Verwenden   xargs : den richtigen Weg mit  find 

Wenn Sie darauf bestehen möchten xargs, verwenden Sie -0(nullterminierte Namen):

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

Beachten Sie zwei Dinge:

  1. Auf diese Weise erstellen Sie Dateien mit .dat.ansEndung.
  2. Dies wird unterbrochen, wenn ein Dateiname ein Anführungszeichen ( ") enthält.

Beide Probleme können auf unterschiedliche Weise durch Aufrufen der Shell gelöst werden:

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

3. Alles innerhalb erledigt find ... -exec

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

Dies erzeugt wiederum .dat.ansDateien und bricht ab, wenn Dateinamen enthalten ". Verwenden bashund ändern Sie dazu die Art und Weise, wie es aufgerufen wird:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;
rozcietrzewiacz
quelle
+1 für die Erwähnung, die Ausgabe von ls nicht zu analysieren.
Rahmu
2
Option 2 bricht ab, wenn der Dateiname ".
thiton
2
Sehr guter Punkt, danke! Ich werde entsprechend aktualisieren.
rozcietrzewiacz
Ich möchte nur erwähnen, dass, wenn zshes als Shell verwendet wird (und SH_WORD_SPLIT nicht gesetzt ist), alle bösen Sonderfälle (Leerzeichen, "im Dateinamen usw.) nicht berücksichtigt werden müssen. Das Triviale for file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; donefunktioniert in allen Fällen.
jofel
-1 weil ich versuche herauszufinden, wie man xargs mit stdin macht und meine Frage nichts mit files oder zu tun hat find.
Mehrdad
2

Versuchen Sie Folgendes (die Syntax kann je nach verwendeter Shell etwas variieren):

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

rahmu
quelle
Das würde nicht funktionieren. Es würde versuchen, Dinge wie zu bearbeiten somefile.dat.datund alle Ausgaben in eine einzige Datei umzuleiten.
Rozcietrzewiacz
Du hast recht. Ich habe die Lösung bearbeitet, um sie zu korrigieren.
Rahmu
OK - fast gut :) Nur somefile.dat.ansAusgabe-Zeug würde nicht so schön aussehen.
rozcietrzewiacz
1
Bearbeitet! Ich wusste nichts über '%'. Es funktioniert wie ein Zauber, danke für den Tipp.
Rahmu
1
Ein hinzufügen -type filewäre nett (kann nicht < directory), und das macht die Ungewöhnlich-Dateiname-Fee traurig.
Mat
2

Für einfache Muster ist die for-Schleife geeignet:

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

Für komplexere Fälle, in denen Sie ein anderes Hilfsprogramm zum Auflisten der Dateien benötigen (zsh oder bash 4 verfügen über ausreichend leistungsfähige Muster, die Sie selten finden müssen. Wenn Sie jedoch in der POSIX-Shell bleiben oder eine schnelle Shell wie dash verwenden möchten, müssen Sie für alles eine Suche durchführen nicht trivial), während lesen am besten geeignet ist:

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

Dies behandelt Leerzeichen, da das Lesen (standardmäßig) zeilenorientiert ist. Zeilenumbrüche und umgekehrte Schrägstriche werden nicht behandelt, da standardmäßig Escape-Sequenzen interpretiert werden (mit denen Sie zwar eine neue Zeile übergeben können, aber find dieses Format nicht generieren kann). Viele Shells haben die -0Möglichkeit zu lesen, so dass Sie mit allen Zeichen umgehen können, aber leider ist es kein POSIX.

Jan Hudec
quelle
2

Verwenden Sie GNU Parallel:

parallel ./a.out "<{} >{.}.ans" ::: A/*.dat

Zusätzlicher Bonus: Sie erledigen die Bearbeitung parallel.

Sehen Sie sich die Intro-Videos an, um mehr zu erfahren: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
quelle
1

Ich denke, Sie brauchen mindestens einen Shell-Aufruf in den Xargs:

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

Bearbeiten: Es sollte beachtet werden, dass dieser Ansatz nicht funktioniert, wenn die Dateinamen Leerzeichen enthalten. Kann nicht arbeiten Selbst wenn Sie find -0 und xargs -0 verwenden, damit xargs die Leerzeichen richtig versteht, wird der Shell-Aufruf -c sie quaken. Das OP hat jedoch ausdrücklich nach einer xargs-Lösung gefragt, und dies ist die beste xargs-Lösung, die ich mir ausgedacht habe. Wenn Leerzeichen in Dateinamen ein Problem sein könnten, verwenden Sie find -exec oder eine Shell-Schleife.

Thiton
quelle
@rozcietrzewiacz: Im Allgemeinen sicher, aber ich nehme an, dass jemand, der versucht, xargs voodoo zu machen, das weiß. Da diese Dateinamen eine weitere Shell-Erweiterung erfahren, müssen sie sich trotzdem gut benehmen.
Thiton
1
Sie irren sich über die Shell-Erweiterung. lsgibt Dinge wie Leerzeichen aus, ohne zu entkommen, und das ist das Problem.
Rozcietrzewiacz
@rozcietrzewiacz: Ja, ich verstehe das Problem. Stellen Sie sich nun vor, Sie würden diese Leerzeichen ordnungsgemäß maskieren und in xargs aufnehmen: Sie werden in shs -c-Zeichenfolge ersetzt, sh markiert die -c-Zeichenfolge und alles bricht ab.
Thiton
Ja. Sie sehen jetzt, Parsen lsist nicht gut. Aber xargs kann sicher mit find verwendet werden - siehe den Vorschlag No 2 in meiner Antwort.
Rozcietrzewiacz
1

Es besteht keine Notwendigkeit, es zu komplizieren. Du könntest es mit einer forSchleife machen:

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

Das ${file%.dat}.ansBit entfernt das .datDateinamensuffix aus dem Dateinamen $fileund fügt .anses stattdessen an das Ende an.

Kusalananda
quelle