Como posso encontrar o arquivo mais antigo em uma árvore de diretórios

70

Eu estou procurando por um shell one-liner para encontrar o arquivo mais antigo em uma árvore de diretórios.

    
por Marius Gedminas 15.02.2013 / 17:06

8 respostas

72

Isso funciona (atualizado para incorporar a sugestão de Daniel Andersson):

find -type f -printf '%T+ %p\n' | sort | head -n 1
    
por 15.02.2013 / 17:13
11

Este é um pouco mais portátil e porque não depende do GNU find extension -printf , então também funciona no BSD / OS X:

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

A única desvantagem aqui é que é um pouco limitada ao tamanho de ARG_MAX (que deve ser irrelevante para a maioria dos novos kernels). Então, se houver mais de getconf ARG_MAX caracteres retornados (262.144 no meu sistema), isso não lhe dará o resultado correto. Também não é compatível com POSIX porque -print0 e xargs -0 não são.

Mais algumas soluções para esse problema estão descritas aqui: Como posso encontrar o arquivo mais recente (mais antigo, mais antigo) em um diretório ? - Wiki de Greg

    
por 15.02.2013 / 17:19
11

Os seguintes comandos de comando têm a garantia de funcionar com qualquer tipo de nome de arquivo estranho:

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

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

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

Usar um byte nulo ( \n ) em vez de um caractere de alimentação de linha ( -z ) garante que a saída de localização ainda seja compreensível caso um dos nomes de arquivo contenha um caractere de alimentação de linha.

A opção grep -m 1 faz a classificação e o grep interpretarem somente bytes nulos como caracteres de fim-de-linha. Como não há esse tipo de mudança, usamos sort -n (apenas uma ocorrência).

Os comandos são ordenados por tempo de execução (medido na minha máquina).

  • O primeiro comando será o mais lento, já que ele deve converter primeiro o arquivo mtime de cada arquivo em um formato legível por humanos e depois ordená-los. Piping to cat evita colorir a saída.

  • O segundo comando é um pouco mais rápido. Enquanto ele ainda realiza a conversão de data, numericamente ( %code% ) os segundos decorridos desde a época do Unix são um pouco mais rápidos. sed apaga os segundos desde a época do Unix.

  • O último comando não faz nenhuma conversão e deve ser significativamente mais rápido que os dois primeiros. O comando find em si não exibirá o mtime do arquivo mais antigo, portanto, stat é necessário.

Páginas de manual relacionadas: find - grep - sed - classificar - stat

    
por 16.02.2013 / 05:02
5

Embora a resposta aceita e outras pessoas façam o trabalho, se você tiver uma árvore muito grande, todas elas ordenarão todo o conjunto de arquivos.

Melhor seria se pudéssemos apenas listá-los e rastrear os mais antigos, sem a necessidade de classificar nada.

Foi por isso que surgiu esta solução 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 possa ser de alguma ajuda, mesmo que a pergunta seja um pouco antiga.

Editar 1: essas alterações permitem analisar arquivos e diretórios com espaços. É rápido o suficiente para emiti-lo na raiz / e encontrar o arquivo mais antigo de sempre.

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 - estilo-horário = long-iso "$ PWD" / * lista todos os arquivos (*), formato longo (l), recursivamente (R), sem classificação (U) para ser rápido e pipe para o awk
  • Awk, em seguida, BEGIN pelo contador de zeros (opcional para essa pergunta) e definindo a data mais antiga oldd como sendo hoje, formata YearMonthDay.
  • O loop principal primeiro
    • Pega no 6º campo, na data, no formato Ano-Mês-Dia e altera para YearMonthDay (se o seu ls não for produzido dessa maneira, talvez seja necessário ajustá-lo).
    • Usando recursiva, haverá linhas de cabeçalho para todos os diretórios, na forma de / directory / here :. Pegue esta linha na variável pat. (substituindo o último ":" por um "/"). E define $ 6 para nada para evitar o uso da linha de cabeçalho como uma linha de arquivo válida.
    • se o campo $ 6 tiver um número válido, é uma data. Compare com a data antiga oldd.
    • É mais velho? Em seguida, salve os novos valores para oldd oldd e old filename oldf. BTW, oldf não é apenas o oitavo campo, mas do oitavo ao final. É por isso que um loop para concatenar do 8º ao NF (final).
    • A contagem avança em um
    • END imprimindo o resultado

Executando:

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

Oldest date: 19691231

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

Total compared: 111438

real 0m1.135s

user 0m0.872s

sys 0m0.760s

EDIT 2: Mesmo conceito, melhor solução usando find para ver o tempo de acesso (use %T com o primeiro printf para tempo de modificação ou %C para mudança de status ).

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}'

EDIT 3: O comando abaixo usa tempo de modificação e também imprime progresso incremental conforme encontra arquivos antigos e antigos, o que é útil quando você tem alguns timestamps incorretos (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}'
    
por 19.06.2015 / 17:43
4

Por favor, use ls - a página man informa como encomendar o diretório.

ls -clt | head -n 2

O -n 2 é para que você não obtenha o "total" na saída. Se você quer apenas o nome do arquivo.

ls -t | head -n 1

E se você precisar da lista na ordem normal (obtendo o arquivo mais novo)

ls -tr | head -n 1

Muito mais fácil do que usar o find, muito mais rápido e mais robusto - não precisa se preocupar com formatos de nomenclatura de arquivos. Deve funcionar em quase todos os sistemas também.

    
por 07.08.2014 / 05:13
2
find ! -type d -printf "%T@ %p\n" | sort -n | head -n1
    
por 16.02.2013 / 02:37
0

Parece que por "mais antigos" a maioria das pessoas assumiu que você queria dizer "tempo de modificação mais antigo". Isso é provavelmente corrigido, de acordo com a interpretação mais rigorosa de "mais antigo", mas no caso de você querer aquele com o tempo de acesso mais antigo , eu modificaria a melhor resposta assim:

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

Observe o %A+ .

    
por 16.12.2016 / 18:57
-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 datas e nomes de arquivos em duas colunas.
  • sort | head -n1 mantém a linha correspondente ao arquivo mais antigo.
  • echo $2 exibe a segunda coluna, ou seja, o nome do arquivo.
por 08.06.2015 / 15:09