arquivo dividido em duas partes, em um padrão

14

Como dividir um arquivo grande em duas partes, em um padrão?

Dado um exemplo file.txt:

ABC
EFG
XYZ
HIJ
KNL

Quero dividir esse arquivo de XYZmodo que file1contenha linhas até o XYZrestante das linhas file2.

d.putto
fonte
A XYZlinha deve ser incluída na saída ou não?
terdon
@terdon No meu caso, nenhuma linha "XYZ" não deve fazer parte do arquivo2. Mas se você tiver uma maneira de fazer isso, adicione a resposta. Pode ser útil em outros casos.
d.putto
Justo, feito.
terdon

Respostas:

10

Com awkvocê pode fazer:

awk '{print >out}; /XYZ/{out="file2"}' out=file1 largefile


Explicação: O primeiro awkargumento ( out=file1) define uma variável com o nome do arquivo que será usado para a saída enquanto o argumento subsequente ( largefile) for processado. O awkprograma imprimirá todas as linhas no arquivo especificado pela variável out( {print >out}). Se o padrão XYZfor encontrado, a variável de saída será redefinida para apontar para o novo arquivo ( {out="file2}") que será usado como destino para imprimir as linhas de dados subsequentes.

Referências:

Janis
fonte
14

Este é um trabalho para csplit:

csplit -sf file -n 1 large_file /XYZ/

seria silently dividir o arquivo, criando peças com pré fix filee numbered usando um único dígito, por exemplo file0etc. Note que usar /regex/iria dividir até, mas não incluindo a linha que partidas regex. Para dividir até e incluindo a linha correspondente, regexadicione um +1deslocamento:

csplit -sf file -n 1 large_file /XYZ/+1

Isso cria dois arquivos file0e file1. Se você absolutamente precisar que eles sejam nomeados file1e file2sempre poderá adicionar um padrão vazio ao csplitcomando e remover o primeiro arquivo:

csplit -sf file -n 1 large_file // /XYZ/+1

cria file0, file1e file2mas file0é vazio assim que você pode removê-lo com segurança:

rm -f file0
don_crissti
fonte
Esta, penso eu, é a resposta mais simples. Tudo o que você precisa fazer é listar alguns padrões e o arquivo será dividido por eles em ordem. Brilhante!
Henry Blyth
6

Com um moderno, kshaqui está uma variante do shell (ou seja, sem sed) de uma das sedrespostas baseadas acima:

{ read in <##XYZ ; print "$in" ; cat >file2 ;} <largefile >file1


E outra variante kshsozinha (ou seja, também omitindo o cat):

{ read in <##XYZ ; print "$in" ; { read <##"" ;} >file2 ;} <largefile >file1


(A kshsolução pura parece ter um bom desempenho; em um arquivo de teste de 2,4 GB, foram necessários 19 a 21 segundos, em comparação com 39 a 47 segundos com a abordagem sed/ cat).

Janis
fonte
É muito rápido Mas eu acho que você não precisa reade print- você deve deixá-lo sair sozinho. O desempenho melhora se você compilar o kit de ferramentas AST completamente e kshcompilar todos os componentes internos - é estranho para mim que sednão seja um deles, na verdade. Mas com coisas como while <file doeu acho que você não precisa sedtanto ...
mikeserv
Estou curioso - como foi o awkdesempenho no seu benchmark? E enquanto eu tenho certeza kshque provavelmente sempre vencerá essa luta, se você estiver usando um GNU com o sedqual não está sendo muito justo sed- o -unbuffered do GNU é uma abordagem pobre para o POSIXLY, garantindo que o deslocamento do descritor seja deixado onde o programa é encerrado ele - não deve haver necessidade de retardar a operação regular do programa - o buffer está bom - tudo o sedque você precisa fazer é procurar o descritor quando terminar. Por qualquer motivo, o GNU reverte essa mentalidade.
Mikeerv #
@mikeserv; A correspondência do padrão de redirecionamento é feita até que o padrão seja encontrado, e a linha com o padrão encontrado não será impressa se não for explicitamente feita conforme representado. (Pelo menos isso mostrou meu teste.) Observe que não há while; a impressão é implicitamente feita como o efeito colateral definido do <##operador de redirecionamento. E apenas a linha correspondente precisa de impressão. (Dessa forma, a implementação do recurso de shell é mais flexível para suporte a incl./excl.) Um whileloop explícito que eu esperaria ser significativamente mais lento (mas não foi verificado).
Janis
1
@mikeserv; Ah ok. BTW, eu apenas tentei o em headvez do read; ele parece ser apenas um pouco mais lento, mas de código terser: { head -1 <##XYZ ; { read <##"" ;} >file4 ;} <largefile >file3.
Janis
1
@mikeserv; Bom ponto; não foi. Mas quando eu ativo o builtin (acabei de fazer e verifiquei os resultados), são os mesmos números, estranhamente. (Talvez alguns função chamada sobrecarga em comparação a ler?)
Janis
6
{ sed '/XYZ/q' >file1; cat >file2; } <infile

Com o GNU, sedvocê deve usar o -uswitch nbuffered. A maioria dos outros sedsó deve funcionar.

Para deixar XYZ de fora ...

{ sed -n '/XYZ/q;p'; cat >file2; } <infile >file1
mikeserv
fonte
3

Tente isso com o GNU sed:

sed -n -e '1,/XYZ/w file1' -e '/XYZ/,${/XYZ/d;w file2' -e '}' large_file
Cyrus
fonte
Menor:sed -e '1,/XYZ/{w file1' -e 'd}' large_file > file2
don_crissti
1

Um truque fácil é imprimir em STDOUT ou STDERR, dependendo se o padrão de destino foi correspondido. Você pode usar os operadores de redirecionamento do shell para redirecionar a saída adequadamente. Por exemplo, no Perl, supondo que o arquivo de entrada seja chamado fe os dois arquivos de saída f1e f2:

  1. Descartando a linha que corresponde ao padrão de divisão:

    perl -ne 'if(/XYZ/){$a=1; next} ; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2
  2. Incluindo a linha correspondente:

    perl -ne '$a=1 if /XYZ/; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2

Como alternativa, imprima em diferentes identificadores de arquivo:

  1. Descartando a linha que corresponde ao padrão de divisão:

    perl -ne 'BEGIN{open($fh1,">","f1");open($fh2,">","f2");}
    if(/XYZ/){$a=1; next}$a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
  2. Incluindo a linha correspondente:

    perl -ne 'BEGIN{open($fh1,">","f1"); open($fh2,">","f2");}
              $a=1 if /XYZ/; $a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
terdon
fonte