¿Cómo dividir un archivo grande en dos partes, en un patrón?
Dado un ejemplo file.txt
:
ABC
EFG
XYZ
HIJ
KNL
Quiero dividir este archivo en XYZ
tal que file1
contenga líneas hasta XYZ
y el resto de las líneas file2
.
¿Cómo dividir un archivo grande en dos partes, en un patrón?
Dado un ejemplo file.txt
:
ABC
EFG
XYZ
HIJ
KNL
Quiero dividir este archivo en XYZ
tal que file1
contenga líneas hasta XYZ
y el resto de las líneas file2
.
Respuestas:
Con awk
usted puede hacer:
awk '{print >out}; /XYZ/{out="file2"}' out=file1 largefile
Explicación: El primer awk
argumento ( out=file1
) define una variable con el nombre de archivo que se utilizará para la salida mientras largefile
se procesa el argumento posterior ( ). El awk
programa imprimirá todas las líneas en el archivo especificado por la variable out
( {print >out}
). Si se encuentra el patrón XYZ
, la variable de salida se redefinirá para que apunte al nuevo archivo ( {out="file2}"
) que se utilizará como destino para imprimir las líneas de datos posteriores.
Referencias
Este es un trabajo para csplit
:
csplit -sf file -n 1 large_file /XYZ/
sería s
ilently dividir el archivo, creando piezas con pre f
IX file
y n
umbered utilizando un solo dígito, por ejemplo, file0
etc Tenga en cuenta que el uso de /regex/
partiría hasta, pero sin incluir la línea que coincidencias regex
. Para dividir e incluir la coincidencia de línea, regex
agregue un +1
desplazamiento:
csplit -sf file -n 1 large_file /XYZ/+1
Esto crea dos archivos file0
y file1
. Si realmente necesita que se nombren file1
y file2
siempre puede agregar un patrón vacío al csplit
comando y eliminar el primer archivo:
csplit -sf file -n 1 large_file // /XYZ/+1
crea file0
, file1
y file2
aunque file0
está vacío para que pueda quitar de forma segura:
rm -f file0
Con un moderno, ksh
aquí hay una variante de shell (es decir, sin sed
) de una de las sed
respuestas basadas arriba:
{ read in <##XYZ ; print "$in" ; cat >file2 ;} <largefile >file1
Y otra variante en ksh
solo (es decir, también omitiendo cat
):
{ read in <##XYZ ; print "$in" ; { read <##"" ;} >file2 ;} <largefile >file1
(La ksh
solución pura parece ser bastante eficaz; en un archivo de prueba de 2.4 GB necesitaba 19-21 segundos, en comparación con 39-47 segundos con el enfoque sed
/ cat
basado).
read
y print
, simplemente debe dejarlo ir a la salida por sí solo. El rendimiento mejora si construye el conjunto de herramientas AST por completo y ksh
compila todos los componentes incorporados; es extraño para mí que sed
no sea uno de ellos, en realidad. Pero con cosas como while <file do
supongo que no es necesario sed
tanto ...
awk
desempeñó en su punto de referencia? Y aunque estoy bastante seguro de ksh
que siempre ganará esta pelea, si está utilizando un GNU con el sed
que no es muy justo sed
: -u
nbuffered de GNU es un enfoque pobre para POSIXLY garantizar que el desplazamiento del descriptor se deja donde el programa se cerró - no debería ser necesario ralentizar el funcionamiento regular del programa - el almacenamiento en búfer está bien - todo lo que sed
debe hacer es buscar el descriptor cuando haya terminado. Por alguna razón, GNU revierte esa mentalidad.
while
; la impresión se realiza implícitamente como el efecto secundario definido del <##
operador de redireccionamiento. Y solo se necesita imprimir la línea correspondiente. (De esa forma, la implementación de la función de shell es más flexible para admitir incl./excl.) Un while
ciclo explícito que esperaría que fuera significativamente más lento (pero no lo he verificado).
head
lugar de la read
; parece que sólo un poco más lento, pero más concisa de código: { head -1 <##XYZ ; { read <##"" ;} >file4 ;} <largefile >file3
.
Prueba esto con 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
Un truco fácil es imprimir ya sea en STDOUT o STDERR, dependiendo de si el patrón de destino ha coincidido. Luego puede usar los operadores de redirección del shell para redirigir la salida en consecuencia. Por ejemplo, en Perl, suponiendo que se llama al archivo de entrada f
y los dos archivos de salida f1
y f2
:
Descartando la línea que coincide con el patrón dividido:
perl -ne 'if(/XYZ/){$a=1; next} ; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2
Incluyendo la línea coincidente:
perl -ne '$a=1 if /XYZ/; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2
Alternativamente, imprima en diferentes identificadores de archivo:
Descartando la línea que coincide con el patrón dividido:
perl -ne 'BEGIN{open($fh1,">","f1");open($fh2,">","f2");}
if(/XYZ/){$a=1; next}$a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
Incluyendo la línea coincidente:
perl -ne 'BEGIN{open($fh1,">","f1"); open($fh2,">","f2");}
$a=1 if /XYZ/; $a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
XYZ
incluirse la línea en la salida o no?