Shell un revestimiento para anteponer a un archivo


Respuestas:


29

El truco a continuación fue una respuesta rápida que funcionó y recibió muchos votos positivos. Luego, a medida que la pregunta se hizo más popular y pasó más tiempo, la gente indignada comenzó a informar que funcionaba pero que cosas extrañas podrían suceder, o simplemente no funcionó en absoluto, por lo que se rechazó furiosamente por un tiempo. Que 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 los nixes, su éxito depende completamente del sistema, definitivamente no es portátil, y no se debe confiar en ella para nada incluso vagamente importante.

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


Crear otro descriptor de archivo para el archivo ( exec 3<> yourfile) desde allí escribir en ese ( >&3) parece superar el dilema de lectura / escritura en el mismo archivo. Funciona para mí en archivos de 600K con awk. Sin embargo, intentar el mismo truco usando 'cat' falla.

Pasar el prependage como una variable a awk ( -v TEXT="$text") supera el problema de las comillas literales que impide 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

3
ADVERTENCIA: verifique la salida para asegurarse de que nada ha cambiado excepto la primera línea. Utilicé esta solución y, aunque parecía funcionar, noté que parecían agregarse algunas líneas nuevas. No entiendo de dónde provienen, por lo que no puedo estar seguro de que esta solución realmente tenga un problema, pero tenga cuidado.
conradlee

1
Tenga en cuenta en el ejemplo bash anterior que las comillas dobles se extienden sobre el retorno de carro para demostrar las líneas múltiples anteriores. Verifique que su shell y editor de texto estén cooperando. \ r \ n viene a la mente.
John Mee

44
¡Use esto con precaución! Vea el siguiente comentario para saber por qué: stackoverflow.com/questions/54365/…
Alex

3
Si ahora no es confiable, ¿qué tal actualizar su respuesta con una mejor solución?
zakdances

Un hack es útil si y solo si se sabe con precisión por qué funciona y, por lo tanto, bajo las restricciones cuidadosamente observadas . A pesar de su descargo de responsabilidad, su pirateo no cumple con estos criterios ("depende completamente del sistema", "parece superar", "funciona para mí"). Si tomamos en serio su descargo de responsabilidad, nadie debería usar su truco, y estoy de acuerdo. Entonces, ¿qué nos queda? Una ruidosa distracción. Veo dos opciones: (a) eliminar su respuesta, o (b) convertirla en una advertencia que explique por qué, por tentador que sea, este enfoque no puede funcionar en general .
mklement0

102

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


44
¿Qué está haciendo -después cat?
makbol

¿Hay alguna manera de agregar "texto" sin una nueva línea después?
chishaku

@chishakuecho -n "text"
Sparhawk

3
Una advertencia: si yourfilees un enlace simbólico, esto no hará lo que desea.
dshepherd

La respuesta es diferente a esta: echo "text"> / tmp / out; cat yourfile >> / tmp / out; mv / tmp / out tu archivo?
Richard


20

John Mee: no se garantiza que su método funcione, y probablemente fallará si antepone más de 4096 bytes de cosas (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 entrará en un bucle sin fin donde leerá su propia salida, haciendo crecer el archivo 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: mátalo después de un tiempo o llenará el sistema de archivos)

Además, es muy peligroso editar archivos de esa manera, y es un consejo muy malo, ya que si algo sucede mientras el archivo se está editando (bloqueo, disco lleno) es casi seguro que se quedará con el archivo en un estado inconsistente.


66
Esto probablemente debería ser un comentario, no una respuesta por separado.
lkraav

10
@Ikraav: tal vez, pero el "comentario" es muy largo y elaborado (y útil), no se verá bien y tal vez no se ajuste a la capacidad limitada de comentarios de StackOverflow de todos modos.
Hendy Irawan

18

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

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

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


16

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

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

15

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

cat header myfile | sponge myfile

Esto funcionó muy bien para mí cuando se combinó con la respuesta de @Vinko Vrsalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Jesse Hallett

13

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 redireccionamiento.


9

asumiendo que el archivo que desea editar es my.txt

$cat my.txt    
this is the regular file

Y el archivo que desea anteponer es encabezado

$ cat header
this is the header

Asegúrese de tener una última línea en blanco en el archivo de encabezado.
Ahora puedes anteponerlo 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'.


¿Por qué funciona esto? ¿Los paréntesis hacen que el gato del medio se ejecute en un shell separado y vuelque todo el archivo a la vez?
Catskul

Creo que <(cat my.txt) crea un archivo temporal en algún lugar del sistema de archivos. Este archivo se lee antes de modificar el archivo original.
cb0

Alguien me mostró una vez lo que puede hacer con la sintaxis "<()". Sin embargo, nunca aprendí cómo se llama este método ni pude encontrar algo en la página de manual de bash. Si alguien sabe más al respecto, hágamelo saber.
cb0

1
No funciono para mi. No se porque. Estoy usando GNU bash, versión 3.2.57 (1) -release (x86_64-apple-darwin14), estoy en OS X Yosemite. Terminé con dos líneas de this is the headeren my.txt. Incluso después de actualizar Bash 4.3.42(1)-release, obtengo el mismo resultado.
Kohányi Róbert

1
Bash no crea un archivo temporal, crea un FIFO (tubería) en el que escribe el comando en el proceso de sustitución ( <(...)) , por lo que no hay garantía de que my.txtse lea por completo , sin lo cual esta técnica no funcionará.
mklement0

9

Cuando comience a intentar hacer cosas que se vuelven difíciles en el script de shell, le sugiero encarecidamente que busque reescribir el script en un lenguaje de script "adecuado" (Python / Perl / Ruby / etc.)

En cuanto a anteponer una línea a un archivo, no es posible hacer esto a través de una tubería, ya que cuando hace algo así cat blah.txt | grep something > blah.txt, inadvertidamente pone el archivo en blanco. Hay un pequeño comando de utilidad llamado spongeque puede instalar (lo hace cat blah.txt | grep something | sponge blah.txty almacena el contenido del archivo, luego lo escribe en el archivo). Es similar a un archivo temporal, pero no tiene que hacerlo explícitamente. pero yo 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, con mucho, la forma más fácil (¿/ solo?).


7

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

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

7

EDITAR: Esto está roto. Vea Comportamiento extraño al anteponer un archivo con cat y tee

La solución al problema de sobrescritura está usando tee:

cat header main | tee main > /dev/null

Colgado por un rato. Terminó con "tee: main: No queda espacio en el dispositivo". El principal era varios GB, aunque ambos archivos de texto originales tenían solo unos pocos KB.
Marcos

3
Si la solución que publicó no funciona (y de hecho llena los discos duros de los usuarios), haga un favor a la comunidad y elimine su respuesta.
aioobe

1
¿Me puede indicar una directriz que diga que se deben eliminar las respuestas incorrectas?
Daniel Velkov

3

El que yo uso. Este le permite especificar el orden, los caracteres adicionales, etc. de la manera que desee:

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

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


3

Principalmente por diversión / golf de conchas, pero

ex -c '0r myheader|x' myfile

hará el truco, y no hay tuberías ni redireccionamientos. Por supuesto, vi / ex no es realmente para uso no interactivo, por lo que vi parpadeará brevemente.


La mejor receta desde mi punto de vista, ya que utiliza un comando establecido y lo hace de una manera directa.
Diego

2

¿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 in situ del archivo!

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

Otra solución alternativa sería utilizar identificadores de archivos abiertos como lo sugiere Jürgen Hötzel en la salida Redirect de sed 's / c / d /' myFile a myFile

echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt

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


2

Una variante en la solución de cb0 para "sin archivo temporal" para anteponer texto fijo:

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

Una vez más, esto se basa en la ejecución de sub-shell (the (..)) para evitar que el gato se niegue a tener el mismo archivo para entrada y salida.

Nota: me gustó esta solución. Sin embargo, en mi Mac, el archivo original se pierde (pensé que no debería, pero lo hace). Eso podría solucionarse escribiendo su solución como: echo "texto para anteponer" | cat - file_to_be_modified | cat> archivo_mp; mv tmp_file file_to_be_modified


1
Yo también encuentro el archivo original perdido. Ubuntu Lucid.
Erizo

2

Esto es lo que descubrí:

echo -e "header \n$(cat file)" >file

1
Esto es lo mismo que la sugerencia anterior de @nemisj ¿No?
Erizo


2

ADVERTENCIA: esto necesita un poco más de trabajo para satisfacer las necesidades del OP.

Debería haber una manera de hacer que el enfoque sed de @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 de sustitución sed (por ejemplo, reemplazar caracteres de nueva línea con '\ n'. Comandos de shell visy catpuede tratar con caracteres no imprimibles, pero no espacios en blanco, por lo que esto no resolverá los OP problema:

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

falla debido a las nuevas líneas sin procesar en el guión sustituto, que deben anteponerse con un carácter de continuación de línea () y tal vez seguido de un &, para mantener el shell y quedar contento, como esta respuesta SO

sed tiene un límite de tamaño de 40 K para los comandos de reemplazo de búsqueda no global (sin seguimiento / g después del patrón), por lo que probablemente evitaría los problemas de desbordamiento del búfer de miedo de awk que advirtió anónimo.


2
sed -i -e "1s/^/new first line\n/" old_file.txt

exactamente lo que estaba buscando, pero de hecho no es la respuesta correcta a la pregunta
masterxilo

2

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

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

1

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

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


1

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 puedes hacer:

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

Pero en ese caso, debe asegurarse de que no haya ninguno %, incluido el contenido del archivo de destino, ya que puede interpretarse y arruinar sus resultados.


2
Esto parece explotar (¡y truncar su archivo!) Si tiene algo en su archivo de destino que printf interpreta como una opción de formato.
Alan H.

echoParece una opción más segura. echo "my new line\n$(cat my/file.txt)" > my/file.txt
Alan H.

@AlanH. Advierto de los peligros en la respuesta.
user137369

En realidad, solo advirtió sobre los porcentajes ${new_line}, no el archivo de destino
Alan H.

¿Podrías ser más explícito? Es una gran advertencia IMO. "En cualquier lugar" no deja esto muy claro.
Alan H.

1

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á que ^ coincida solo con el principio. -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.


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

donde "my_file" es el archivo al que anteponer "my_string".


0

Me gusta el enfoque de @ fluffle's ed . Después de todo, los cambios de línea de comando de cualquier herramienta frente a los comandos de editor con script son esencialmente lo mismo aquí; no ver una solución de editor de secuencias de comandos "limpieza" es menor o no.

Aquí está mi línea única añadida para .git/hooks/prepare-commit-msganteponer un .gitmessagearchivo en el repositorio para enviar mensajes:

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

Ejemplo .gitmessage:

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

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

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

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

0

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

0

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

0

Si está escribiendo scripts en BASH, en realidad, puede emitir:

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

Eso está realmente en el ejemplo complejo que usted mismo publicó en su propia pregunta.


Un editor sugirió cambiar esto a cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile, sin embargo, eso es lo suficientemente diferente de mi respuesta como para que sea su propia respuesta.
Tim Kennedy

0

En mi humilde opinión, no hay una solución de shell (y nunca será una) que funcione de manera consistente y confiable independientemente de los tamaños de los dos archivos myheadery myfile. La razón es que si desea hacerlo sin recurrir a un archivo temporal (y sin dejar que el shell se repita silenciosamente a un archivo temporal, por ejemplo, a través de construcciones como exec 3<>myfile, canalizar atee , etc.)

La solución "real" que está buscando debe jugar con el sistema de archivos, por lo que no está disponible en el espacio de usuario y dependerá de la plataforma: está solicitando modificar el puntero del sistema de archivos en uso según myfileel valor actual del puntero del sistema de archivos para myheadery reemplazar en el sistema EOFde archivos de myheadercon un enlace encadenado a la dirección del sistema de archivos actual señalado por myfile. Esto no es trivial y obviamente no puede ser hecho por un no superusuario, y probablemente tampoco por el superusuario ... Jugar con inodes, etc.

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

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.