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.
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.
Respostas:
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:
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
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).
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 ...
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.
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).
headvez 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 fe os dois arquivos de saída f1e 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>f2Incluindo a linha correspondente:
perl -ne '$a=1 if /XYZ/; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2Como 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 "$_";' fIncluindo a linha correspondente:
perl -ne 'BEGIN{open($fh1,">","f1"); open($fh2,">","f2");}
$a=1 if /XYZ/; $a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
XYZlinha deve ser incluída na saída ou não?