Agregue líneas al principio y al final del archivo enorme

23

Tengo el escenario donde se agregarán líneas al comienzo y al final de los archivos enormes.

He intentado como se muestra a continuación.

  • para la primera línea:

    sed -i '1i\'"$FirstLine" $Filename
  • para la última línea:

    sed -i '$ a\'"$Lastline" $Filename  

Pero el problema con este comando es que agrega la primera línea del archivo y atraviesa todo el archivo. Para la última línea, nuevamente atraviesa todo el archivo y agrega una última línea. Dado que es un archivo muy grande (14 GB), esto lleva mucho tiempo.

¿Cómo puedo agregar una línea al principio y otra al final de un archivo mientras solo leo el archivo una vez?

UNIXbest
fuente

Respuestas:

20

sed -iusa archivos temporales como detalle de implementación, que es lo que está experimentando; sin embargo, anteponer datos al comienzo de un flujo de datos sin sobrescribir los contenidos existentes requiere reescribir el archivo, no hay forma de evitarlo, incluso cuando se evita sed -i.

Si reescribir el archivo no es una opción, puede considerar manipularlo cuando se lea, por ejemplo:

{ echo some prepended text ; cat file ; } | command

Además, sed es para editar secuencias: un archivo no es una secuencia. Utilice un programa destinado a este propósito, como ed o ex. La -iopción de sed no solo no es portátil, sino que también romperá los enlaces simbólicos a su archivo, ya que esencialmente lo elimina y lo recrea, lo cual no tiene sentido.

Puede hacer esto en un solo comando con el siguiente edmodo:

ed -s file << 'EOF'
0a
prepend these lines
to the beginning
.
$a
append these lines
to the end
.
w
EOF

Tenga en cuenta que dependiendo de su implementación de ed, puede usar un archivo de paginación, lo que requiere que tenga al menos tanto espacio disponible.

Chris Down
fuente
Hola, el comando ed que proporcionaste está funcionando muy bien para archivos enormes. Pero tengo 3 archivos enormes como Test, Test1, Test 2. Di comandos como ed -s Tes * << 'EOF' 0a anteponiendo estas líneas al principio. $ a agrega estas líneas hasta el final. w EOF Pero solo toma el archivo de prueba y agrega la primera / última línea. ¿Cómo podemos hacer cambios en el mismo comando para que tenga que agregar la primera y la última línea en todos los archivos?
UNIXbest
@UNIXbest - Usa un forbucle:for file in Tes*; do [command]; done
Chris Down
Hola, he utilizado el siguiente comando para el archivo en Tes *; do ed -s Tes * << 'EOF' 0a HEllO HDR. $ a Hola TLR. w EOF hecho Pero todavía está escribiendo en el primer archivo.
UNIXbest
Correcto, porque debes usarlo "$file", no Tes*como argumento para hacerlo ed.
Chris Down
2
@UNIXbest Si su respuesta ha resuelto su problema, debería considerar aceptarlo.
Joseph R.
9

Tenga en cuenta que si desea evitar asignar una copia completa del archivo en el disco, puede hacer lo siguiente:

sed '
1i\
begin
$a\
end' < file 1<> file

Eso utiliza el hecho de que cuando su stdin / stdout es un archivo, sed lee y escribe por bloque. Entonces, está bien que anule el archivo que está leyendo siempre que la primera línea que agregue sea más pequeña que sedel tamaño de bloque (debería ser algo así como 4k u 8k).

Sin embargo sed, tenga en cuenta que si por alguna razón falla (muerto, falla de la máquina ...), terminará con el archivo medio procesado, lo que significará que faltan algunos datos del tamaño de la primera línea en algún lugar en el medio.

También tenga en cuenta que a menos que su sedsea ​​GNU sed, eso no funcionará para datos binarios (pero como está usando -i, está usando GNU sed).

Stéphane Chazelas
fuente
esto me errores en Ubuntu 16.04
Csaba Toth
4

Aquí hay algunas opciones (todas las cuales crearán una nueva copia del archivo, así que asegúrese de tener suficiente espacio para eso):

  • eco simple / gato

    echo "first" > new_file; cat $File >> new_file; \
      echo "last" >> new_file; 
  • awk / gawk etc.

    gawk 'BEGIN{print "first\n"}{print}END{print "last\n"}' $File > NewFile 

    awky es como leer archivos línea por línea. El BEGIN{}bloque se ejecuta antes de la primera línea y el END{}bloque después de la última línea. Entonces, el comando anterior significa print "first" at the beginning, then print every line in the file and print "last" at the end.

  • Perl

    perl -ne 'BEGIN{print "first\n"} print;END{print "last\n"}' $File > NewFile

    Esto es esencialmente lo mismo que el gawk anterior escrito en Perl.

terdon
fuente
1
Tenga en cuenta que en todos estos casos, necesitará al menos 14 GB más de espacio para el nuevo archivo.
Chris Down
@ChrisDown buen punto, edité mi respuesta para dejar eso claro. Supuse que no era un problema ya que el OP estaba usando lo sed -ique crea archivos temporales.
terdon
3

Prefiero el mucho más simple:

gsed -i '1s/^/foo\n/gm; $s/$/\nbar/gm' filename.txt

Esto transforma el archivo:

asdf
qwer

al archivo:

foo
asdf
qwer
bar
Tostada de coma
fuente
2

Puede usar Vim en modo Ex:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 seleccione primera línea

  2. i insertar texto y nueva línea

  3. $ seleccione la última línea

  4. a agregar texto y nueva línea

  5. x guardar y cerrar

Steven Penny
fuente
¿Qué pasaría si quisiéramos hacer esto a varios archivos?
geoyws
1
@geoyws que no está realmente dentro del alcance de esta pregunta
Steven Penny
¿estás seguro de que es $ a y no% a?
Carlos Robles
2

No hay forma de insertar datos al comienzo de un archivo¹, todo lo que puede hacer es crear un nuevo archivo, escribir los datos adicionales y agregar los datos antiguos. Por lo tanto, tendrá que reescribir todo el archivo al menos una vez para insertar la primera línea. Sin embargo, puede agregar la última línea sin reescribir el archivo.

sed -i '1i\'"$FirstLine" $Filename
echo "$LastLine" >>$Filename

Alternativamente, puede combinar los dos comandos en una ejecución de sed.

sed -i -e '1i\'"$FirstLine" -e '$ a\'"$Lastline" $Filename

sed -icrea un nuevo archivo de salida y luego lo mueve sobre el archivo anterior. Esto significa que mientras sed funciona, hay una segunda copia del archivo que utiliza espacio. Puede evitar esto sobrescribiendo el archivo en su lugar , pero con restricciones importantes: la línea que está agregando debe ser más pequeña que el búfer de sed, y si su sistema falla, terminará con un archivo dañado y algo de contenido perdido en el medio, así que lo recomiendo encarecidamente.

¹ Linux tiene una forma de insertar datos en un archivo, pero solo puede insertar un número entero de bloques del sistema de archivos, no puede insertar cadenas de longitudes arbitrarias. Es útil para algunas aplicaciones, como bases de datos y máquinas virtuales, pero es inútil para archivos de texto.

Gilles 'SO- deja de ser malvado'
fuente
No es verdad. Mire fallocate()con FALLOC_FL_INSERT_RANGEdisponible en XFS y ext4 en kernels modernos (4.xx) man7.org/linux/man-pages/man2/fallocate.2.html
Eric
@Eric Solo puede insertar bloques completos, sin embargo, no longitudes de bytes arbitrarias, al menos a partir de Linux 4.15.0 con ext4. ¿Existe un sistema de archivos que pueda insertar longitudes de bytes arbitrarias?
Gilles 'SO- deja de ser malvado'
Correcto, pero todavía no hace que su declaración sea correcta. Usted escribió: "No hay forma de insertar datos al comienzo de un archivo". Eso todavía no es cierto: hay un mecanismo para insertar extensiones al comienzo de un archivo. Viene con advertencias, claro, pero vale la pena mencionarlo porque algunos usuarios pueden no preocuparse por las restricciones de tamaño de bloque al llenar espacios o retornos de carro.
Eric
0
$ (echo "Some Text" ; cat file1) > file2
Koushik Karmakar
fuente
44
Solo la respuesta de código no es aceptable, por favor mejore su respuesta
Networker
Considere expandir su respuesta para incluir una explicación de su sugerencia o enlaces a documentación que respalde su solución.
HalosGhost
-1

Los núcleos modernos de Linux (superiores a 4.1 o 4.2) admiten la inserción de datos al comienzo de un archivo a través de la fallocate()llamada al sistema con los FALLOC_FL_INSERT_RANGEsistemas de archivos ext4 y xfs. En esencia, esta es una operación de cambio lógico: los datos se reubican lógicamente en un desplazamiento más alto.

Existe una restricción con respecto a la granularidad del rango que desea insertar al comienzo del archivo. Pero para los archivos de texto, probablemente pueda asignar un poco más de lo requerido (hasta el límite de granularidad) y llenar con espacios o retornos de carro, pero eso depende de su aplicación

No conozco ninguna utilidad de Linux fácilmente disponible que manipule las extensiones de archivo, pero no es difícil de escribir: obtenga un descriptor de archivo y llame fallocate()con los argumentos apropiados. Para obtener más detalles, consulte la página de manual de la fallocatellamada del sistema: http://man7.org/linux/man-pages/man2/fallocate.2.html

Eric
fuente
Una utilidad no es el problema (suponiendo un Linux no incrustado): util-linux contiene una fallocateutilidad. El problema es que una granularidad de bloques completos hace que esto sea inútil para la mayoría de los archivos de texto. Otro problema es que la asignación de rango y la modificación posterior no son atómicas. Entonces, esto en realidad no resuelve el problema aquí.
Gilles 'SO- deja de ser malvado'
La granularidad es una advertencia que ya he mencionado y no, no lo hace inútil, depende de la aplicación. ¿Dónde viste en la pregunta que la atomicidad es importante? Solo puedo ver el problema de las actuaciones. Aun así, esta llamada al sistema parece ser atómica: elixir.bootlin.com/linux/latest/source/fs/open.c#L228 y si la atomicidad se vuelve importante (no lo es, pero digamos que es por el argumento) solo usa el bloqueo de archivos. (señaleme el lugar en el código del núcleo donde la fallocateatomicidad está rota, por favor, tengo curiosidad)
Eric