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

72

Estou procurando um liner de shell para encontrar o arquivo mais antigo em uma árvore de diretórios.

Marius Gedminas
fonte

Respostas:

72

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

find -type f -printf '%T+ %p\n' | sort | head -n 1
Marius Gedminas
fonte
8
Menos digitação:find -type f -printf '%T+ %p\n' | sort | head -1
Daniel Andersson
11
Eu recebo espaço vazio porque minha primeira linha findestá vazia devido ao fato de eu ter o nome do arquivo que contém uma nova linha.
林果皞
11
Posso perguntar se isso usa a data de criação ou modificação?
MrMesees
11
O Linux não armazena a data de criação do arquivo em nenhum lugar [*]. Isso usa a data da modificação. [*] isso não é verdade; ext4 armazena a data de criação inode, mas não é exposta através de quaisquer chamadas de sistema e você precisa usar debugfs para vê-lo).
Marius Gedminas
11

Este é um pouco mais portátil e, como não depende da findextensão GNU -printf, 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 limitado ao tamanho de ARG_MAX(o que deve ser irrelevante para a maioria dos kernels mais recentes). Portanto, se houver mais de getconf ARG_MAXcaracteres retornados (262.144 no meu sistema), ele não fornecerá o resultado correto. Também não é compatível com POSIX, porque -print0e xargs -0não é.

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

slhck
fonte
Isso funciona também, mas também emite um xargs: ls: terminated by signal 13erro como efeito colateral. Eu estou supondo que é SIGPIPE. Não sei por que não recebo um erro semelhante quando canalizo a saída da classificação para iniciar minha solução.
Marius Gedminas
Sua versão também é mais fácil de digitar da memória. :-)
Marius Gedminas
Sim, isso é um cano quebrado. Eu não entendo isso nas versões GNU e BSD de todos esses comandos, mas é o headcomando que sai quando lê uma linha e, portanto, "quebra" o pipe, eu acho. Você não recebe o erro porque sortparece não reclamar, mas lsocorre no outro caso.
slhck
4
Isso é interrompido se houver tantos nomes de arquivos que xargsprecisam ser chamados lsmais de uma vez. Nesse caso, as saídas classificadas dessas múltiplas invocações acabam concatenadas quando devem ser mescladas.
Nicole Hamilton
2
Eu acho que isso é pior do que postar um script que assume que os nomes de arquivos nunca contêm espaços. Na maioria das vezes, eles funcionam porque os nomes dos arquivos não têm espaços. E quando eles falham, você recebe um erro. Mas é improvável que isso funcione em casos reais e o fracasso será descoberto. Não em qualquer árvore de diretórios grande o suficiente para que você pode apenas lsele e globo ocular o arquivo mais antigo, a sua solução provavelmente será superado o limite de comprimento de linha de comando, fazendo com que lsa ser invocada várias vezes. Você receberá a resposta errada, mas nunca saberá.
Nicole Hamilton
11

É garantido que os seguintes comandos de comandos funcionam com qualquer tipo de nome de arquivo estranho:

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/[^ ]* //')"

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

A -zopção faz com que a classificação e o grep interpretem apenas bytes nulos como caracteres de final de linha. Como não existe essa opção de cabeçalho, usamos em grep -m 1vez disso (apenas uma ocorrência).

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

  • O primeiro comando será o mais lento, pois ele deve converter o mtime de todos os arquivos em um formato legível por humanos primeiro e depois classificar essas strings. Tubulação para gato evita colorir a saída.

  • O segundo comando é um pouco mais rápido. Enquanto ele ainda realiza a conversão da data, classificar numericamente ( sort -n) os segundos decorridos desde a época do Unix é um pouco mais rápido. sed exclui 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 próprio comando find não exibirá o mtime do arquivo mais antigo, portanto, o stat é necessário.

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

Dennis
fonte
5

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

Melhor seria se pudéssemos listá-las e acompanhar as mais antigas, sem a necessidade de classificar.

É por isso que eu vim com essa 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 ajudar, 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 todos os tempos.

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" / * lista todos os arquivos (*), formato longo (l), recursivamente (R), sem classificar (U) para ser rápido e conectá-lo ao awk
  • O Awk então COMEÇA zerando o contador (opcional para esta pergunta) e configurando a data mais antiga para hoje, no formato YearMonthDay.
  • O loop principal primeiro
    • Agarra o sexto campo, a data, formata Ano-Mês-Dia e altera-o para YearMonthDay (se o seu ls não for dessa maneira, talvez seja necessário ajustá-lo).
    • Utilizando recursivo, haverá linhas de cabeçalho para todos os diretórios, no formato / directory / here :. Pegue esta linha na variável pat. (substituindo o último ":" por um "/"). E define $ 6 em 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-o com a data antiga oldd.
    • É mais velho? Em seguida, salve os novos valores para data antiga oldd e old filename oldf. BTW, oldf não é apenas o 8º campo, mas do 8º ao final. É por isso que um loop para concatenar da 8ª para a NF (final).
    • Contar avanços em um
    • FINAL imprimindo o resultado

Executando:

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

Data mais antiga: 19691231

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

Total comparado: 111438

0m1.135s reais

usuário 0m0.872s

sys 0m0.760s


EDIT 2: Mesmo conceito, melhor solução usando findpara observar o tempo de acesso (use %Tcom o primeiro printfpara tempo de modificação ou %Cpara alteração 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 o tempo de modificação e também imprime o progresso incremental à medida que localiza arquivos mais antigos e mais antigos, o que é útil quando você tem alguns registros de data e hora 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}'
Dr Beco
fonte
Ele ainda precisa de ajustes para aceitar arquivos com espaços. Eu farei isso em breve.
Dr Beco
Eu acho que analisar ls para arquivos com espaços não é uma boa ideia. Talvez usando o find.
Dr Beco
Apenas execute-o em toda a árvore "/". Tempo gasto: Total comparação: 585.744 usuário real 2m14.017s 0m8.181s sys 0m8.473s
Dr Beco
O uso lsé ruim para scripts, pois sua saída não se destina a máquinas, a formatação da saída varia entre as implementações. Como você já declarou, findé bom para scripts, mas também pode ser bom adicionar essas informações antes de falar sobre as lssoluções.
Sampo Sarrala
4

Por favor, use ls - a página de manual informa como solicitar o diretório.

ls -clt | head -n 2

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

ls -t | head -n 1

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

ls -tr | head -n 1

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

user1363990
fonte
6
Isso funciona apenas se os arquivos estiverem em um único diretório, enquanto minha pergunta era sobre uma árvore de diretórios.
Marius Gedminas 02/09/2014
2
find ! -type d -printf "%T@ %p\n" | sort -n | head -n1
Okki
fonte
Isso não funcionará corretamente se houver arquivos com mais de 9 de setembro de 2001 (1000000000 segundos desde a época do Unix). Para habilitar a classificação numérica, use sort -n.
Dennis
Isso ajuda a me encontrar o arquivo, mas é difícil ver quantos anos ela tem sem executar um segundo comando :)
Marius Gedminas
0

Parece que por "mais antigo" a maioria das pessoas assumiu que você quis dizer "tempo de modificação mais antigo". Provavelmente isso foi corrigido, de acordo com a interpretação mais rigorosa de "mais antigo", mas, caso você desejasse aquele com o tempo de acesso mais antigo , modificaria a melhor resposta da seguinte maneira:

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

Observe o %A+.

PenguinLust
fonte
-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.
Dima
fonte
11
Bem-vindo ao Super Usuário! Embora isso possa responder à pergunta, seria uma resposta melhor se você pudesse fornecer alguma explicação para isso.
DavidPostill
11
Observe que várias pessoas também pediram explicações sobre sua resposta excluída anterior (idêntica).
DavidPostill
O que é difícil de responder? localize ./search/dirname -type f -printf '% T +% h /% f \ n' | classificar | head -n 1 Mostra duas colunas como a hora e o caminho do arquivo. É necessário remover a primeira coluna. Usando set e echo $ 2
Dima
11
Você deve fornecer explicações em vez de apenas colar uma linha de comando, conforme solicitado por vários outros usuários.
Ob1lan
11
Como isso é diferente da resposta aceita?
Ramhound