Como dividir uma saída em dois arquivos com grep?


14

Eu tenho um script mycommand.shque não consigo executar duas vezes. Eu quero dividir a saída em dois arquivos diferentes, um arquivo contendo as linhas que correspondem a uma regex e um arquivo contendo as linhas que não correspondem a uma regex. O que eu gostaria de ter é basicamente algo como isto:

./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 depois para dois greps diferentes com e sem a opção -v e redirecionar sua saída para dois arquivos diferentes. Mas eu estava apenas me perguntando se era possível fazê-lo com um grep.

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

Respostas:


20

Existem muitas maneiras de conseguir isso.

Usando awk

O seguinte envia as linhas correspondentes coolregexao 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}

    Quaisquer linhas correspondentes à expressão regular coolregexsão impressas file1. Em seguida, pulamos todos os comandos restantes e pulamos para começar de novo na nextlinha.

  2. 1

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

Também é possível dividir em vários fluxos:

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

Usando substituição de processo

Isso não é tão elegante quanto a solução awk, mas, para ser completo, também podemos usar vários greps combinados com a substituição do processo:

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

Também podemos nos dividir em vários fluxos:

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

Oh fixe! Também é possível dividi-lo em vários arquivos sem apenas fazer outro awk em vez de file2? Quero dizer de uma maneira que as expressões regulares podem se sobrepor, por exemplo.
— yukashima huksay

1
@aran Sim, o awk é muito flexível. Precisamente como se faz, dependerá de como as expressões regulares se sobrepõem.
— John1024

Eu adoraria ver uma solução, mesmo que ela não suporte regexes sobrepostas. ao se sobrepor, quero dizer como ter a interseção do subconjunto não vazia de maneira inabalável.
— yukashima huksay

1
@aran Adicionei aos exemplos de respostas com vários fluxos para os dois métodos.
— John1024

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

w filename - escreva o espaço padrão atual no nome do arquivo.

Se você deseja que todas as linhas correspondentes acessem file_1e todas as linhas não correspondentes file_2, você pode:

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. /pattern/!{p;d};
    • /pattern/!- negação - se uma linha não contiver pattern.
    • p - imprime o espaço padrão atual.
    • d- excluir espaço padrão. Iniciar o próximo ciclo.
    • portanto, se uma linha não contiver padrão, ela será impressa na saída padrão e escolherá a próxima linha. A saída padrão é redirecionada para o file_2no nosso caso. A próxima parte do sedscript ( w file_1) não é alcançada enquanto a linha não corresponde ao padrão.
  2. w file_1- se uma linha contiver um padrão, a /pattern/!{p;d};peça será ignorada (porque é executada apenas quando o padrão não corresponde) e, portanto, essa linha vai para o file_1.

Você pode adicionar mais explicações à última solução?
— yukashima huksay

@aran Explicação adicionada. Além disso, o comando foi corrigido - file_1e file_2foi trocado pela ordem correta.
— MiniMax

0

Gostei da sedsolução, pois ela não se baseia em basismos e trata os arquivos de saída no mesmo pé. AFAIK, não existe uma ferramenta Unix autônoma que faça o que você deseja, portanto você precisará programá-lo 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 isso 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

0

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

#!/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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.