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?
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?
Respuestas:
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
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
-
después cat
?
echo -n "text"
yourfile
es un enlace simbólico, esto no hará lo que desea.
echo '0a
your text here
.
w' | ed some_file
¡ed es el editor estándar! http://www.gnu.org/fun/jokes/ed.msg.html
0r header.file
echo -e '0a\nyour text here\n.\nw' | ed some_file
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.
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.
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; } )
Si necesita esto en las computadoras que controla, instale el paquete "moreutils" y use "esponja". Entonces puedes hacer:
cat header myfile | sponge myfile
{ echo "prepended text"; cat 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 redireccionamiento.
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'.
this is the header
en my.txt. Incluso después de actualizar Bash 4.3.42(1)-release
, obtengo el mismo resultado.
<(...)
) , por lo que no hay garantía de que my.txt
se lea por completo , sin lo cual esta técnica no funcionará.
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 sponge
que puede instalar (lo hace 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 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?).
Como sugiere Daniel Velkov, usa tee.
Para mí, esa es una solución inteligente simple:
{ echo foo; cat bar; } | tee bar > /dev/null
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
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
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.
¿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.
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
Esto es lo que descubrí:
echo -e "header \n$(cat file)" >file
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 vis
y cat
puede 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.
sed -i -e "1s/^/new first line\n/" old_file.txt
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.
echo
Parece una opción más segura. echo "my new line\n$(cat my/file.txt)" > my/file.txt
${new_line}
, no el archivo de destino
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.
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-msg
anteponer un .gitmessage
archivo 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 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 pongas una línea vacía encima de la tuya .gitmessage
, terminarás con dos líneas vacías.-s
suprime 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'
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á 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.
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.
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 myheader
y 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 myfile
el valor actual del puntero del sistema de archivos para myheader
y reemplazar en el sistema EOF
de archivos de myheader
con 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 .
mktemp
? Siempre puedes limpiar el archivo temporal después ...