¿Cómo eliminar la última línea de todos los archivos de un directorio?

17

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?

ThehelfarisedPheonix
fuente
66
Que has intentado unix.stackexchange.com/help/how-to-ask : "Compartiendo su investigación ayuda a todos Cuéntanos lo que encontraste y por la que no cumplía con sus necesidades Esto demuestra que usted ha tomado el tiempo para tratar de ayudarse a sí mismo.. , nos salva de reiterar respuestas obvias y, sobre todo, ¡te ayuda a obtener una respuesta más específica y relevante! "
Patrick
¿Por qué la idea de que alguien accidentalmente aplicándolo a / etc tienen una calidad muy especial de la inducción encogerse :)
rackandboneman

Respuestas:

4

Si tiene acceso a vim, puede usar:

for file in ./*
do
  if [ -f "${file}" ]
  then
    vim -c '$d' -c "wq" "${file}"
  fi
done
R Sahu
fuente
16

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 -ipor GNU sedsignifica operar en su lugar y '$ d'ordena sedque elimine la última línea (lo que $significa último y lo que dsignifica eliminar).

StefanR
fuente
3
Esto arrojará errores (y no hará nada) si la carpeta contiene algo más que un archivo normal, por ejemplo, otra carpeta ...
Najib Idrissi
1
@StefanR Estás utilizando -ique 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 sedno te permiten poner ningún espacio en blanco entre el $y el d(o, en general, entre el patrón y el comando).
Zwol
1
@zwol Como escribí, esto dará como resultado un error , no una advertencia, y sed se dará por vencido una vez que llegue a ese archivo (al menos con la versión de sed que tengo). No se procesarán los siguientes archivos. Tirar los mensajes de error sería una mala idea ya que ni siquiera sabría que había sucedido! Con zsh se puede utilizar *(.)a glob para los archivos normales, no sé acerca de otras conchas.
Najib Idrissi
@NajibIdrissi Hmm, tienes razón. Eso me sorprende; Yo habría esperado que se quejan de la guía, pero luego pasar al siguiente archivo en la línea de comandos. De hecho, creo que voy a informar que como un error.
Zwol
@don_crissti También tengo GNU sed v4.3 ... No sé qué decirte, acabo de probar nuevamente. gist.github.com/nidrissi/66fad6be334234f5dbb41c539d84d61e
Najib Idrissi
11

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 encontrado
    • sed -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 1a find.

Najib Idrissi
fuente
1
Hmm, pero a diferencia de las otras respuestas, esto desciende a subdirectorios. (También, con las versiones actuales de findesto está escrito de manera más eficiente find $dir -type f -exec sed -i '$d' '{}' '+'.)
Zwol
@zwol Gracias, lo he agregado a la respuesta.
Najib Idrissi
-print0no está presente en el comando completo, ¿por qué ponerlo en la explicación?
Ruslan
1
Además, -depth 0no funciona (findutils 4.4.2), debería funcionar -maxdepth 1.
Ruslan
@Ruslan Tuve una primera versión donde usé xargs pero luego recordé -exec.
Najib Idrissi
9

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 tailimplementación, tail -n 1devolverán solo esos bytes adicionales (como GNU tail), o la última línea (delimitada correctamente) y esos bytes adicionales.

Stéphane Chazelas
fuente
necesitas un |wc -cen la tailllamada? (o a ${#length})
Jeff Schaller
@JeffSchaller. Ups wc -c estaba destinado de hecho. ${#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.
Stéphane Chazelas
6

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 des lo mismo $dya que edpor 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

don_crissti
fuente
¡Agradable! Si cambia [[]]a [], será totalmente compatible con POSIX. ( [[ ... ]]es un Bashismo.)
Comodín
@Wildcard - gracias, cambiado (aunque [[no es un bashism )
don_crissti
Un no-POSIX-ismo, debo decir. :)
Comodín
3

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 {} +

.txtSolo 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:

Comodín
fuente