Comando Unix para anteponer texto a un archivo

126

¿Hay un comando Unix para anteponer algunos datos de cadena a un archivo de texto?

Algo como:

prepend "to be prepended" text.txt
Uno dos tres
fuente
¿Está buscando un solo comando o está bien un pequeño script / alias?
Levon
2
Si combina todas las respuestas, esta es probablemente la forma más compacta:<<(echo "to be prepended") < text.txt | sponge text.txt
Sridhar Sarnobat
¡Esta pregunta realmente sacó a relucir la creatividad en mucha gente!
Richard Jessop hace

Respuestas:

111
sed -i.old '1s;^;to be prepended;' inFile
  • -iescribe el cambio en su lugar y realiza una copia de seguridad si se proporciona alguna extensión. (En este caso .old)
  • 1s;^;to be prepended;sustituye el comienzo de la primera línea por la cadena de reemplazo dada, utilizando ;como delimitador de comando.
Príncipe John Wesley
fuente
19
Si pudiera agregar alguna explicación con el comando, sería útil para otros que no estén tan familiarizados con el funcionamiento de sed. OP puede querer insertar \ndespués de anteponer; dependiendo de sus necesidades Solución ordenada
Levon
Sí, una explicación sería genial. ¿Puede inFileincluir los directorios en su ruta?
zakdances
3
@Levon, quería insertar un \n. Debería haber leído los comentarios primero. Maldición.
chishaku
En mi caso, yo quiero tener un punto y coma al final de la línea antepuesto, así que fui con'1s;^;my-prepended-line\;\n;'
SamGamgee
55
Tenga en cuenta que '\ n' solo funciona para GNU sed, no BSD (como OSX, FreeBSD, etc.). Para ser más portátil con esos, consulte: stackoverflow.com/questions/1421478/…
jwd
163
printf '%s\n%s\n' "to be prepended" "$(cat text.txt)" >text.txt
shime
fuente
2
¡En una línea y en el archivo de texto original! Esta es la respuesta simple correcta. Sugiero que la nueva línea adicional \ n echo -e "se anteponga \ n $ (cat text.txt)"> text.txt
VincentPerrin.com
32
Esta solución tiene problemas ... convierte las apariciones de "\ n" en nuevas líneas reales ... problemático si antepone a un archivo de código con cadenas que contienen "\ n".
Paul Go
1
Tenía esto en mi archivo git config --get-regex $arg | sed -r 's/\t{3}/\\n/g';y esto se confunde al convertir el \ty \n.
FELIZ
8
Esto (1) carga todo el archivo en la memoria y (2) pega todo el archivo en un solo argumento de línea de comandos. Está bien para cosas simples, pero ten cuidado con las limitaciones.
jwd
2
Lo último que intenté, creo que esto se rompería en archivos realmente grandes, donde cat no puede leer todo el contenido de text.txt antes de que se sobrescriba. Si la opción -e en echo es un problema, puede citar una nueva línea en bash o doecho "to be prepended"$'\n'"$(cat text.txt)"
jbo5112
43

Proceso de sustitución

Me sorprende que nadie haya mencionado esto.

cat <(echo "before") text.txt > newfile.txt

lo cual es posiblemente más natural que la respuesta aceptada (imprimir algo y colocarlo en un comando de sustitución es lexicográficamente contra-intuitivo).

... y secuestrando lo que Ryan dijo anteriormente, con spongeusted no necesita un archivo temporal:

sudo apt-get install moreutils
<<(echo "to be prepended") < text.txt | sponge text.txt

EDITAR: Parece que esto no funciona en Bourne Shell /bin/sh


Here String (solo zsh)

Usando una cadena aquí <<<, puede hacer:

<<< "to be prepended" < text.txt | sponge text.txt
Sridhar Sarnobat
fuente
3
Esta respuesta es la ganadora para mí. Simple y alcanzable solo con incorporados. ¡Buen trabajo!
PiersyP
1
@PiersyP ¡Estoy de acuerdo! Esto ayudó mucho, gracias Sridhar-Sarnobat.
El problema es que hay 1 archivo, no 2 archivos. Entonces, la primera línea de la respuesta realmente no funciona. La última línea podría funcionar (pero requiere esponja).
VasiliNovikov
1
¿Qué hace la esponja?
Hemanta
1
Tu <<(echo "to be prepended") < text.txty <<< "to be prepended" < text.txtconstrucciones no funcionan en bash; requieren zsh.
Anders Kaseorg el
32

Esta es una posibilidad:

(echo "to be prepended"; cat text.txt) > newfile.txt

Probablemente no pueda moverse fácilmente por un archivo intermedio.

Alternativas (pueden ser engorrosas con el escape de la carcasa):

sed -i '0,/^/s//to be prepended/' text.txt
0xC0000022L
fuente
La estrategia 1) es la más intuitiva de todas las respuestas aquí, creo. Y una ligera variación: cat <(echo "to be prepended") text.txt > newfile.txt . Ahora que lo pienso, no estoy seguro de que el mío esté relacionado, así que publicaré una respuesta por separado.
Sridhar Sarnobat
1
El primer método no preservará los permisos de archivo
Xlsx
La única forma de preservar los permisos es usar esponja
Sridhar Sarnobat
20

Esto funcionará para formar la salida. El - significa entrada estándar, que se proporciona a través de la tubería desde el eco.

echo -e "to be prepended \n another line" | cat - text.txt

Para reescribir el archivo, se requiere un archivo temporal ya que no se puede volver a conectar al archivo de entrada.

echo "to be prepended" | cat - text.txt > text.txt.tmp
mv text.txt.tmp text.txt
Adán
fuente
2
sin embargo, esto no antepone el texto al archivo text.txtcomo se solicitó, sino que lo muestra en stdout. La salida podría canalizarse a un archivo diferente si eso funciona para el OP
Levon
1
esto definitivamente imprimiría "to be prepended" y luego el contenido de "text.txt". Pero me gustaría que el texto se anteponga al archivo
One Two Three
1
¿Y si tengo texto de varias líneas? ¿Cómo coloco el carácter de nueva línea aquí?
One Two Three
Esto sería genial, pero a bash no le gusta: $ echo RewriteBase / | cat - web / .htaccess> web / .htaccess cat: web / .htaccess: el archivo de entrada es el archivo de salida
geek-merlin
14

Prefiero la respuesta de Adam

Podemos hacer que sea más fácil usar esponja . Ahora no necesitamos crear un archivo temporal y renombrarlo por

echo -e "to be prepended \n another line" | cat - text.txt | sponge text.txt
Ryan
fuente
Esta fue la única solución que funcionó para mí. Es bastante divertido que el nombre de mi servidor sea Bob Esponja.
Ömer Un
9

Si es aceptable reemplazar el archivo de entrada:

Nota:

  • Hacerlo puede tener efectos secundarios inesperados, que pueden reemplazar un enlace simbólico con un archivo normal, terminar con diferentes permisos en el archivo y cambiar la fecha de creación (nacimiento) del archivo.

  • sed -i, como en la respuesta del príncipe John Wesley , intenta al menos restaurar los permisos originales, pero también se aplican las otras limitaciones.

Aquí hay una alternativa simple que usa un archivo temporal (evita leer todo el archivo de entrada en la memoria como lo hace la solución de shime ):

{ printf 'to be prepended'; cat text.txt; } > tmp.txt && mv tmp.txt text.txt

Usar un comando de grupo ( { ...; ...; }) es un poco más eficiente que usar un subshell ( (...; ...)), como en la solución de 0xC0000022L .

Las ventajas son:

  • Es fácil controlar si el nuevo texto debe anteponerse directamente a la primera línea o si debe insertarse como nueva (s) línea (s) (simplemente agregue \nel printfargumento).

  • A diferencia de la sedsolución, funciona si el archivo de entrada está vacío ( 0bytes).


La sedsolución se puede simplificar si la intención es anteponer una o más líneas enteras al contenido existente (suponiendo que el archivo de entrada no esté vacío):

sedLa ifunción de inserta líneas enteras:

Con GNU sed :

# Prepends 'to be prepended' *followed by a newline*, i.e. inserts a new line.
# To prepend multiple lines, use '\n' as part of the text.
# -i.old creates a backup of the input file with extension '.old'
sed -i.old '1 i\to be prepended' inFile

Una variante portátil que también funciona con macOS / BSD sed:

# Prepends 'to be prepended' *followed by a newline*
# To prepend multiple lines, escape the ends of intermediate
# lines with '\'
sed -i.old -e '1 i\
to be prepended' inFile

Tenga en cuenta que la nueva línea literal después de la \es obligatoria.


Si el archivo de entrada debe editarse en su lugar (conservando su inodo con todos sus atributos):

Usando la venerable edutilidad POSIX :

Nota:

  • edinvariablemente lee el archivo de entrada como un todo en la memoria primero.

Para anteponer directamente a la primera línea (como con sed, esto no funcionará si el archivo de entrada está completamente vacío ( 0bytes)):

ed -s text.txt <<EOF
1 s/^/to be prepended/
w
EOF
  • -sedmensajes de estado suprimidos .
  • Observe cómo se proporcionan los comandos edcomo un documento de varias líneas aquí ( <<EOF\n...\nEOF), es decir, a través de stdin ; de manera predeterminada, la expansión de cadenas se realiza en dichos documentos (las variables de shell se interpolan); cita el delimitador de apertura para suprimir eso (por ejemplo, <<'EOF').
  • 1 convierte la primera línea en la línea actual
  • La función srealiza una sustitución de cadena basada en expresiones regulares en la línea actual, como en sed; puede incluir nuevas líneas literales en el texto de sustitución, pero deben ser \-escapeadas.
  • wvuelve a escribir el resultado en el archivo de entrada (para probar, reemplace wcon ,ppara imprimir solo el resultado, sin modificar el archivo de entrada).

Para anteponer una o más líneas enteras :

Al igual que con sed, la ifunción agrega invariablemente una nueva línea final al texto que se va a insertar.

ed -s text.txt <<EOF
0 i
line 1
line 2
.
w
EOF
  • 0 ihace 0(el comienzo del archivo) la línea actual y comienza el modo de inserción ( i); tenga en cuenta que los números de línea están 1basados ​​en otra cosa .
  • Las siguientes líneas son el texto a insertar antes de la línea actual, terminada .en su propia línea.
mklement0
fuente
7

Probablemente no tenga nada incorporado, pero podría escribir el suyo con bastante facilidad, así:

#!/bin/bash
echo -n "$1" > /tmp/tmpfile.$$
cat "$2" >> /tmp/tmpfile.$$
mv /tmp/tmpfile.$$ "$2"

Algo así al menos ...

Rob I
fuente
66
+1 por usar $$ (el PID actual) como parte del nombre de archivo temporal. Si crea un script para uso general, esto evitará que otro usuario pueda sobrescribir el archivo temporal ejecutando el mismo script al mismo tiempo.
E Brown
3
Sin $$embargo, el uso es insuficiente para el código de producción (ataque de enlace simbólico de Google); pero ciertamente es mejor que un nombre de archivo estático.
tripleee
1
solo usemktemp
mewa
7

En algunas circunstancias, el texto antepuesto puede estar disponible solo desde stdin. Entonces esta combinación funcionará.

echo "to be prepended" | cat - text.txt | tee text.txt

Si desea omitir la teesalida, agregue > /dev/null.

sehari24jam
fuente
Realmente me gusta esta respuesta. Es una forma muy limpia y clara de hacerlo. El uso de tee es una solución natural al problema de tratar de generar la salida al archivo de entrada. Tampoco hacks con cambio de nombre. Mejor que la solución más votada en este momento, ya que no crea un nuevo archivo. Esto es básicamente lo más cerca que estará >> de anteponer en lugar de agregar.
DC2
Esta es lejos la mejor y más limpia respuesta.
nerdoc
66
¿Hay alguna garantía que teeno golpee text.txtantes de catpoder leerlo? Creo que no, lo que haría que esta solución fuera peligrosa para la salud del archivo.
Jonathan Leffler
3
@JonathanLeffler probablemente no. Probé este código de prueba: lenA=1000000; yes a | head -c $lenA > a.txt; lenB=10000; b=$(yes b | head -c $lenB); echo "$b" | cat - a.txt | tee a.txt > /dev/null. Si lenAes 1000000 (archivo de 1Mb) y lenBes 10000 (antecedente de texto de 10Kb), el archivo "a.txt" se sobrescribe con 20Kb de letras "b". Esto está totalmente roto. Ahora, si usa texto de 1Mb a.txt y 1Mb para anteponer, teeentra en un bucle que genera un archivo de 7Gb +, tuve que detener el comando. Por lo tanto, es obvio que el resultado es impredecible para tamaños grandes. No tengo información sobre si debería funcionar en tamaños pequeños.
VasiliNovikov
3
Para ponerlo en términos conceptuales: este comando provocará la pérdida de datos si el archivo de entrada es más grande que el tamaño del búfer de la tubería de su sistema , que actualmente es de 64 KB.
mklement0
7

Solución:

printf '%s\n%s' 'text to prepend' "$(cat file.txt)" > file.txt

Tenga en cuenta que esto es seguro en todo tipo de entradas, porque no hay expansiones. Por ejemplo, si desea anteponer !@#$%^&*()ugly text\n\t\n, simplemente funcionará:

printf '%s\n%s' '!@#$%^&*()ugly text\n\t\n' "$(cat file.txt)" > file.txt

La última parte que queda por considerar es la eliminación de espacios en blanco al final del archivo durante la sustitución del comando "$(cat file.txt)". Todas las soluciones para esto son relativamente complejas. Si desea conservar las nuevas líneas al final del archivo.txt, consulte esto: https://stackoverflow.com/a/22607352/1091436

VasiliNovikov
fuente
Quiéralo. Súper portátil y no necesita generar un nuevo archivo. ¡Gracias!
pyb
1
El único problema con esto viene si el contenido de file.txtes más grande de lo que se puede incluir en la lista de argumentos printf.
Jonathan Leffler
6

Otra forma de usar sed:

sed -i.old '1 {i to be prepended
}' inFile

Si la línea a anteponer es multilínea:

sed -i.old '1 {i\ 
to be prepended\
multiline
}' inFile
Mert Nuhoglu
fuente
¿Por qué no sed -i '1ito be prepended' inFile, o solo está permitido para GNU sed?
Usuario desconocido
@userunknown: en el estándar sed, el icomando va seguido de una barra diagonal inversa y una nueva línea, y cada línea de entrada, excepto la última, también termina con una barra diagonal inversa. GNU sedpermite shorthands a lo largo de las líneas que usted pregunta, pero solo GNU sedlo hace '.
Jonathan Leffler
3

Según lo probado en Bash (en Ubuntu), si comienza con un archivo de prueba a través de;

echo "Original Line" > test_file.txt

puedes ejecutar;

echo "$(echo "New Line"; cat test_file.txt)" > test_file.txt

o, si la versión de bash es demasiado antigua para $ (), puede usar backticks;

echo "`echo "New Line"; cat test_file.txt`" > test_file.txt

y reciba los siguientes contenidos de "test_file.txt";

New Line
Original Line

Sin archivo intermediario, solo bash / echo.

AlexanderESmith
fuente
2
mejor respuesta, escribí esta línea para rotar mi archivo usando esta respuesta: para i en $ (<file2.txt); do echo "$ (echo $ i; cat file.txt)"> file.txt; hecho;
Aurangzeb
2

Otra solución bastante sencilla es:

    $ echo -e "string\n" $(cat file)
Palmadita
fuente
Esto funcionó para mí ... solo una ligera modificación, para mantener varias líneas del archivo de entrada, debe envolverlo entre comillas, así que algo como esto: echo -e "string \ n" "$ (cat ~ / file.txt) "> ~ / file.txt;
Craig Wayne
2
No tener la catexpansión del parámetro entre comillas dobles es una muy buena razón para un voto negativo . Este código está dividiendo el contenido del archivo en palabras y pasando cada una de esas palabras como un argumento separado para echo. Pierde los límites del argumento original, pierde líneas nuevas / pestañas / etc., y las cadenas como \ty \nen el texto original se reemplazan con pestañas y líneas nuevas en lugar de mantenerse como estaban.
Charles Duffy el
1

Si te gusta vi / vim, este puede ser más tu estilo.

printf '0i\n%s\n.\nwq\n' prepend-text | ed file
Ian Kelling
fuente
0
# create a file with content..
echo foo > /tmp/foo
# prepend a line containing "jim" to the file
sed -i "1s/^/jim\n/" /tmp/foo
# verify the content of the file has the new line prepened to it
cat /tmp/foo
Jim
fuente
0

Recomiendo definir una función y luego importarla y usarla cuando sea necesario.

prepend_to_file() { 
    file=$1
    text=$2

    if ! [[ -f $file ]] then
        touch $file
    fi

    echo "$text" | cat - $file > $file.new

    mv -f $file.new $file
}

Luego úsalo así:

prepend_to_file test.txt "This is first"
prepend_to_file test.txt "This is second"

El contenido de su archivo será:

This is second
This is first

Estoy a punto de utilizar este enfoque para implementar un actualizador de registro de cambios.

AwesomeBobX64
fuente