script make how ejemplos create linux bash file shell text

linux - make - ¿Cómo dividir un archivo y mantener la primera línea en cada una de las piezas?



shell script linux (9)

Dado: un gran archivo de datos de texto (por ejemplo, formato CSV) con una primera línea ''especial'' (por ejemplo, nombres de campo).

Se busca: un equivalente del comando coreutils split -l , pero con el requisito adicional de que la línea del encabezado del archivo original aparezca al comienzo de cada una de las piezas resultantes.

Supongo que algún brebaje de split y head hará el truco?


Esta es la secuencia de comandos de robhruska limpiado un poco:

tail -n +2 file.txt | split -l 4 - split_ for file in split_* do head -n 1 file.txt > tmp_file cat $file >> tmp_file mv -f tmp_file $file done

Eliminé wc , cut , ls y echo en los lugares donde no son necesarios. Cambié algunos de los nombres de los archivos para hacerlos un poco más significativos. Lo dividí en varias líneas solo para que sea más fácil de leer.

Si quieres ser elegante, puedes usar mktemp o tempfile para crear un nombre de archivo temporal en lugar de usar uno con código tempfile .

Editar

Usando la split GNU es posible hacer esto:

split_filter () { { head -n 1 file.txt; cat; } > "$FILE"; }; export -f split_filter; tail -n +2 file.txt | split --lines=4 --filter=split_filter - split_

Descompuesto para la legibilidad:

split_filter () { { head -n 1 file.txt; cat; } > "$FILE"; } export -f split_filter tail -n +2 file.txt | split --lines=4 --filter=split_filter - split_

Cuando se especifica --filter , split ejecuta el comando (una función en este caso, que debe exportarse) para cada archivo de salida y establece la variable FILE , en el entorno del comando, en el nombre del archivo.

Un script o función de filtro podría hacer cualquier manipulación que quisiera al contenido de salida o incluso al nombre del archivo. Un ejemplo de esto último podría ser la salida a un nombre de archivo fijo en un directorio de variables: > "$FILE/data.dat" por ejemplo.


Esta es una versión más robusta del guión de Denis Williamson . La secuencia de comandos crea una gran cantidad de archivos temporales, y sería una pena si se dejaran tirados si la ejecución fuera incompleta. Entonces, agreguemos trampas de señal (vea http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html y luego http://tldp.org/LDP/abs/html/debugging.html ) y eliminar nuestros archivos temporales; esta es una mejor práctica de todos modos.

trap ''rm split_* tmp_file ; exit 13'' SIGINT SIGTERM SIGQUIT tail -n +2 file.txt | split -l 4 - split_ for file in split_* do head -n 1 file.txt > tmp_file cat $file >> tmp_file mv -f tmp_file $file done

Reemplace ''13'' con el código de retorno que desee. Ah, y probablemente deberías estar usando mktemp de todos modos (como algunos ya lo han sugerido), así que adelante y elimina ''tmp_file'' de la memoria en la línea de trampa. Mira la página de señal para obtener más señales.


Me gustó la versión awk de marco, adoptada a partir de esto, una línea simplificada donde se puede especificar fácilmente la fracción de división tan granular como desee:

awk ''NR==1{print $0 > FILENAME ".split1"; print $0 > FILENAME ".split2";} NR>1{if (NR % 10 > 5) print $0 >> FILENAME ".split1"; else print $0 >> FILENAME ".split2"}'' file


Nunca estoy seguro de las reglas para copiar scripts directamente de los sitios de otras personas, pero Geekology tiene una buena secuencia de comandos para hacer lo que quiera, con algunos comentarios que confirman que funciona. Asegúrese de hacer tail -n +2 como se indica en un comentario cerca de la parte inferior.


Puede usar la nueva funcionalidad de filtro en GNU coreutils split> = 8.13 (2011):

tail -n +2 FILE.in | split -l 50 - --filter=''sh -c "{ head -n1 FILE.in; cat; } > $FILE"''


Puedes usar [mg] awk:

awk ''NR==1{ header=$0; count=1; print header > "x_" count; next } !( (NR-1) % 100){ count++; print header > "x_" count; } { print $0 > "x_" count }'' file

100 es el número de líneas de cada sector. No requiere archivos temporales y se puede poner en una sola línea.


Realmente me gustaron las versiones de Rob y Dennis, tanto que quería mejorarlas.

Aquí está mi versión:

in_file=$1 awk ''{if (NR!=1) {print}}'' $in_file | split -d -a 5 -l 100000 - $in_file"_" # Get all lines except the first, split into 100,000 line chunks for file in $in_file"_"* do tmp_file=$(mktemp $in_file.XXXXXX) # Create a safer temp file head -n 1 $in_file | cat - $file > $tmp_file # Get header from main file, cat that header with split file contents to temp file mv -f $tmp_file $file # Overwrite non-header containing file with header-containing file done

Diferencias

  1. archivo_in es el argumento del archivo que desea dividir manteniendo encabezados
  2. Use awk lugar de tail debido a que awk tiene un mejor rendimiento
  3. dividido en 100.000 archivos de línea en lugar de 4
  4. El nombre del archivo dividido será el nombre del archivo de entrada anexado con un guión bajo y números (hasta 99999 - del argumento de división "-d -a 5")
  5. Use mktemp para manejar de forma segura los archivos temporales
  6. Use una sola head | cat línea de head | cat lugar de dos líneas

Soy un novato cuando se trata de Bash-fu, pero pude inventar esta monstruosidad de dos comandos. Estoy seguro de que hay soluciones más elegantes.

$> tail -n +2 file.txt | split -l 4 $> for file in `ls xa*`; do echo "`head -1 file.txt`" > tmp; cat $file >> tmp; mv -f tmp $file; done

Esto supone que su archivo de entrada es file.txt , no está usando el argumento de prefix para split , y está trabajando en un directorio que no tiene ningún otro archivo que comience con el formato de salida xa* predeterminado de split . Además, reemplace el ''4'' con el tamaño de línea de división que desee.


Use GNU Parallel:

parallel -a bigfile.csv --header : --pipepart ''cat > {#}''

Si necesita ejecutar un comando en cada una de las partes, GNU Parallel también puede ayudar a hacer eso:

parallel -a bigfile.csv --header : --pipepart my_program_reading_from_stdin parallel -a bigfile.csv --header : --pipepart --fifo my_program_reading_from_fifo {} parallel -a bigfile.csv --header : --pipepart --cat my_program_reading_from_a_file {}

Si desea dividir en 2 partes por núcleo de CPU (por ejemplo, 24 núcleos = 48 partes de igual tamaño):

parallel --block -2 -a bigfile.csv --header : --pipepart my_program_reading_from_stdin

Si desea dividir en bloques de 10 MB:

parallel --block 10M -a bigfile.csv --header : --pipepart my_program_reading_from_stdin