Как разделить вывод на два файла с помощью grep?


14

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

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

Я знаю, что могу просто перенаправить вывод в файл, а затем в два разных greps с параметром -v и без него и перенаправить их вывод в два разных файла. Но мне было просто интересно, можно ли сделать это с одним grep.

Итак, возможно ли добиться того, что я хочу, в одной строке?

Ответы:


20

Есть много способов сделать это.

Использование awk

Следующее посылает любые строки, соответствующие coolregexfile1. Все остальные строки идут в file2:

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

Как это устроено:

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

    Любые строки, соответствующие регулярному выражению coolregex, печатаются в file1. Затем мы пропускаем все оставшиеся команды и начинаем с начала next.

  2. 1

    Все остальные строки отправляются на стандартный вывод. 1является загадочным сокращением awk для print-the-line.

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

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

Использование процесса замены

Это не так элегантно, как решение awk, но для полноты картины мы также можем использовать несколько grep в сочетании с заменой процесса:

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

Я хотел бы увидеть решение, даже если оно не поддерживает перекрывающиеся регулярные выражения. под перекрытием я подразумеваю, что пересечение подмножества не является пустым.
Юкашима Хуксай

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решение, так как оно не опирается на bashisms и обрабатывает выходные файлы на той же основе. 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.