¿Cómo puedo dividir un archivo de texto en múltiples archivos de texto?


16

Tengo un archivo de texto llamado entry.txtque contiene lo siguiente:

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631
[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631
[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

Me gustaría que dividirlo en tres archivos de texto: entry1.txt, entry2.txt, entry3.txt. Sus contenidos son los siguientes.

entry1.txt :

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631

entry2.txt :

[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631

entry3.txt :

[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

En otras palabras, el [carácter indica que debe comenzar un nuevo archivo. Las entradas ( [ entry*]donde *es un número entero) siempre están en orden numérico y son números enteros consecutivos que comienzan de 1 a N (en mi archivo de entrada real, N = 200001).

¿Hay alguna manera de lograr la división automática de archivos de texto en bash? Mi entrada real en entry.txtrealidad contiene 200,001 entradas.

Respuestas:


11

Y aquí hay un lindo, simple, gawk one-liner:

$ gawk '/^\[/{match($0, /^\[ (.+?) \]/, k)} {print >k[1]".txt" }' entry.txt

Esto funcionará para cualquier tamaño de archivo, independientemente del número de líneas en cada entrada, siempre que se vea cada encabezado de entrada [ blahblah blah blah ]. Observe el espacio justo después de la apertura [y justo antes del cierre ].


EXPLICACIÓN:

awky gawklee un archivo de entrada línea por línea. A medida que se lee cada línea, su contenido se guarda en la $0variable. Aquí, le decimos que haga gawkcoincidir cualquier cosa entre corchetes y guarde su coincidencia en la matriz k.

Por lo tanto, cada vez que coincida esa expresión regular, es decir, para cada encabezado en su archivo, k [1] tendrá la región coincidente de la línea. A saber, "entrada1", "entrada2" o "entrada3" o "entradaN".

Finalmente, imprimimos cada línea en un archivo llamado <whatever value k currently has>.txt, es decir, entrada1.txt, entrada2.txt ... entradaN.txt.

Este método será mucho más rápido que Perl para archivos más grandes.


+1 bien. No necesita matchla entrada: /^\[/ { name=$2 }debería ser suficiente.
Thor

Gracias @Thor. Su sugerencia es correcta para el caso descrito, pero supone que nunca hay un espacio en el nombre de la entrada. Por eso usé el ejemplo [ blahblah blah blah ]en mi respuesta.
terdon

Ah, me perdí un poco acerca de las entradas separadas por espacios. También podría acomodar a aquellos con FS, por ejemplo -F '\\[ | \\]'.
Thor

@terdon Realmente me gustan estas soluciones cortas, desafortunadamente generalmente no las generalizo a mis necesidades. ¿Me puedes dar una mano? Mi archivo tiene líneas que comienzan con #S x, donde x es un número de 1, 2 o 3 dígitos. Solo guardarlos en x.dat sería suficiente. Lo intenté: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txty algunas variaciones de eso.
mikuszefski

Lo tengo gawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txthecho el truco. Sin 2embargo, no entiendo muy bien el número de matriz .
mikuszefski

17

Con csplit de GNU coreutils (Linux no incorporado, Cygwin):

csplit -f entry -b '%d.txt' entry.txt '/^\[ .* \]$/' '{*}'

Terminará con un archivo vacío adicional entry0.txt(que contiene la parte anterior al primer encabezado).

El estándar csplit carece del {*}repetidor indefinido y la -bopción de especificar el formato del sufijo, por lo que en otros sistemas tendrá que contar primero el número de secciones y luego cambiar el nombre de los archivos de salida.

csplit -f entry -n 9 entry.txt '/^\[ .* \]$/' "{$(egrep -c '^'\[ .* \]$' <entry.txt)}"
for x in entry?????????; do
  y=$((1$x - 1000000000))
  mv "entry$x" "entry$y.txt"
done

Encuentro que csplit es un poco peculiar de vez en cuando, pero increíblemente útil cuando quiero hacer este tipo de cosas.
ixtmixilix

10

En perl se puede hacer mucho más simple:

perl -ne 'open(F, ">", ($1).".txt") if /\[ (entry\d+) \]/; print F;' file

9

Aquí hay un breve awk one-liner:

awk '/^\[/ {ofn=$2 ".txt"} ofn {print > ofn}' input.txt

¿Como funciona esto?

  • /^\[/ coincide con las líneas que comienzan con un corchete izquierdo y
  • {ofn=$2 ".txt"}establece una variable en la segunda palabra delimitada por espacios en blanco como nuestro nombre de archivo de salida. Luego,
  • ofn es una condición que se evalúa como verdadera si la variable está establecida (lo que hace que se ignoren las líneas antes de su primer encabezado)
  • {print > ofn} redirige la línea actual al archivo especificado.

Tenga en cuenta que todos los espacios en este script awk se pueden eliminar, si la compacidad lo hace feliz.

Tenga en cuenta también que el script anterior realmente necesita que los encabezados de sección tengan espacios alrededor y no dentro de ellos. Si quisieras poder manejar encabezados de sección como [foo]y [ this that ], necesitarías un poco más de código:

awk '/^\[/ {sub(/^\[ */,""); sub(/ *\] *$/,""); ofn=$0 ".txt"} ofn {print > ofn}' input.txt

Esto utiliza la sub()función de awk para quitar espacios iniciales y finales de corchetes, más espacios en blanco. Tenga en cuenta que según el comportamiento awk estándar, esto colapsará los espacios en blanco (el separador de campo) en un solo espacio ( [ this that ]es decir, se guarda en "this that.txt"). Si es importante mantener el espacio en blanco original en los nombres de los archivos de salida, puede experimentar configurando FS.


2

Se puede hacer desde la línea de comando en python como:

paddy$ python3 -c 'out=0
> with open("entry.txt") as f: 
>   for line in f:
>     if line[0] == "[":
>       if out: out.close()
>       out = open(line.split()[1] + ".txt", "w")
>     else: out.write(line)'

2

Esta es una forma un tanto cruda, pero fácil de entender: utilícela grep -l '[ entry ]' FILENAMEpara dividir los números de línea en [entrada]. Usa una combinación de cabeza y cola para obtener las piezas correctas.

Como dije; No es bonito, pero es fácil de comprender.


2

¿Qué pasa con el uso de awk con [como separador de registros y el espacio como separador de campo? Esto nos proporciona fácilmente los datos que se colocarán en el archivo, ya $0que debe volver a colocar el encabezado eliminado [y el nombre de archivo como $1. Entonces solo tenemos que manejar el caso especial del primer registro que está vacío. Esto nos da:

awk -v "RS=[" -F " " 'NF != 0 {print "[" $0 > $1}' entry.txt

2

La respuesta de terdon me funciona, pero necesitaba usar gawk, no awk. El manual de gawk (busque 'match (') explica que el argumento de matriz en match () es una extensión de gawk. Tal vez depende de su instalación de Linux y sus versiones awk / nawk / gawk, pero en mi máquina Ubuntu solo gawk ejecutó la excelente terdon responder:

$ gawk '{if(match($0, /^\[ (.+?) \]/, k)){name=k[1]}} {print >name".txt" }' entry.txt

1

Aquí hay una solución perl. Este script detecta las [ entryN ]líneas y cambia el archivo de salida en consecuencia, pero no valida, analiza o procesa los datos en cada sección, solo imprime la línea de entrada en el archivo de salida.

#! /usr/bin/perl 

# default output file is /dev/null - i.e. dump any input before
# the first [ entryN ] line.

$outfile='/dev/null';
open(OUTFILE,">",$outfile) || die "couldn't open $outfile: $!";

while(<>) {
  # uncomment next two lines to optionally remove comments (starting with
  # '#') and skip blank lines.  Also removes leading and trailing
  # whitespace from each line.
  # s/#.*|^\s*|\s*$//g;
  # next if (/^$/)

  # if line begins with '[', extract the filename
  if (m/^\[/) {
    (undef,$outfile,undef) = split ;
    close(OUTFILE);
    open(OUTFILE,">","$outfile.txt") || die "couldn't open $outfile.txt: $!";
  } else {
    print OUTFILE;
  }
}
close(OUTFILE);

1

Hola, escribí este sencillo script usando ruby ​​para resolver tu problema

#!ruby
# File Name: split.rb

fout = nil

while STDIN.gets
  line = $_
  if line.start_with? '['
    fout.close if fout
    fname = line.split(' ')[1] + '.txt'
    fout = File.new fname,'w'
  end
  fout.write line if fout
end

fout.close if fout

puedes usarlo de esta manera:

ruby split.rb < entry.txt

Lo he probado y funciona bien.


1

Prefiero la csplitopción, pero como alternativa, aquí hay una solución awk de GNU:

parse.awk

BEGIN { 
  RS="\\[ entry[0-9]+ \\]\n"  # Record separator
  ORS=""                      # Reduce whitespace on output
}
NR == 1 { f=RT }              # Entries are of-by-one relative to matched RS
NR  > 1 {
  split(f, a, " ")            # Assuming entries do not have spaces 
  print f  > a[2] ".txt"      # a[2] now holds the bare entry name
  print   >> a[2] ".txt"
  f = RT                      # Remember next entry name
}

Ejecútelo así:

gawk -f parse.awk entry.txt

1
FWIW, la RTvariable parece ser específica de gawk. Esta solución no funciona para mí usando el awk de FreeBSD.
ghoti

@ ghoti: Correcto, debería haber mencionado eso. He incluido eso en la respuesta ahora. Gracias.
Thor
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.