Як розділити вихід на два файли грепом?


14

У мене є сценарій, mycommand.shякий я не можу запустити двічі. Я хочу розділити вихід на два різних файли, один файл, що містить рядки, що відповідають регулярному вираженню, і один файл, що містить рядки, що не відповідають регулярному вираженню. Що я хотів би мати в основному щось подібне:

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

Я знаю, що я можу просто перенаправити вихід на файл, а потім на два різні грепи з опцією і без -v і перенаправити їх вихід на два різних файли. Але мені було цікаво, чи можна це зробити одним грепом.

Отже, чи можна досягти того, що я хочу за один рядок?

Відповіді:


20

Є багато способів досягти цього.

Використання awk

Наступне надсилає будь-які рядки, що відповідають coolregexфайлу1. Усі інші рядки переходять до file2:

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

Як це працює:

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

    Будь-які рядки, що відповідають регулярному виразу coolregex, друкуються в file1. Потім ми пропускаємо всі команди, що залишилися, і стрибаємо, щоб почати на nextлінії.

  2. 1

    Усі інші рядки надсилаються до stdout. 1криптична стенограма awk для друку-лінії.

Також можливе розділення на кілька потоків:

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

Використання процесу заміщення

Це не настільки елегантно, як рішення awk, але для повноти ми також можемо використовувати декілька грепсів у поєднанні з заміною процесу:

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

Ми також можемо розділитись на кілька потоків:

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

О, круто! Чи можливо також розділити його на кілька файлів, не роблячи ще один awk замість file2? Я маю на увазі таким чином, що, наприклад, регекси можуть перекриватися.
юкасіма хуксай

1
@aran Так, awk дуже гнучкий. Саме від того, як це зробити, буде залежати від того, як регекси перекриваються.
John1024

Я хотів би бачити рішення, навіть якщо воно не підтримує перекриваються регулярні вирази. маючи на увазі, я маю на увазі те, що перетин підмножини не є нервово порожнім.
yukashima huksay

1
@aran Я додав до прикладів відповідей з кількома потоками для обох методів.
John1024

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

w filename - записати поточний простір шаблону до імені файла.

Якщо ви хочете, щоб усі відповідні рядки переходили, file_1а всі невідповідні лінії file_2, можете зробити:

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

або

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

Пояснення

  1. /pattern/!{p;d};
    • /pattern/!- заперечення - якщо рядок не містить pattern.
    • p - друкувати поточний простір шаблону.
    • d- видалити простір шаблону. Почніть наступний цикл.
    • Отже, якщо рядок не містить візерунок, він друкує цей рядок до стандартного виводу і вибирає наступний рядок. file_2У нашому випадку стандартний вихід переспрямовується на вихідний . Наступна частина sedсценарію ( w file_1) не досягла, поки рядок не відповідає шаблону.
  2. w file_1- якщо рядок містить шаблон, /pattern/!{p;d};частина пропускається (тому що вона виконується лише тоді, коли шаблон не збігається), і, таким чином, цей рядок переходить до file_1.

Чи можете ви додати ще якесь пояснення до останнього рішення?
юкасіма хуксай

@aran Пояснення додано. Також команда виправлена ​​- file_1і file_2були замінені на потрібний порядок.
MiniMax

0

sedРішення мені сподобалось, оскільки воно не покладається на башизми і обробляє вихідні файли на одній основі. AFAIK, не існує автономного інструменту Unix, який виконує те, що ви хочете, тому вам потрібно буде програмувати його самостійно. Якщо ми відмовилися від ножового підходу швейцарської армії, ми могли б використовувати будь-яку з мов сценаріїв (Perl, Python, NodeJS).

Так би це було зроблено в 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)));

Приклад використання

# 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

Якщо ви не заперечуєте проти використання Python та іншого синтаксису регулярного вираження:

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

Використання

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

Приклад

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.