Pregunta

Esto es probable 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?

¿Fue útil?

Solución

El cortar a tajos A continuación se muestra una respuesta rápida e improvisada que funcionó y recibió muchos votos a favor.Luego, a medida que la pregunta se hizo más popular y pasó más tiempo, la gente indignada comenzó a informar que en cierto modo funcionaba, pero que podían suceder cosas raras, o simplemente que no funcionaba en absoluto, por lo que fue rechazada 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 se debe confiar en él para nada que sea vagamente importante.

Ahora, con todo eso aclarado, la respuesta fue:


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

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

Otros consejos

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: INTENTO: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

Juan 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 sólo fallará en ese caso, sino que entrará en un bucle sin fin donde leerá su propia salida, haciendo así que el archivo crezca hasta llenar todo el espacio disponible.

Pruébelo usted mismo:

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

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

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

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

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

Puedes utilizar otras herramientas como ed o perl para hacerlo sin archivos temporales.

Puede que valga la pena señalar que a menudo es un buena idea para generar de forma segura el archivo temporal usando una utilidad como mktemp, al menos si el script alguna vez se ejecutará con privilegios de root.Por ejemplo, podrías hacer lo siguiente (nuevamente en bash):

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

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 heredoc de bash puedes 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 cat 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 ú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 yo sé, esto solo funciona en 'bash'.

Cuando empiece 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 scripting "adecuado" (Python/Perl/Ruby/etc.)

En cuanto a anteponer una línea a un archivo, no es posible hacerlo mediante tuberías, como cuando haces algo como cat blah.txt | grep something > blah.txt, sin darse cuenta deja el archivo en blanco.Hay un pequeño comando de utilidad llamado sponge puedes instalar (lo haces cat blah.txt | grep something | sponge blah.txt y almacena en buffer el contenido del archivo, luego lo escribe en el archivo).Es similar a un archivo temporal pero no es necesario hacerlo explícitamente.pero yo diría que es un requisito "peor" que, digamos, Perl.

Puede haber una manera de hacerlo a través de awk, o similar, pero si tiene que usar un script de shell, creo que un archivo temporal es, con diferencia, la forma más fácil (¿/única?).

EDITAR:Esto está descompuesto.Ver Comportamiento extraño al anteponer a un archivo con cat y tee

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

cat header main | tee main > /dev/null

Como sugiere Daniel Velkov, use camiseta.
Para mí, esa es una solución simple e inteligente:

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

El que uso yo.Éste le permite especificar el orden, caracteres adicionales, etc. de la forma que desee:

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

PD:Sólo que no funciona si los archivos contienen texto con barra invertida, porque se interpreta como caracteres de escape.

Principalmente por diversión/golf de conchas, pero

ex -c '0r myheader|x' myfile

funcionará y no hay canalizaciones ni redirecciones.Por supuesto, vi/ex no es realmente para uso no interactivo, por lo que vi parpadeará brevemente.

¿Por qué no utilizar simplemente el comando ed (como ya lo sugiere fluuffle aquí)?

¡ed lee el archivo completo en la memoria y realiza automáticamente una edición del 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

Otra solución alternativa sería utilizar identificadores de archivos abiertos como sugiere Jürgen Hötzel en Redirigir la salida 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 se podría poner en una sola línea, por supuesto.

Una variante de 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 ) 

Nuevamente, esto depende de la ejecución del subshell, el (..), para evitar que cat 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 así es).Eso podría solucionarse escribiendo su solución como:Echo "texto para prever" | CAT - file_to_be_modified | cat> tmp_file;mv tmp_file archivo_para_ser_modificado

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 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 de los espacios en blanco al leer un archivo en una cadena sustituta sed (p. ej.reemplace los caracteres de nueva línea con ' '.Comandos de shell vis y cat puede manejar caracteres no imprimibles, pero no espacios en blanco, por lo que esto no resolverá el problema del OP:

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

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

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

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

Con $(comando) puedes escribir la salida de un comando en una variable.Entonces lo hice con 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 cat soluciones de tuberías:

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

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

Pero en ese caso tienes que estar seguro de que no hay % en cualquier lugar, incluido el contenido del archivo de destino, ya que puede interpretarse y arruinar los resultados.

Puedes usar la línea de comando de Perl:

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

Donde -i crearé un reemplazo en línea del archivo y -0777 sorbirá todo el archivo y hará que solo coincida 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 del código en la sustitución.

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

donde "mi_archivo" es el archivo al que anteponer "mi_cadena".

me esta gustando @fluffle ed acercarse el mejor.Después de todo, los cambios de línea de comandos de cualquier herramienta frente a los comandos del editor con script son esencialmente lo mismo aquí;No veo que la "limpieza" de una solución de editor con script sea menor o cualquier otra cosa.

Aquí está mi frase adjunta a .git/hooks/prepare-commit-msg anteponer un in-repo .gitmessage archivo para confirmar 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 en 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 pongas una línea vacía encima de tu .gitmessage entonces, terminarás 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, yo descubierto 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

Si estás escribiendo un script en BASH, en realidad, puedes simplemente emitir:

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

En realidad, eso está en el ejemplo complejo que usted mismo publicó en su propia pregunta.

En mi humilde opinión, no existe una solución de shell (y nunca habrá 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 desea hacer eso sin recurrir a un archivo temporal (y sin permitir que el shell recurra silenciosamente a un archivo temporal, p. ej.a través de construcciones como exec 3<>myfile, tubería a tee, etc.

La solución "real" que está buscando necesita modificar el sistema de archivos, por lo que no está disponible en el espacio de usuario y dependería de la plataforma:está solicitando modificar el puntero del sistema de archivos en uso por myfile al valor actual del puntero del sistema de archivos para myheader y reemplazar 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 lo puede hacer alguien que no sea superusuario, y probablemente tampoco el superusuario...Juega con inodos, etc.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top