Come dividere un output in due file con grep?


14

Ho una sceneggiatura mycommand.shche non posso eseguire due volte. Voglio dividere l'output in due file diversi un file contenente le righe che corrispondono a una regex e un file contenente le righe che non corrispondono a una regex. Quello che desidero avere è fondamentalmente qualcosa del genere:

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

So che posso semplicemente reindirizzare l'output su un file e poi su due greps diversi con e senza l'opzione -v e reindirizzare il loro output su due file diversi. Ma mi stavo solo chiedendo se fosse possibile farlo con un grep.

Quindi, è possibile ottenere ciò che voglio in una sola riga?

Risposte:


20

Ci sono molti modi per farlo.

Usando awk

Di seguito vengono inviate tutte le righe corrispondenti coolregexa file1. Tutte le altre righe vanno a file2:

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

Come funziona:

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

    Tutte le righe corrispondenti all'espressione regolare coolregexvengono stampate su file1. Quindi, saltiamo tutti i comandi rimanenti e saltiamo per ricominciare da capo next.

  2. 1

    Tutte le altre righe vengono inviate a stdout. 1è la stenografia criptica di awk per la stampa.

È anche possibile suddividere in più flussi:

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

Utilizzo della sostituzione del processo

Questo non è elegante come la soluzione awk ma, per completezza, possiamo anche usare più greps combinati con la sostituzione del processo:

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

Possiamo anche suddividere in più flussi:

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

Oh fico! È anche possibile dividerlo in più file senza semplicemente fare un altro awk invece di file2? Voglio dire in un modo che le regex possono sovrapporsi per esempio.
Yukashima Huksay,

1
@aran Sì, awk è molto flessibile. Proprio come uno dipenderebbe da come le regex si sovrappongono.
Giovanni 1024,

Mi piacerebbe vedere una soluzione anche se non supporta regex sovrapposte. per sovrapposizione intendo come avere l'intersezione del sottoinsieme non svuotata senza nervi.
Yukashima Huksay,

1
@aran Ho aggiunto agli esempi di risposta con più flussi per entrambi i metodi.
Giovanni 1024,

8
sed -n -e '/pattern_1/w file_1' -e '/pattern_2/w file_2' input.txt

w filename - Scrive lo spazio del motivo corrente nel nome del file.

Se vuoi che tutte le linee corrispondenti siano indirizzate a file_1tutte le linee non corrispondenti file_2, puoi fare:

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

o

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

Spiegazione

  1. /pattern/!{p;d};
    • /pattern/!- negazione - se una riga non contiene pattern.
    • p - stampa lo spazio del motivo corrente.
    • d- elimina lo spazio del motivo. Inizia il ciclo successivo.
    • quindi, se una linea non contiene un motivo, la stampa sull'output standard e seleziona la riga successiva. file_2Nel nostro caso, l' output standard viene reindirizzato a . La parte successiva dello sedscript ( w file_1) non viene raggiunta mentre la linea non corrisponde al modello.
  2. w file_1- se una linea contiene un motivo, la /pattern/!{p;d};parte viene ignorata (perché viene eseguita solo quando il motivo non corrisponde) e, quindi, questa linea va al punto file_1.

Potete per favore aggiungere qualche spiegazione in più all'ultima soluzione?
yukashima huksay,

@aran Spiegazione aggiunta. Anche il comando è stato corretto file_1e file_2sono stati scambiati nell'ordine giusto.
MiniMax

0

Mi è piaciuta la sedsoluzione poiché non si basa su bashismi e tratta i file di output sullo stesso piano. AFAIK, non esiste uno strumento Unix autonomo che fa ciò che desideri, quindi è necessario programmarlo da soli. Se abbandonassimo l'approccio del coltellino svizzero, potremmo usare uno qualsiasi dei linguaggi di scripting (Perl, Python, NodeJS).

Ecco come sarebbe fatto in NodeJS

  #!/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)));

Esempio di utilizzo

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

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

0

Se non ti dispiace l'uso di Python e una diversa sintassi delle espressioni regolari:

#!/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)

uso

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

Esempio

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt
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.