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 XYZ
modo que file1
contenha linhas até o XYZ
restante das linhas file2
.
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 XYZ
modo que file1
contenha linhas até o XYZ
restante das linhas file2
.
Respostas:
Com awk
você pode fazer:
awk '{print >out}; /XYZ/{out="file2"}' out=file1 largefile
Explicação: O primeiro awk
argumento ( 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 awk
programa imprimirá todas as linhas no arquivo especificado pela variável out
( {print >out}
). Se o padrão XYZ
for 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:
Este é um trabalho para csplit
:
csplit -sf file -n 1 large_file /XYZ/
seria s
ilently dividir o arquivo, criando peças com pré f
ix file
e n
umbered usando um único dígito, por exemplo file0
etc. Note que usar /regex/
iria dividir até, mas não incluindo a linha que partidas regex
. Para dividir até e incluindo a linha correspondente, regex
adicione um +1
deslocamento:
csplit -sf file -n 1 large_file /XYZ/+1
Isso cria dois arquivos file0
e file1
. Se você absolutamente precisar que eles sejam nomeados file1
e file2
sempre poderá adicionar um padrão vazio ao csplit
comando e remover o primeiro arquivo:
csplit -sf file -n 1 large_file // /XYZ/+1
cria file0
, file1
e file2
mas file0
é vazio assim que você pode removê-lo com segurança:
rm -f file0
Com um moderno, ksh
aqui está uma variante do shell (ou seja, sem sed
) de uma das sed
respostas baseadas acima:
{ read in <##XYZ ; print "$in" ; cat >file2 ;} <largefile >file1
E outra variante ksh
sozinha (ou seja, também omitindo o cat
):
{ read in <##XYZ ; print "$in" ; { read <##"" ;} >file2 ;} <largefile >file1
(A ksh
soluçã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
).
read
e print
- você deve deixá-lo sair sozinho. O desempenho melhora se você compilar o kit de ferramentas AST completamente e ksh
compilar todos os componentes internos - é estranho para mim que sed
não seja um deles, na verdade. Mas com coisas como while <file do
eu acho que você não precisa sed
tanto ...
awk
desempenho no seu benchmark? E enquanto eu tenho certeza ksh
que provavelmente sempre vencerá essa luta, se você estiver usando um GNU com o sed
qual não está sendo muito justo sed
- o -u
nbuffered 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 sed
que você precisa fazer é procurar o descritor quando terminar. Por qualquer motivo, o GNU reverte essa mentalidade.
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 while
loop explícito que eu esperaria ser significativamente mais lento (mas não foi verificado).
head
vez do read
; ele parece ser apenas um pouco mais lento, mas de código terser: { head -1 <##XYZ ; { read <##"" ;} >file4 ;} <largefile >file3
.
Tente isso com o GNU sed:
sed -n -e '1,/XYZ/w file1' -e '/XYZ/,${/XYZ/d;w file2' -e '}' large_file
sed -e '1,/XYZ/{w file1' -e 'd}' large_file > file2
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 f
e os dois arquivos de saída f1
e f2
:
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
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:
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
Incluindo a linha correspondente:
perl -ne 'BEGIN{open($fh1,">","f1"); open($fh2,">","f2");}
$a=1 if /XYZ/; $a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
XYZ
linha deve ser incluída na saída ou não?