Tengo muchos archivos de texto en un directorio y quiero eliminar la última línea de cada archivo en el directorio.
¿Cómo puedo hacerlo?
Tengo muchos archivos de texto en un directorio y quiero eliminar la última línea de cada archivo en el directorio.
¿Cómo puedo hacerlo?
Respuestas:
Puedes usar este bonito oneliner si tienes GNU sed
.
sed -i '$ d' ./*
Eliminará la última línea de cada archivo no oculto en el directorio actual. Cambiar -i
por GNU sed
significa operar en su lugar y '$ d'
ordena sed
que elimine la última línea (lo que $
significa último y lo que d
significa eliminar).
-i
que es un GNUism, así que esto es discutible, pero perdería mi antigua posición de barba si no señalara que algunas versiones anteriores de sed
no te permiten poner ningún espacio en blanco entre el $
y el d
(o, en general, entre el patrón y el comando).
*(.)
a glob para los archivos normales, no sé acerca de otras conchas.
Todas las otras respuestas tienen problemas si el directorio contiene algo diferente a un archivo normal, o un archivo con espacios / líneas nuevas en el nombre del archivo. Aquí hay algo que funciona independientemente:
find "$dir" -type f -exec sed -i '$d' '{}' '+'
find "$dir" -type f
: Encontrar los archivos en el directorio $dir
-type f
que son archivos regulares;-exec
ejecutar el comando en cada archivo encontradosed -i
: Editar los archivos en su lugar;'$d'
: Eliminar ( d
) la última ( $
la línea).'+'
: le dice a find que continúe agregando argumentos a sed
(un poco más eficiente que ejecutar el comando para cada archivo por separado, gracias a @zwol).Si no desea descender en subdirectorios, a continuación, puede agregar el argumento -maxdepth 1
a find
.
find
esto está escrito de manera más eficiente find $dir -type f -exec sed -i '$d' '{}' '+'
.)
-print0
no está presente en el comando completo, ¿por qué ponerlo en la explicación?
-depth 0
no funciona (findutils 4.4.2), debería funcionar -maxdepth 1
.
-exec
.
Usar GNU sed -i '$d'
significa leer el archivo completo y hacer una copia sin la última línea, mientras que sería mucho más eficiente simplemente truncar el archivo en su lugar (al menos para archivos grandes).
Con GNU truncate
, puedes hacer:
for file in ./*; do
[ -f "$file" ] &&
length=$(tail -n 1 "$file" | wc -c) &&
[ "$length" -gt 0 ] &&
truncate -s "-$length" "$file"
done
Si los archivos son relativamente pequeños, eso probablemente sería menos eficiente, ya que ejecuta varios comandos por archivo.
Tenga en cuenta que para los archivos que contienen bytes adicionales después del último carácter de nueva línea (después de la última línea) o, en otras palabras, si tienen una última línea no delimitada , entonces, dependiendo de la tail
implementación, tail -n 1
devolverán solo esos bytes adicionales (como GNU tail
), o la última línea (delimitada correctamente) y esos bytes adicionales.
|wc -c
en la tail
llamada? (o a ${#length}
)
${#length}
no funcionaría ya que cuenta caracteres, no bytes, y $(...)
eliminaría el carácter de nueva línea de cola, por ${#...}
lo que estaría desactivado por uno, incluso si todos los caracteres fueran de un solo byte.
Un enfoque más portátil:
for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done
No creo que esto necesite ninguna explicación ... excepto tal vez que en este caso d
es lo mismo $d
ya que ed
por defecto selecciona la última línea.
Esto no buscará recursivamente y no procesará archivos ocultos (también conocidos como archivos de puntos).
Si desea editarlos también, vea Cómo hacer coincidir * con archivos ocultos dentro de un directorio
[[]]
a []
, será totalmente compatible con POSIX. ( [[ ... ]]
es un Bashismo.)
[[
no es un bashism )
One-liner compatible con POSIX para todos los archivos que comienzan recursivamente en el directorio actual, incluidos los archivos de puntos:
find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
.txt
Solo para archivos, de forma no recursiva:
find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Ver también: