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.


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"' {} \;

+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


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.



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.



@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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.