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.

Respostas:


72

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

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

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


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


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

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.


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

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+.


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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.