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.