Estou procurando um liner de shell para encontrar o arquivo mais antigo em uma árvore de diretórios.
Estou procurando um liner de shell para encontrar o arquivo mais antigo em uma árvore de diretórios.
Respostas:
Isso funciona (atualizado para incorporar a sugestão de Daniel Andersson):
find -type f -printf '%T+ %p\n' | sort | head -n 1
find
está vazia devido ao fato de eu ter o nome do arquivo que contém uma nova linha.
Este é um pouco mais portátil e, como não depende da find
extensã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_MAX
caracteres retornados (262.144 no meu sistema), ele não fornecerá o resultado correto. Também não é compatível com POSIX, porque -print0
e xargs -0
nã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
xargs: ls: terminated by signal 13
erro 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.
head
comando que sai quando lê uma linha e, portanto, "quebra" o pipe, eu acho. Você não recebe o erro porque sort
parece não reclamar, mas ls
ocorre no outro caso.
xargs
precisam ser chamados ls
mais de uma vez. Nesse caso, as saídas classificadas dessas múltiplas invocações acabam concatenadas quando devem ser mescladas.
ls
ele 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 ls
a ser invocada várias vezes. Você receberá a resposta errada, mas nunca saberá.
É 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 -z
opçã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 1
vez 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
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:
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 find
para observar o tempo de acesso (use %T
com o primeiro printf
para tempo de modificação ou %C
para 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}'
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 ls
soluções.
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.
find ! -type d -printf "%T@ %p\n" | sort -n | head -n1
sort -n
.
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+
.
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.
find -type f -printf '%T+ %p\n' | sort | head -1