Como dividir uma saída para dois arquivos com o grep?

10

Eu tenho um script mycommand.sh que não posso executar duas vezes. Eu quero dividir a saída para dois arquivos diferentes, um arquivo contendo as linhas que correspondem a um regex e um arquivo contendo as linhas que não correspondem a um regex. O que eu quero é basicamente algo assim:

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

Eu sei que posso apenas redirecionar a saída para um arquivo e, em seguida, para dois diferentes greps com e sem opção -v e redirecionar sua saída para dois arquivos diferentes. Mas eu estava pensando se seria possível fazer isso com um grep.

Então, é possível conseguir o que eu quero em uma única linha?

    
por yukashima huksay 31.12.2017 / 22:27

4 respostas

16

Existem muitas maneiras de conseguir isso.

Usando o awk

O seguinte envia as linhas correspondentes a coolregex para o arquivo1. Todas as outras linhas vão para o arquivo2:

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

Como funciona:

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

    Todas as linhas que correspondem à expressão regular coolregex são impressas em file1 . Então, pulamos todos os comandos restantes e pulamos para começar de novo na linha next .

  2. 1

    Todas as outras linhas são enviadas para a stdout. 1 é a abreviação enigmática do awk para imprimir na linha.

A divisão em vários fluxos também é possível:

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

Usando a substituição de processos

Isso não é tão elegante quanto a solução awk, mas, para completar, também podemos usar vários greps combinados com a substituição de processos:

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

Também podemos dividir em vários fluxos:

./mycommand.sh | tee >(grep 'coolregex' >File1) >(grep 'otherregex' >File3) >(grep 'anotherregex' >File4) | grep -v 'coolregex' >File2
    
por 31.12.2017 / 22:31
7
sed -n -e '/pattern_1/w file_1' -e '/pattern_2/w file_2' input.txt

w filename - escreve o espaço do padrão atual em filename.

Se você quiser que todas as linhas correspondentes acessem file_1 e todas as linhas não correspondentes a file_2 , faça:

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

ou

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

Explicação

  1. %código%
    • /pattern/!{p;d}; - negação - se uma linha não contiver /pattern/! .
    • pattern - imprime o espaço padrão atual.
    • p - exclui o espaço do padrão. Comece o próximo ciclo.
    • Assim, se uma linha não contiver um padrão, ela imprimirá essa linha na saída padrão e selecionará a próxima linha. A saída padrão é redirecionada para o d no nosso caso. A próxima parte do script file_2 ( sed ) não foi atingida enquanto a linha não corresponde ao padrão.
  2. w file_1 - se uma linha contém um padrão, a parte w file_1 é ignorada (porque é executada apenas quando o padrão não corresponde) e, portanto, essa linha vai para o /pattern/!{p;d}; .
por 01.01.2018 / 00:05
0

Gostei da solução sed , pois ela não depende de bashisms e trata os arquivos de saída com a mesma base. AFAIK, não existe uma ferramenta Unix independente que faça o que você quer, então você precisa programá-la por conta própria. Se abandonássemos a abordagem do canivete suíço, poderíamos usar qualquer uma das linguagens de script (Perl, Python, NodeJS).

É assim que seria feito no 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)));

Exemplo de uso

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

# Using standard output streams
./partition.js pattern > file1.txt 2> file2.txt
    
por 01.01.2018 / 10:52
-1

Se você não se importa com o uso do Python e com uma sintaxe de expressão regular diferente:

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

Exemplo

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt
    
por 01.01.2018 / 16:33