Shell one liner para anteponer a un archivo

Esta es probablemente una solución compleja .

Estoy buscando un operador simple como “>>”, pero para anteponer.

Me temo que no existe Tendré que hacer algo como

  mv myfile tmp
  cat myheader tmp> myfile

¿Algo más inteligente?

El truco a continuación fue una respuesta rápida que funcionó y recibió muchos votos ascendentes. Luego, a medida que la pregunta se hizo más popular y pasó más tiempo, las personas indignadas empezaron a informar que funcionaba pero que cosas extrañas podían suceder, o que simplemente no funcionaba en absoluto, por lo que se vino abajo furiosamente por un tiempo. Qué divertido.

La solución explota la implementación exacta de los descriptores de archivos en su sistema y, debido a que la implementación varía significativamente entre nixes, su éxito depende completamente del sistema, definitivamente no es portátil, y no debe confiarse en él para algo incluso vagamente importante.

Ahora, con todo eso fuera del camino, la respuesta fue:


La creación de otro descriptor de archivo para el archivo ( exec 3<> yourfile ) desde allí escribiendo ( >&3 ) parece superar la lectura / escritura en el mismo dilema de archivo. Funciona para mí en 600K archivos con awk. Sin embargo, falla el bash de usar el mismo truco con ‘gato’.

Pasar el preámbulo como una variable a awk ( -v TEXT="$text" ) supera el problema de citas literales que evita hacer este truco con ‘sed’.

 #!/bin/bash text="Hello world What's up?" exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3 

Esto todavía usa un archivo temporal, pero al menos está en una línea:

 echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile 

Crédito: BASH: anteponer un texto / líneas a un archivo

 echo '0a your text here . w' | ed some_file 

ed es el editor estándar! http://www.gnu.org/fun/jokes/ed.msg.html

John Mee: su método no está garantizado para funcionar, y probablemente fallará si antepone más de 4096 bytes de material (al menos eso es lo que sucede con gnu awk, pero supongo que otras implementaciones tendrán restricciones similares). No solo fallará en ese caso, sino que ingresará en un bucle sin fin donde leerá su propia salida, haciendo que el archivo crezca hasta que se llene todo el espacio disponible.

Pruébalo por ti mismo:

 exec 3<>myfile && awk 'BEGIN{for(i=1;i< =1100;i++)print i}{print}' myfile >&3 

(advertencia: matarlo después de un tiempo o llenará el sistema de archivos)

Además, es muy peligroso editar archivos de esa forma, y ​​es un consejo muy malo, ya que si algo sucede mientras el archivo está siendo editado (locking, disco lleno) casi se garantiza que el archivo quedará en un estado incoherente.

Vale la pena señalar que a menudo es una buena idea generar de manera segura el archivo temporal utilizando una utilidad como mktemp , al menos si el script se ejecutará alguna vez con privilegios de administrador. Por ejemplo, podría hacer lo siguiente (nuevamente en bash):

 (tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } ) 

No es posible sin un archivo temporal, pero aquí va un oneliner

 { echo foo; cat oldfile; } > newfile && mv newfile oldfile 

Puede usar otras herramientas como ed o perl para hacerlo sin archivos temporales.

Si necesita esto en las computadoras que controla, instale el paquete “moreutils” y use “sponge”. Entonces puedes hacer:

 cat header myfile | sponge myfile 

Usando un bash heredoc puede evitar la necesidad de un archivo tmp:

 cat < <-EOF > myfile $(echo this is prepended) $(cat myfile) EOF 

Esto funciona porque $ (cat myfile) se evalúa cuando se evalúa el script bash, antes de que se ejecute el gato con redirección.

suponiendo que el archivo que desea editar es my.txt

 $cat my.txt this is the regular file 

Y el archivo que desea anteponer es el encabezado

 $ cat header this is the header 

Asegúrese de tener una línea en blanco final en el archivo de encabezado.
Ahora puedes precederlo con

 $cat header < (cat my.txt) > my.txt 

Terminas con

 $ cat my.txt this is the header this is the regular file 

Hasta donde sé, esto solo funciona en ‘bash’.

Cuando empiezas a intentar hacer cosas que se vuelven difíciles en shell-script, te sugiero encarecidamente buscar reescribir el script en un lenguaje de scripting “apropiado” (Python / Perl / Ruby / etc)

En cuanto a anteponer una línea a un archivo, no es posible hacerlo a través de tuberías, como cuando haces algo como cat blah.txt | grep something > blah.txt cat blah.txt | grep something > blah.txt , inadvertidamente cat blah.txt | grep something > blah.txt en blanco el archivo. Hay un pequeño comando de utilidad llamado sponge que puedes instalar (haces cat blah.txt | grep something | sponge blah.txt y almacena el contenido del archivo, luego lo escribe en el archivo). Es similar a un archivo temporal, pero no tiene que hacer eso explícitamente. pero diría que es un requisito “peor” que, por ejemplo, Perl.

Puede haber una manera de hacerlo a través de awk, o similar, pero si tiene que usar shell-script, creo que un archivo temporal es, de lejos, el más fácil (¿solo?).

EDITAR: Esto está roto. Ver Comportamiento raro al anteponer a un archivo con cat y tee

La solución al problema de sobrescritura es usar tee :

 cat header main | tee main > /dev/null 

Como sugiere Daniel Velkov, usa tee.
Para mí, esa es una solución inteligente simple:

 { echo foo; cat bar; } | tee bar > /dev/null 

El que yo uso. Este te permite especificar el orden, los caracteres extra, etc. de la manera que te gusta:

 echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt 

PD: solo no funciona si los archivos contienen texto con barra diagonal inversa, porque se interpreta como caracteres de escape

En su mayoría para divertirse / jugar al golf, pero

 ex -c '0r myheader|x' myfile 

hará el truco, y no hay oleoductos o redirecciones. Por supuesto, vi / ex no es realmente para uso no interactivo, por lo que vi parpadeará brevemente.

¿Por qué no simplemente usar el comando ed (como ya sugirió Fluffle aquí)?

ed lee todo el archivo en la memoria y realiza automáticamente una edición de archivo en el lugar!

Entonces, si su archivo no es tan grande …

 # cf. "Editing files with the ed text editor from scripts.", # http://wiki.bash-hackers.org/doku.php?id=howto:edit-ed prepend() { printf '%s\n' H 1i "${1}" . wq | ed -s "${2}" } echo 'Hello, world!' > myfile prepend 'line to prepend' myfile 

Sin embargo, otra solución alternativa sería utilizar archivos abiertos como lo sugiere Jürgen Hötzel en Redirect output from sed ‘s / c / d /’ myFile to myFile

 echo cat > manipulate.txt exec 3 manipulate.txt 

Todo esto podría ponerse en una sola línea, por supuesto.

Una variante de la solución de cb0 para “ningún archivo temporal” para anteponer el texto fijo:

 echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified ) 

De nuevo, esto depende de la ejecución del subcuadro: (…) para evitar que el gato se niegue a tener el mismo archivo para la entrada y la salida.

Nota: Me gustó esta solución. Sin embargo, en mi Mac, el archivo original está perdido (pensó que no debería, pero lo hace). Eso podría solucionarse escribiendo su solución como: echo “text to prepend” | cat – file_to_be_modified | cat> tmp_file; mv tmp_file file_to_be_modified

Esto es lo que descubrí:

 echo -e "header \n$(cat file)" >file 
 sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile 

ADVERTENCIA: esto requiere un poco más de trabajo para cumplir con las necesidades del OP.

Debería haber una manera de hacer que el enfoque sed por @shixilun funcione a pesar de sus dudas. Debe haber un comando bash para escapar del espacio en blanco al leer un archivo en una cadena sustituta sed (por ejemplo, reemplazar caracteres de nueva línea con ‘\ n’. Los comandos de shell vis y cat pueden tratar caracteres no imprimibles, pero no espacios en blanco, así que esto no resolverá el problema de OP:

 sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt 

falla debido a las líneas nuevas sin formato en el script sustituto, que deben ir precedidas de un carácter de continuación de línea () y quizás seguido por un &, para mantener contentos al shell y al sed, como esta respuesta SO

sed tiene un límite de tamaño de 40K para comandos no globales de búsqueda-reemplazo (sin seguimiento / g después del patrón) por lo que es probable que evite los temibles problemas de desbordamiento del búfer de awk que advirtió anonymous.

Con $ (comando) puede escribir la salida de un comando en una variable. Así que lo hice en tres comandos en una línea y sin archivo temporal.

 originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile 

Si tiene un archivo grande (unos cientos de kilobytes en mi caso) y acceso a Python, esto es mucho más rápido que las soluciones de cat :

python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'

Una solución con printf :

 new_line='the line you want to add' target_file='/file you/want to/write to' printf "%s\n$(cat ${target_file})" "${new_line}" > "${target_file}" 

También podrías hacer:

 printf "${new_line}\n$(cat ${target_file})" > "${target_file}" 

Pero en ese caso, debe asegurarse de que no haya ningún % ninguna parte, incluidos los contenidos del archivo de destino, ya que eso puede interpretarse y estropear sus resultados.

Puede usar la línea de comando perl:

 perl -i -0777 -pe 's/^/my_header/' tmp 

Donde -i creará un reemplazo en línea del archivo y -0777 sorberá todo el archivo y hará ^ coincidir solo con el comienzo. -pe imprimirá todas las líneas

O si my_header es un archivo:

 perl -i -0777 -pe 's/^/`cat my_header`/e' tmp 

Donde / e permitirá una evaluación de código en la sustitución.

 current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file 

donde “my_file” es el archivo para anteponer “my_string” a.

Me gusta el enfoque ed de @ fluffle lo mejor. Después de todo, los conmutadores de línea de comando de cualquier herramienta frente a los comandos de editor con guiones son esencialmente lo mismo aquí; no ver una solución de editor guionizada “limpieza” siendo menor o lo que sea.

Este es mi documento único adjunto a .git/hooks/prepare-commit-msg para .gitmessage archivo .gitmessage en repo para confirmar los mensajes:

 echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1" 

Ejemplo .gitmessage :

 # Commit message formatting samples: # runlevels: boot +consolekit -zfs-fuse # 

Estoy haciendo 1r lugar de 0r , porque eso dejará la línea vacía lista para escribir en la parte superior del archivo de la plantilla original. No coloque una línea vacía en la parte superior de su .gitmessage , entonces, terminará con dos líneas vacías. -s suprime la salida de información de diagnóstico de ed.

En relación con pasar por esto, descubrí que para vim-buffs también es bueno tener:

 [core] editor = vim -c ':normal gg' 

variables, ftw?

 NEWFILE=$(echo deb http://mirror.csesoc.unsw.edu.au/ubuntu/ $(lsb_release -cs) main universe restricted multiverse && cat /etc/apt/sources.list) echo "$NEWFILE" | sudo tee /etc/apt/sources.list 

Creo que esta es la variación más limpia de ed:

 cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile 

como una función:

 function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; } cat myheader | prepend myfile 
 sed -i -e "1s/^/new first line\n/" old_file.txt 

Si estás creando scripts en BASH, en realidad, solo puedes emitir:

 cat - yourfile / tmp / out && mv / tmp / out yourfile

Eso es en realidad en el ejemplo complejo que usted mismo publicó en su propia pregunta.

En mi humilde opinión, no hay una solución de shell (y nunca será una) que funcione de manera consistente y confiable, independientemente del tamaño de los dos archivos myheader y myfile . La razón es que si quieres hacer eso sin recurrir a un archivo temporal (y sin dejar que el shell vuelva a aparecer silenciosamente en un archivo temporal, por ejemplo, a través de construcciones como exec 3<>myfile , piping to tee , etc.

La solución “real” que está buscando tiene que juguetear con el sistema de archivos, por lo que no está disponible en el espacio de usuario y depende de la plataforma: usted está pidiendo modificar el puntero del sistema de archivos en uso por myfile al valor actual del sistema de archivos puntero para myheader y reemplace en el sistema de archivos el EOF de myheader con un enlace encadenado a la dirección actual del sistema de archivos apuntada por myfile . Esto no es trivial y obviamente no puede ser hecho por un no superusuario, y probablemente tampoco por el superusuario … Juega con inodos, etc.

Sin embargo, puedes más o menos simular esto usando dispositivos de bucle. Ver por ejemplo este hilo SO .