Wie teilt man eine Ausgabe mit grep in zwei Dateien?

14

Ich habe ein Skript mycommand.sh, das ich nicht zweimal ausführen kann. Ich möchte die Ausgabe in zwei verschiedene Dateien aufteilen, wobei eine Datei die Zeilen enthält, die einem regulären Ausdruck entsprechen, und eine Datei die Zeilen enthält, die keinem regulären Ausdruck entsprechen. Was ich haben möchte, ist im Grunde so etwas:

./mycommand.sh | grep -E 'some|very*|cool[regex].here;)' --match file1.txt --not-match file2.txt

Ich weiß, dass ich die Ausgabe einfach in eine Datei und dann in zwei verschiedene Greps mit und ohne Option -v umleiten und ihre Ausgabe in zwei verschiedene Dateien umleiten kann. Aber ich habe mich nur gefragt, ob es möglich ist, es mit einem Grep zu tun.

Ist es also möglich, in einer einzigen Zeile das zu erreichen, was ich will?

Yukashima Huksay
quelle

Antworten:

20

Es gibt viele Möglichkeiten, dies zu erreichen.

Awk benutzen

Der folgende coolregexBefehl sendet alle Zeilen, die mit file1 übereinstimmen . Alle anderen Zeilen gehen zu file2:

./mycommand.sh | awk '/[coolregex]/{print>"file1";next} 1' >file2

Wie es funktioniert:

  1. /[coolregex]/{print>"file1";next}

    Alle Zeilen, die mit dem regulären Ausdruck übereinstimmen, coolregexwerden in gedruckt file1. Dann überspringen wir alle verbleibenden Befehle und springen, um in der nextZeile von vorne zu beginnen .

  2. 1

    Alle anderen Zeilen werden an stdout gesendet. 1ist awks kryptische Abkürzung für Print-the-Line.

Eine Aufteilung in mehrere Streams ist ebenfalls möglich:

./mycommand.sh | awk '/regex1/{print>"file1"} /regex2/{print>"file2"} /regex3/{print>"file3"}'

Prozessersetzung verwenden

Dies ist nicht so elegant wie die awk-Lösung, aber der Vollständigkeit halber können wir auch mehrere greps in Kombination mit der Prozessersetzung verwenden:

./mycommand.sh | tee >(grep 'coolregex' >File1) | grep -v 'coolregex' >File2

Wir können auch in mehrere Streams aufteilen:

./mycommand.sh | tee >(grep 'coolregex' >File1) >(grep 'otherregex' >File3) >(grep 'anotherregex' >File4) | grep -v 'coolregex' >File2
John1024
quelle
Oh cool! Ist es auch möglich, es in mehrere Dateien aufzuteilen, ohne eine andere awk anstelle von file2 auszuführen? Ich meine auf eine Art und Weise, dass sich beispielsweise reguläre Ausdrücke überschneiden können.
Yukashima Huksay
1
@aran Ja, awk ist sehr flexibel. Wie genau man das macht, hängt davon ab, wie sich die regulären Ausdrücke überlappen.
John1024
Ich würde gerne eine Lösung sehen, auch wenn sie keine überlappenden regulären Ausdrücke unterstützt. Mit Überlappung meine ich, dass die Kreuzung der Teilmenge nicht nervenlos leer ist.
Yukashima Huksay
1
@aran Ich habe den Antwortbeispielen für beide Methoden mehrere Streams hinzugefügt.
John1024
8
sed -n -e '/pattern_1/w file_1' -e '/pattern_2/w file_2' input.txt

w filename - Schreibe den aktuellen Musterraum in den Dateinamen.

Wenn Sie möchten, dass alle übereinstimmenden Zeilen zu file_1und alle nicht übereinstimmenden Zeilen zu file_2wechseln, können Sie Folgendes tun:

sed -n -e '/pattern/w file_1' -e '/pattern/!w file_2' input.txt

oder

sed -n '/pattern/!{p;d}; w file_1' input.txt > file_2

Erläuterung

  1. /pattern/!{p;d};
    • /pattern/!- negation - wenn eine Zeile nicht enthält pattern.
    • p - Den aktuellen Musterbereich drucken.
    • d- Musterraum löschen. Beginnen Sie den nächsten Zyklus.
    • Wenn also eine Zeile kein Muster enthält, wird diese Zeile in der Standardausgabe gedruckt und die nächste Zeile ausgewählt. Die Standardausgabe wird file_2in unserem Fall an umgeleitet . Der nächste Teil des sedSkripts ( w file_1) wird nicht erreicht, solange die Linie nicht mit dem Muster übereinstimmt.
  2. w file_1- Wenn eine Zeile ein Muster enthält, wird der /pattern/!{p;d};Teil übersprungen (da er nur ausgeführt wird, wenn das Muster nicht übereinstimmt), und diese Zeile geht daher zum file_1.
MiniMax
quelle
Können Sie der letzten Lösung noch eine Erklärung hinzufügen?
Yukashima Huksay
@aran Erklärung hinzugefügt. Auch der Befehl wird korrigiert - file_1und file_2in die richtige Reihenfolge getauscht.
MiniMax
0

Ich mochte die sedLösung, da sie nicht auf Bashismen beruht und die Ausgabedateien auf derselben Grundlage behandelt. AFAIK, es gibt kein eigenständiges Unix-Tool, das genau das tut, was Sie wollen. Sie müssen es also selbst programmieren. Wenn wir den Schweizer Taschenmesser-Ansatz aufgeben würden, könnten wir eine der Skriptsprachen (Perl, Python, NodeJS) verwenden.

So würde es in NodeJS gemacht

  #!/usr/bin/env node

  const fs = require('fs');
  const {stderr, stdout, argv} = process;

  const pattern = new RegExp(argv[2] || '');
  const yes = argv[3] ? fs.createWriteStream(argv[3]) : stdout;
  const no = argv[4] ? fs.createWriteStream(argv[4]) : stderr;

  const out = [no, yes];

  const partition = predicate => e => {
    const didMatch = Number(!!predicate(e));
    out[didMatch].write(e + '\n');
  };

  fs.readFileSync(process.stdin.fd)
    .toString()
    .split('\n')
    .forEach(partition(line => line.match(pattern)));

Anwendungsbeispiel

# Using designated files
./mycommand.sh | partition.js pattern file1.txt file2.txt

# Using standard output streams
./partition.js pattern > file1.txt 2> file2.txt
Elias
quelle
0

Wenn Ihnen die Verwendung von Python und eine andere Syntax für reguläre Ausdrücke nichts ausmacht:

#!/usr/bin/env python3
import sys, re

regex, os1, os2 = sys.argv[1:]
regex = re.compile(regex)
with open(os1, 'w') as os1, open(os2, 'w') as os2:
    os = (os1, os2)
    for line in sys.stdin:
        end = len(line) - line.endswith('\n')
        os[regex.search(line, 0, end) is not None].write(line)

Verwendung

./match-split.py PATTERN FILE-MATCH FILE-NOMATCH

Beispiel

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt
David Foerster
quelle