¿Cómo puedo encontrar el archivo más antiguo en un árbol de directorios?

72

Estoy buscando un shell de una línea para encontrar el archivo más antiguo en un árbol de directorios.

Marius Gedminas
fuente

Respuestas:

72

Esto funciona (actualizado para incorporar la sugerencia de Daniel Andersson):

find -type f -printf '%T+ %p\n' | sort | head -n 1
Marius Gedminas
fuente
8
Menos mecanografía:find -type f -printf '%T+ %p\n' | sort | head -1
Daniel Andersson
1
Obtengo espacio vacío porque mi primera línea de este findestá vacía debido al hecho de que el nombre del archivo contiene nueva línea.
林果 皞
1
¿Puedo preguntar si esto usa la fecha de creación o modificación?
MrMesees
1
Linux no almacena la fecha de creación del archivo en ningún lugar [*]. Esto usa la fecha de modificación. [*] esto en realidad no es cierto; ext4 almacena la fecha de creación del inodo, pero no se expone a través de ninguna llamada del sistema y debe usar debugfs para verlo.)
Marius Gedminas
11

Este es un poco más portátil y porque no depende de la findextensión GNU -printf, por lo que también funciona en BSD / OS X:

find . -type f -print0 | xargs -0 ls -ltr | head -n 1

El único inconveniente aquí es que está algo limitado al tamaño de ARG_MAX(que debería ser irrelevante para la mayoría de los núcleos más nuevos). Entonces, si hay más de getconf ARG_MAXcaracteres devueltos (262,144 en mi sistema), no le da el resultado correcto. Tampoco es compatible con POSIX porque -print0y xargs -0no lo es.

Aquí se describen algunas soluciones más para este problema: ¿Cómo puedo encontrar el archivo más reciente (más nuevo, más antiguo, más antiguo) en un directorio? - Wiki de Greg

slhck
fuente
Esto también funciona, pero también emite un xargs: ls: terminated by signal 13error como efecto secundario. Supongo que es SIGPIPE. No tengo idea de por qué no obtengo un error similar cuando canalizo la salida del tipo a la cabeza en mi solución.
Marius Gedminas
Su versión también es más fácil de escribir desde la memoria. :-)
Marius Gedminas
Sí, eso es una tubería rota. No entiendo esto con las versiones GNU y BSD de todos esos comandos, pero headcreo que es el comando que se cierra una vez que ha leído una línea y "rompe" la tubería. No obtiene el error porque sortno parece quejarse de ello, pero lssí en el otro caso.
slhck
44
Esto se rompe si hay tantos nombres de archivo que xargsnecesitan invocar lsmás de una vez. En ese caso, los resultados ordenados de esas invocaciones múltiples terminan concatenados cuando deberían fusionarse.
Nicole Hamilton
2
Creo que esto es peor que publicar un script que asume que los nombres de archivo nunca contienen espacios. Muchas veces, funcionarán porque los nombres de los archivos no tienen espacios. Y cuando fallan, obtienes un error. Pero es poco probable que esto funcione en casos reales y el fracaso quedará sin descubrir. En cualquier árbol de directorios lo suficientemente grande como para que no pueda lsverlo y mirar el archivo más antiguo, su solución probablemente superará el límite de longitud de la línea de comando, provocando lsque se invoque varias veces. Obtendrá la respuesta incorrecta, pero nunca lo sabrá.
Nicole Hamilton
11

Los siguientes comandos están garantizados para funcionar con cualquier tipo de nombre de archivo extraño:

find -type f -printf "%T+ %p\0" | sort -z | grep -zom 1 ".*" | cat

find -type f -printf "%T@ %T+ %p\0" | \
    sort -nz | grep -zom 1 ".*" | sed 's/[^ ]* //'

stat -c "%y %n" "$(find -type f -printf "%T@ %p\0" | \
    sort -nz | grep -zom 1 ".*" | sed 's/[^ ]* //')"

El uso de un byte nulo ( \0) en lugar de un carácter de salto de línea ( \n) asegura que la salida de find seguirá siendo comprensible en caso de que uno de los nombres de archivo contenga un carácter de salto de línea.

El -zconmutador hace que tanto sort como grep interpreten solo bytes nulos como caracteres de fin de línea. Como no hay tal interruptor para la cabeza, usamos en su grep -m 1lugar (solo una ocurrencia).

Los comandos están ordenados por tiempo de ejecución (medido en mi máquina).

  • El primer comando será el más lento, ya que primero tiene que convertir el mtime de cada archivo en un formato legible por humanos y luego ordenar esas cadenas. La tubería al gato evita colorear la salida.

  • El segundo comando es un poco más rápido. Si bien todavía realiza la conversión de fecha, la ordenación numérica ( sort -n) de los segundos transcurridos desde la época de Unix es un poco más rápida. sed elimina los segundos desde la época de Unix.

  • El último comando no realiza ninguna conversión y debería ser significativamente más rápido que los dos primeros. El comando find en sí no mostrará el tiempo m del archivo más antiguo, por lo que se necesita stat.

Páginas man relacionadas: find - grep - sed - sort - stat

Dennis
fuente
5

Aunque la respuesta aceptada y otros aquí hacen el trabajo, si tiene un árbol muy grande, todos ellos ordenarán todo el conjunto de archivos.

Mejor sería si pudiéramos enumerarlos y hacer un seguimiento de los más antiguos, sin la necesidad de ordenarlos.

Por eso se me ocurrió esta solución alternativa:

ls -lRU $PWD/* | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { gsub(/-/,"",$6); if (substr($1,0,1)=="/") { pat=substr($1,0,length($0)-1)"/"; }; if( $6 != "") {if ( $6 < oldd ) { oldd=$6; oldf=pat$8; }; print $6, pat$8; count++;}} END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'

Espero que pueda ser de ayuda, incluso si la pregunta es un poco vieja.


Edición 1: estos cambios permiten analizar archivos y directorios con espacios. Es lo suficientemente rápido como para emitirlo en la raíz /y encontrar el archivo más antiguo.

ls -lRU --time-style=long-iso "$PWD"/* | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { gsub(/-/,"",$6); if (substr($0,0,1)=="/") { pat=substr($0,0,length($0)-1)"/"; $6="" }; if( $6 ~ /^[0-9]+$/) {if ( $6 < oldd ) { oldd=$6; oldf=$8; for(i=9; i<=NF; i++) oldf=oldf $i; oldf=pat oldf; }; count++;}} END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'

Comando explicado:

  • ls -lRU --time-style = long-iso "$ PWD" / * enumera todos los archivos (*), formato largo (l), recursivamente (R), sin clasificar (U) para que sea rápido, y canalizarlo a awk
  • Awk y luego COMIENZO poniendo a cero el contador (opcional para esta pregunta) y configurando la fecha más antigua para que sea hoy, formatee YearMonthDay.
  • El bucle principal primero
    • Toma el sexto campo, la fecha, formatea Año-Mes-Día y cámbialo a YearMonthDay (si tu ls no sale de esta manera, es posible que tengas que ajustarlo).
    • Usando recursivo, habrá líneas de encabezado para todos los directorios, en forma de / directorio / aquí :. Toma esta línea en la variable pat. (sustituyendo el último ":" por un "/"). Y establece $ 6 en nada para evitar usar la línea de encabezado como una línea de archivo válida.
    • si el campo $ 6 tiene un número válido, es una fecha. Compárelo con la antigua fecha oldd.
    • ¿Es mayor? Luego guarde los nuevos valores para la fecha antigua oldd y el nombre antiguo de archivo oldf. Por cierto, oldf no es solo el octavo campo, sino del octavo hasta el final. Es por eso que un bucle para concatenar del 8 al NF (final).
    • Cuenta los avances por uno
    • FINALIZANDO imprimiendo el resultado

Ejecutándolo:

~ $ time ls -lRU "$ PWD" / * | awk etc.

Fecha más antigua: 19691231

Archivo: /home/.../.../backupold/.../EXAMPLES/how-to-program.txt

Total comparado: 111438

0m1.135s reales

usuario 0m0.872s

sys 0m0.760s


EDITAR 2: Mismo concepto, mejor solución findpara mirar el tiempo de acceso (use %Tcon el primero printfpara el tiempo de modificación o %Cpara el cambio de estado ).

find . -wholename "*" -type f -printf "%AY%Am%Ad %h/%f\n" | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { if ($1 < oldd) { oldd=$1; oldf=$2; for(i=3; i<=NF; i++) oldf=oldf " " $i; }; count++; } END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'

EDITAR 3: el siguiente comando utiliza el tiempo de modificación y también imprime el progreso incremental a medida que encuentra archivos antiguos y antiguos, lo cual es útil cuando tiene algunas marcas de tiempo incorrectas (como 1970-01-01):

find . -wholename "*" -type f -printf "%TY%Tm%Td %h/%f\n" | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { if ($1 < oldd) { oldd=$1; oldf=$2; for(i=3; i<=NF; i++) oldf=oldf " " $i; print oldd " " oldf; }; count++; } END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
Dr. Beco
fuente
Todavía necesita ajustes para aceptar archivos con espacios. Lo haré pronto.
Dr Beco
Creo que analizar ls para archivos con espacios no es una buena idea. Tal vez usando find.
Dr Beco
Simplemente ejecútelo en todo el árbol "/". Tiempo empleado: Total comparado: 585744 usuario real 2m14.017s 0m8.181s sys 0m8.473s
Dr Beco
El uso lses malo para las secuencias de comandos, ya que su salida no está destinada a máquinas, el formato de salida varía según las implementaciones. Como ya dijiste, findes bueno para las secuencias de comandos, pero también podría ser bueno agregar esa información antes de contar las lssoluciones.
Sampo Sarrala
4

Utilice ls: la página de manual le indica cómo ordenar el directorio.

ls -clt | head -n 2

El -n 2 es para que no obtenga el "total" en la salida. Si solo quieres el nombre del archivo.

ls -t | head -n 1

Y si necesita la lista en el orden normal (obtener el archivo más reciente)

ls -tr | head -n 1

Mucho más fácil que usar find, mucho más rápido y más robusto: no tiene que preocuparse por los formatos de nombres de archivos. También debería funcionar en casi todos los sistemas.

usuario1363990
fuente
66
Esto funciona solo si los archivos están en un solo directorio, mientras que mi pregunta era sobre un árbol de directorios.
Marius Gedminas
2
find ! -type d -printf "%T@ %p\n" | sort -n | head -n1
Okki
fuente
Esto no funcionará correctamente si hay archivos anteriores al 9 de septiembre de 2001 (1000000000 segundos desde la época de Unix). Para habilitar la ordenación numérica, use sort -n.
Dennis
Esto me ayuda a encontrar el archivo, pero es difícil ver qué edad tiene sin ejecutar un segundo comando :)
Marius Gedminas
0

Parece que por "más viejo" la mayoría de la gente ha asumido que se refería al "tiempo de modificación más antiguo". Probablemente se haya corregido, de acuerdo con la interpretación más estricta de "más antiguo", pero en caso de que desee el que tenga el tiempo de acceso más antiguo , modificaría la mejor respuesta de esta manera:

find -type f -printf '%A+ %p\n' | sort | head -n 1

Note el %A+.

PenguinLust
fuente
-1
set $(find /search/dirname -type f -printf '%T+ %h/%f\n' | sort | head -n 1) && echo $2
  • find ./search/dirname -type f -printf '%T+ %h/%f\n' Imprime fechas y nombres de archivos en dos columnas.
  • sort | head -n1 mantiene la línea correspondiente al archivo más antiguo.
  • echo $2 muestra la segunda columna, es decir, el nombre del archivo.
Dima
fuente
1
¡Bienvenido a Super User! Si bien esto puede responder la pregunta, sería una mejor respuesta si pudiera proporcionar alguna explicación de por qué lo hace.
DavidPostill
1
Tenga en cuenta que varias personas también pidieron alguna explicación de su respuesta eliminada anterior (idéntica).
DavidPostill
¿Qué es difícil de responder? buscar ./search/dirname -type f -printf '% T +% h /% f \ n' | ordenar | head -n 1 Muestra dos columnas como el tiempo y la ruta del archivo. Es necesario eliminar la primera columna. Usando set y echo $ 2
Dima
1
Debe proporcionar explicaciones en lugar de simplemente pegar una línea de comando, como lo solicitaron varios otros usuarios.
Ob1lan
1
¿Cómo es esto diferente a la respuesta aceptada?
Ramhound