Jak podzielić wyjście na dwa pliki za pomocą grep?


14

Mam skrypt mycommand.sh, którego nie mogę uruchomić dwukrotnie. Chcę podzielić dane wyjściowe na dwa różne pliki: jeden plik zawierający linie, które pasują do wyrażenia regularnego, i jeden plik zawierający linie, które nie pasują do wyrażenia regularnego. To, co chciałbym mieć, to w zasadzie coś takiego:

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

Wiem, że mogę po prostu przekierować dane wyjściowe do pliku, a następnie do dwóch różnych greps z opcją -v i bez opcji i przekierować ich dane wyjściowe do dwóch różnych plików. Ale zastanawiałem się, czy można to zrobić jednym grepem.

Czy to możliwe, aby osiągnąć to, czego chcę w jednym wierszu?

Odpowiedzi:


20

Istnieje wiele sposobów na osiągnięcie tego.

Korzystanie z awk

Następujące wysyła wszystkie wiersze pasujące coolregexdo pliku 1. Wszystkie pozostałe linie przechodzą do pliku 2:

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

Jak to działa:

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

    Wszystkie wiersze pasujące do wyrażenia regularnego coolregexsą drukowane file1. Następnie pomijamy wszystkie pozostałe polecenia i przeskakujemy, aby zacząć od nowa na nextlinii.

  2. 1

    Wszystkie pozostałe linie są wysyłane do standardowego wyjścia. 1jest tajemniczym skrótem awk dla print-the-line.

Możliwy jest również podział na wiele strumieni:

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

Korzystanie z podstawienia procesu

Nie jest to tak eleganckie jak rozwiązanie awk, ale dla kompletności możemy również użyć wielu greps w połączeniu z podstawieniem procesu:

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

Możemy również podzielić na wiele strumieni:

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

Fajnie! Czy można także podzielić go na kilka plików bez wykonywania kolejnego awk zamiast file2? Mam na myśli w taki sposób, że wyrażenia regularne mogą się na przykład nakładać.
yukashima huksay

1
@aran Tak, awk jest bardzo elastyczny. Dokładnie, jak to zrobić, zależy od tego, jak wyrażenia regularne się pokrywają.
John1024,

Chciałbym zobaczyć rozwiązanie, nawet jeśli nie obsługuje nakładających się wyrażeń regularnych. nakładając się, mam na myśli to, że przecięcie podzestawu nie jest nerwowo puste.
yukashima huksay

1
@aran Do przykładów odpowiedzi dodałem wiele strumieni dla obu metod.
John1024,

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

w filename - zapisz bieżącą przestrzeń wzorców do nazwy pliku.

Jeśli chcesz, aby wszystkie pasujące linie były kierowane do file_1wszystkich niepasujących linii file_2, możesz:

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

lub

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

Wyjaśnienie

  1. /pattern/!{p;d};
    • /pattern/!- negacja - jeśli linia nie zawiera pattern.
    • p - wydrukuj bieżącą przestrzeń wzoru.
    • d- usuń przestrzeń wzoru. Rozpocznij następny cykl.
    • więc jeśli linia nie zawiera wzorca, drukuje ją na standardowe wyjście i wybiera następny wiersz. Standardowe wyjście jest przekierowywane do file_2w naszym przypadku. Następna część sedskryptu ( w file_1) nie osiągnęła, gdy linia nie pasuje do wzorca.
  2. w file_1- jeśli linia zawiera wzór, /pattern/!{p;d};część jest pomijana (ponieważ jest wykonywana tylko wtedy, gdy wzór nie pasuje), a zatem linia ta przechodzi do file_1.

Czy możesz dodać jakieś wyjaśnienie do ostatniego rozwiązania?
yukashima huksay

@aran Dodano wyjaśnienie. Również polecenie zostało poprawione - file_1i file_2zostały zamienione we właściwej kolejności.
MiniMax

0

Podobało mi się to sedrozwiązanie, ponieważ nie opiera się ono na bashismach i traktuje pliki wyjściowe na tej samej podstawie. AFAIK, nie ma samodzielnego narzędzia uniksowego, które robi to, co chcesz, więc musisz sam go zaprogramować. Gdybyśmy zrezygnowali ze szwajcarskiego podejścia do noża wojskowego, moglibyśmy użyć dowolnego języka skryptowego (Perl, Python, NodeJS).

Tak by to było zrobić w 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)));

Przykładowe użycie

# 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

Jeśli nie przeszkadza ci użycie Pythona i innej składni wyrażeń regularnych:

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

Stosowanie

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

Przykład

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.