Como localizar e listar recursivamente os arquivos modificados mais recentes em um diretório com subdiretórios e horários?

417
  • Sistema operacional: Linux

  • Tipo de sistema de arquivos: ext3

  • Solução preferida: bash (script / oneliner), ruby, python

Eu tenho vários diretórios com vários subdiretórios e arquivos. Eu preciso fazer uma lista de todos esses diretórios que são construídos de forma que todos os diretórios de primeiro nível sejam listados ao lado da data e hora do último arquivo criado / modificado dentro dele.

Para esclarecer, se eu tocar em um arquivo ou modificar seu conteúdo alguns níveis de subdiretório, esse registro de data e hora deve ser exibido ao lado do nome do diretório de primeiro nível. Digamos que eu tenha um diretório estruturado assim:

./alfa/beta/gamma/example.txt

e eu modifico o conteúdo do arquivo example.txt, preciso que o horário seja exibido ao lado do diretório de primeiro nível alfaem formato legível por humanos, não em época. Eu tentei algumas coisas usando find, xargs, sorte os gostos, mas não posso contornar o problema que o timestamp sistema de arquivos de 'alfa' não muda quando eu criar / modificar arquivos de alguns níveis para baixo.

fredrik
fonte
Se você puder construí-lo, github.com/shadkam/recentmost pode ser usado.
user3392225
4
Incrível. 16 respostas, e a maioria / todos nem sequer tenta fazer o que o OP especificou ...
hmijail lamenta os demitidos
Em vez de soluções como uma opção -R, apenas vejo em massa aqui.
neverMind9
Deve ser um recurso nativo.
neverMind9

Respostas:

486

Tente este:

#!/bin/bash
find $1 -type f -exec stat --format '%Y :%y %n' "{}" \; | sort -nr | cut -d: -f2- | head

Execute-o com o caminho para o diretório em que deve começar a varredura recursivamente (ele suporta nomes de arquivos com espaços).

Se houver muitos arquivos, pode demorar um pouco antes de retornar qualquer coisa. O desempenho pode ser aprimorado se usarmos xargs:

#!/bin/bash
find $1 -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head

o que é um pouco mais rápido.

Heppo
fonte
132
Seu "método rápido" também deve ser capaz de usar print0 para suportar espaços e até linhas de alimentação nos nomes de arquivos. Aqui está o que eu uso: find $1 -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head Isso ainda consegue ser rápido para mim.
Dan
20
No Mac OS X, não é o stat do GNU, portanto, o comando falha. Você precisa brew install coreutilse usar em gstatvez destat
CharlesB 28/03
36
Você não precisa executar, statpois find PATH -type f -printf "%T@ %p\n"| sort -nrfaz o trabalho. Também é um pouco mais rápido assim.
nr
4
podemos transformar o comentário de @ user37078 em uma resposta real ou editar a resposta original? parece ser "o caminho certo" [tm] para fazê-lo.
mnagel
6
No Mac OS X, sem instalar gstat ou qualquer outra coisa, você pode fazer:find PATH -type f -exec stat -f "%m %N" "{}" \; | sort -nr | head
cobbzilla
198

Para encontrar todos os arquivos cujo status foi alterado pela última vez há N minutos:

find -cmin -N

por exemplo:

find -cmin -5

iman
fonte
4
+1 Obrigado, muito útil. Funciona no Windows usando o GnuWin32 find.
Sabuncu
muito conciso. muito agradável!
Randy L.
é mais rápido do que outras soluções mais complicado
david.perez
20
Muito bom, você também pode usar 'find -ctime -50', por exemplo, nos últimos 50 dias de alteração.
Gorkem 01/01
1
Para excluir a desordem, usesudo find -cmin -1 2>&1 |grep -v /proc/
Cees Timmerman
39

O GNU Find (consulte man find) possui um -printfparâmetro para exibir os arquivos EPOC mtime e o nome do caminho relativo.

redhat> find . -type f -printf '%T@ %P\n' | sort -n | awk '{print $2}'
user2570243
fonte
3
Obrigado! Esta é a única resposta que é rápida o suficiente para pesquisar minha estrutura de diretórios muito ampla em um tempo razoável. Eu passo a saída tailpara impedir que milhares de linhas sejam impressas na saída.
SFFC
8
Outro comentário: a awk '{print $2}'parte parece causar problemas quando há nomes de arquivos com espaços. Aqui está uma solução usando sedem vez disso, e também imprime o tempo, além do caminho:find . -type f -printf '%T@ %Tc %P\n' | sort -n | tail | sed -r 's/^.{22}//'
SFFC
3
Eu acho que deveria ser uma espécie -rn
Bojan Dević
2
A variante -printf é muito mais rápida do que chamar um processo 'stat' todas as vezes - reduziu horas de trabalho (s) de backup. Obrigado por me informar disso. Evitei o awk / sed, pois estou preocupado apenas com a última atualização dentro da árvore - então X = $ (find / path -type f -printf '% T% p \ n' | grep -v algo-I- don-tcare-sobre | tipo NR | cabeça -n 1) e um echo $ {X # *" "} funcionou bem para mim (dê-me encher até o primeiro espaço)
David Goodwin
2
Tudo não funcionará se o nome do arquivo estiver em várias linhas. Use touch "lala<Enter>b"para criar esse arquivo. Eu acho que o design de utilitários unix tem uma grande falha no nome do arquivo.
Fruit
35

Eu encurtei a resposta incrível da auréola para essa frase

stat --printf="%y %n\n" $(ls -tr $(find * -type f))

Atualizado : se houver espaços nos nomes de arquivos, você poderá usar esta modificação

OFS="$IFS";IFS=$'\n';stat --printf="%y %n\n" $(ls -tr $(find . -type f));IFS="$OFS";
slashdottir
fonte
Que tal isso: IFS = $ '\ n'; Stat --printf = "% y% n \ n" $ (ls -TR $ (encontrar -type f).)
slashdottir
3
Isso não funcionará se você tiver um número muito grande de arquivos. as respostas que usam xargs resolvem esse limite.
carl verbiest
@carlverbiest De fato, um grande número de arquivos quebrará a solução do slashdottir. Mesmo as soluções baseadas em xargs serão lentas. A solução de user2570243 é melhor para grandes sistemas de arquivos.
Stéphane Gourichon
IFS=$'\n'não é seguro em nenhum caso ao manipular nomes de arquivos: as novas linhas são caracteres válidos nos nomes de arquivos no UNIX. Apenas o caractere NUL é garantido para não estar presente em um caminho.
Charles Duffy
17

Tente isto

#!/bin/bash
stat --format %y $(ls -t $(find alfa/ -type f) | head -n 1)

Ele usa findpara reunir todos os arquivos do diretório, lslistá-los classificados por data de modificação, headselecionar o 1º arquivo e finalmente statmostrar a hora em um bom formato.

No momento, não é seguro para arquivos com espaço em branco ou outros caracteres especiais em seus nomes. Escreva um elogio se ele ainda não atender às suas necessidades.

Daniel Böhmer
fonte
1
auréola: eu gosto da sua resposta, ela funciona bem e imprime o arquivo correto. No entanto, não me ajuda, pois há muitos subníveis no meu caso. Então eu recebo "Lista de argumentos muito longa" para ls ... e xargs também não ajudaria neste caso. Vou tentar outra coisa.
Fredrik
Nesse caso, é um pouco mais complexo e precisará de algum programa real. Vou cortar um pouco de Perl.
Daniel Böhmer
1
Eu resolvi isso usando PHP. Uma função recursiva que desce pela árvore do sistema de arquivos e armazena a hora do arquivo modificado mais recentemente.
precisa
11

Este comando funciona no Mac OS X:

find "$1" -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head

No Linux, como o pôster original pediu, use em statvez de gstat.

Essa resposta é, obviamente, a excelente solução do user37078 , promovida do comentário à resposta completa. Eu misturei o insight de CharlesB para usar gstatno Mac OS X. Eu peguei coreutils do MacPorts em vez de homebrew , a propósito.

E aqui está como eu empacotei isso em um comando simples ~/bin/ls-recent.shpara reutilização:

#!/bin/bash
# ls-recent: list files in a dir tree, most recently modified first
#
# Usage: ls-recent path [-10 | more]
# 
# Where "path" is a path to target directory, "-10" is any arg to pass
# to "head" to limit the number of entries, and "more" is a special arg
# in place of "-10" which calls the pager "more" instead of "head".
if [ "more" = "$2" ]; then
   H=more; N=''
else
   H=head; N=$2
fi

find "$1" -type f -print0 |xargs -0 gstat --format '%Y :%y %n' \
    |sort -nr |cut -d: -f2- |$H $N
Jim DeLaHunt
fonte
2
No OS X yosemite; Eu recebo o erro: find: ftsopen: Não existe esse arquivo ou diretório
Reece
Interessante. Que comando você digitou (com parâmetros)? E quais eram os nomes dos arquivos nesse diretório? E se você criou sua própria versão ~/bin/ls-recent.sh, verificou cuidadosamente o script quanto a diferenças?
Jim DeLaHunt
10
para aqueles que não querem instalar nada no Mac OS X:find . -exec stat -f '%m%t%Sm %N' {} + | sort -n | cut -f2-
Jake
5

As soluções perl e Python desta postagem me ajudaram a resolver esse problema no Mac OS X: /unix/9247/how-to-list-files-sorted-by-modification-date-recursively -no-stat-command-avail .

Citando a partir da postagem:

Perl:

find . -type f -print |
perl -l -ne '
    $_{$_} = -M;  # store file age (mtime - now)
    END {
        $,="\n";
        print sort {$_{$b} <=> $_{$a}} keys %_;  # print by decreasing age
    }'

Pitão:

find . -type f -print |
python -c 'import os, sys; times = {}
for f in sys.stdin.readlines(): f = f[0:-1]; times[f] = os.stat(f).st_mtime
for f in sorted(times.iterkeys(), key=lambda f:times[f]): print f'
William Niu
fonte
5

Ignorando arquivos ocultos - com carimbo de tempo agradável e rápido

Lida bem com espaços nos nomes de arquivos - não que você deva usá-los!

$ find . -type f -not -path '*/\.*' -printf '%TY.%Tm.%Td %THh%TM %Ta %p\n' |sort -nr |head -n 10

2017.01.28 07h00 Sat ./recent
2017.01.21 10h49 Sat ./hgb
2017.01.16 07h44 Mon ./swx
2017.01.10 18h24 Tue ./update-stations
2017.01.09 10h38 Mon ./stations.json

Éfind possível encontrar mais abundância seguindo o link.

Serge Stroobandt
fonte
3

Estou mostrando isso para o tempo de acesso mais recente. Você pode modificá-lo facilmente para fazer o tempo de modificação mais recente.

Há duas maneiras de fazer isso:


1) Se você deseja evitar a classificação global, que pode ser cara se tiver dezenas de milhões de arquivos, você pode: (posicionar-se na raiz do diretório em que deseja iniciar sua pesquisa)

linux> touch -d @0 /tmp/a;
linux> find . -type f -exec tcsh -f -c test `stat --printf="%X" {}` -gt  `stat --printf="%X" /tmp/a`  ; -exec tcsh -f -c touch -a -r {} /tmp/a ; -print 

O método acima imprime nomes de arquivos com tempo de acesso progressivamente mais recente e o último arquivo impresso é o arquivo com o tempo de acesso mais recente. Obviamente, você pode obter o tempo de acesso mais recente usando um "tail -1".


2) Você pode encontrar recursivamente imprimir o nome, acessar o tempo de todos os arquivos em seu subdiretório e classificar com base no tempo de acesso e seguir a maior entrada:

linux> \find . -type f -exec stat --printf="%X  %n\n" {} \; | \sort -n | tail -1

E aí está ...

Sean
fonte
3

Eu tenho esse alias no meu perfil que uso com bastante frequência

$ alias | grep xlogs
xlogs='sudo find . \( -name "*.log" -o -name "*.trc" \) -mtime -1 | sudo xargs ls -ltr --color | less -R'

Então, ele faz o que você está procurando (com exceção de que não percorre vários níveis de alteração de data / hora) - procura os arquivos mais recentes (arquivos * .log e * .trc nesse caso); também encontra apenas arquivos modificados no último dia, depois classifica por tempo e canaliza a saída com menos:

sudo find . \( -name "*.log" -o -name "*.trc" \) -mtime -1 | sudo xargs ls -ltr --color | less -R

ps. Observe que eu não tenho root em alguns servidores, mas sempre tenho sudo, então você pode não precisar dessa parte.

Tagar
fonte
Como isso é "exatamente o que você está procurando"? O OP escreveu uma boa explicação sobre o que ele queria, e isso o ignora totalmente.
hmijail lamenta os renunciados em 4/11
obrigado por apontar para isso. você está correto - esse método não sobe vários níveis para obter a data / hora da alteração, apenas mostra a data / hora dos arquivos dos diretórios dentro dele. editei minha resposta.
Tagar
1

Você pode dar o comando printf de encontrar uma tentativa

% Ak última hora de acesso do arquivo no formato especificado por k, que é a @' or a directive for the C função strftime '. Os valores possíveis para k estão listados abaixo; alguns deles podem não estar disponíveis em todos os sistemas, devido a diferenças no `strftime 'entre os sistemas.

graugans
fonte
1

Função bash rápida:

# findLatestModifiedFiles(directory, [max=10, [format="%Td %Tb %TY, %TT"]])
function findLatestModifiedFiles() {
    local d="${1:-.}"
    local m="${2:-10}"
    local f="${3:-%Td %Tb %TY, %TT}"

    find "$d" -type f -printf "%T@ :$f %p\n" | sort -nr | cut -d: -f2- | head -n"$m"
}

Encontre o arquivo modificado mais recente em um diretório:

findLatestModifiedFiles "/home/jason/" 1

Você também pode especificar seu próprio formato de data / hora como o terceiro argumento.

Jason Larke
fonte
1

A seguir, você retorna uma sequência do carimbo de data e hora e o nome do arquivo com o carimbo de data e hora mais recente:

find $Directory -type f -printf "%TY-%Tm-%Td-%TH-%TM-%TS %p\n" | sed -r 's/([[:digit:]]{2})\.([[:digit:]]{2,})/\1-\2/' |     sort --field-separator='-' -nrk1 -nrk2 -nrk3 -nrk4 -nrk5 -nrk6 -nrk7 | head -n 1

Resultando em uma saída do formulário: <yy-mm-dd-hh-mm-ss.nanosec> <filename>

mark_infinite
fonte
1

Aqui está uma versão que funciona com nomes de arquivos que podem conter espaços, novas linhas, caracteres glob também:

find . -type f -printf "%T@ %p\0" | sort -zk1nr
  • find ... -printfimprime modificação de arquivo (valor EPOCH) seguido por um espaço e \0nomes de arquivos finalizados.
  • sort -zk1nr lê dados terminados em NUL e os classifica inversamente numericamente

Como a pergunta está marcada com o Linux, estou assumindo que os gnuutils estão disponíveis.

Você pode canalizar acima com:

xargs -0 printf "%s\n"

para imprimir a hora da modificação e os nomes dos arquivos classificados pela hora da modificação (mais recente primeiro) finalizada por novas linhas.

anubhava
fonte
1

Isto é o que eu estou usando (muito eficiente):

function find_last () { find "${1:-.}" -type f -printf '%TY-%Tm-%Td %TH:%TM %P\n' 2>/dev/null | sort | tail -n "${2:-10}" }

PROS:

  • gera apenas 3 processos

USO:

find_last [dir [number]]

Onde:

  • dir - um diretório a ser pesquisado [dir atual]
  • number - número de arquivos mais recentes a serem exibidos [10]

Saída para se find_last /etc 4parece com isso:

2019-07-09 12:12 cups/printers.conf
2019-07-09 14:20 salt/minion.d/_schedule.conf
2019-07-09 14:31 network/interfaces
2019-07-09 14:41 environment
Seweryn Niemiec
fonte
0

Para lssaída simples , use isso. Não há lista de argumentos, portanto, não pode demorar muito:

find . | while read FILE;do ls -d -l "$FILE";done

E agradecido cutapenas pelas datas, horas e nome:

find . | while read FILE;do ls -d -l "$FILE";done | cut --complement -d ' ' -f 1-5

EDIT : observei que a resposta principal atual é classificada por data de modificação. Isso é tão fácil com o segundo exemplo aqui, já que a data da modificação é a primeira em cada linha - coloque uma classificação no final:

find . | while read FILE;do ls -d -l "$FILE";done | cut --complement -d ' ' -f 1-5 | sort
Izkata
fonte
0

Isso também pode ser feito com uma função reccursiva no bash

Seja F uma função que exibe a hora do arquivo que deve ser ordenada lexicograficamente aaaa-mm-dd etc., (dependente do SO?)

F(){ stat --format %y "$1";}                # Linux
F(){ ls -E "$1"|awk '{print$6" "$7}';}      # SunOS: maybe this could be done easier

R a função recursiva executada pelos diretórios

R(){ local f;for f in "$1"/*;do [ -d "$f" ]&&R $f||F "$f";done;}

E finalmente

for f in *;do [ -d "$f" ]&&echo `R "$f"|sort|tail -1`" $f";done
Nahuel Fouilleul
fonte