Função Bash para encontrar o padrão de correspondência de arquivo mais recente

141

No Bash, gostaria de criar uma função que retorne o nome do arquivo do arquivo mais recente que corresponde a um determinado padrão. Por exemplo, eu tenho um diretório de arquivos como:

Directory/
   a1.1_5_1
   a1.2_1_4
   b2.1_0
   b2.2_3_4
   b2.3_2_0

Eu quero o arquivo mais recente que começa com 'b2'. Como faço isso no bash? Eu preciso ter isso no meu ~/.bash_profilescript.

jlconlin
fonte
4
consulte superuser.com/questions/294161/… para obter mais dicas de resposta. A classificação é o passo fundamental para obter o seu arquivo mais recente
Wolfgang Fahl

Respostas:

229

O lscomando possui um parâmetro -tpara classificar por tempo. Você pode pegar o primeiro (mais novo) com head -1.

ls -t b2* | head -1

Mas cuidado: por que você não deve analisar a saída de ls

Minha opinião pessoal: a análise lssó é perigosa quando os nomes de arquivos podem conter caracteres engraçados, como espaços ou novas linhas. Se você pode garantir que os nomes dos arquivos não contenham caracteres engraçados, a análise lsé bastante segura.

Se você estiver desenvolvendo um script que deve ser executado por muitas pessoas em muitos sistemas em muitas situações diferentes, recomendo não analisar ls.

Aqui está como fazê-lo "certo": Como posso encontrar o arquivo mais recente (mais recente, antigo e antigo) em um diretório?

unset -v latest
for file in "$dir"/*; do
  [[ $file -nt $latest ]] && latest=$file
done
lesmana
fonte
8
Nota para outras pessoas: se você estiver fazendo isso para um diretório, adicione a opção -d ao ls, como este 'ls -td <pattern> | head -1 '
ken.ganong
5
O link LS de análise diz para não fazer isso e recomenda os métodos no BashFAQ 99 . Estou procurando um liner 1 em vez de algo à prova de bala para incluir em um script, então continuarei analisando ls insegura como @lesmana.
epónimo
1
@ Epônimo: Se você está procurando um forro sem usar o frágil ls, printf "%s\n" b2* | head -1fará isso por você.
David Ongaro
2
@DavidOngaro A questão não diz que os nomes de arquivos são números de versão. Trata-se de tempos de modificação. Mesmo com a suposição de nome de arquivo, b2.10_5_2esta solução é eliminada.
Eponymous
1
Seu perfil está me dando a resposta certa, mas o caminho "certo" está realmente me dando o arquivo mais antigo . Alguma idéia do porquê?
NewNameStat 04/04/19
15

A combinação finde lsfunciona bem para

  • nomes de arquivos sem novas linhas
  • quantidade não muito grande de arquivos
  • nomes de arquivos não muito longos

A solução:

find . -name "my-pattern" -print0 |
    xargs -r -0 ls -1 -t |
    head -1

Vamos dividir:

Com findnós podemos combinar todos os arquivos interessantes como este:

find . -name "my-pattern" ...

usando -print0, podemos passar todos os nomes de arquivos com segurança para o lsseguinte:

find . -name "my-pattern" -print0 | xargs -r -0 ls -1 -t

findparâmetros e padrões de pesquisa adicionais podem ser adicionados aqui

find . -name "my-pattern" ... -print0 | xargs -r -0 ls -1 -t

ls -tclassificará os arquivos pela hora da modificação (mais recentes primeiro) e os imprimirá um em uma linha. Você pode usar -cpara classificar por hora de criação. Nota : isso será interrompido com nomes de arquivos contendo novas linhas.

Finalmente, head -1obtemos o primeiro arquivo na lista classificada.

Nota: xargs use limites do sistema para o tamanho da lista de argumentos. Se esse tamanho exceder, xargschamará lsvárias vezes. Isso interromperá a classificação e provavelmente também a saída final. Corre

xargs  --show-limits

para verificar os limites do seu sistema.

Nota 2: use find . -maxdepth 1 -name "my-pattern" -print0se você não deseja pesquisar arquivos através de subpastas.

Nota 3: Como apontado pelo @starfry - o -rargumento for xargsestá impedindo a chamada de ls -1 -t, se nenhum arquivo foi correspondido pelo find. Obrigado pela sugestão.

Boris Brodski
fonte
2
Isso é melhor do que as soluções ls, pois trabalha para diretórios com muitos arquivos, onde ls engasga.
Marcin Zukowski
find . -name "my-pattern" ... -print0me dáfind: paths must precede expression: `...'
Jaakko
Oh! ...significa "mais parâmetros". Apenas o omita, se você não precisar.
Boris Brodski
2
Descobri que isso pode retornar um arquivo que não corresponde ao padrão se não houver arquivos que correspondam ao padrão. Isso acontece porque find não passa nada para o xargs, que chama ls sem listas de arquivos, fazendo com que funcione em todos os arquivos. A solução é adicionar -rà linha de comando xargs que diz ao xargs para não executar sua linha de comando se não receber nada em sua entrada padrão.
starfry
@starfry obrigado! Boa pegada. Eu adicionei -rà resposta.
Boris Brodski
7

Esta é uma possível implementação da função Bash necessária:

# Print the newest file, if any, matching the given pattern
# Example usage:
#   newest_matching_file 'b2*'
# WARNING: Files whose names begin with a dot will not be checked
function newest_matching_file
{
    # Use ${1-} instead of $1 in case 'nounset' is set
    local -r glob_pattern=${1-}

    if (( $# != 1 )) ; then
        echo 'usage: newest_matching_file GLOB_PATTERN' >&2
        return 1
    fi

    # To avoid printing garbage if no files match the pattern, set
    # 'nullglob' if necessary
    local -i need_to_unset_nullglob=0
    if [[ ":$BASHOPTS:" != *:nullglob:* ]] ; then
        shopt -s nullglob
        need_to_unset_nullglob=1
    fi

    newest_file=
    for file in $glob_pattern ; do
        [[ -z $newest_file || $file -nt $newest_file ]] \
            && newest_file=$file
    done

    # To avoid unexpected behaviour elsewhere, unset nullglob if it was
    # set by this function
    (( need_to_unset_nullglob )) && shopt -u nullglob

    # Use printf instead of echo in case the file name begins with '-'
    [[ -n $newest_file ]] && printf '%s\n' "$newest_file"

    return 0
}

Ele usa apenas os recursos internos do Bash e deve lidar com arquivos cujos nomes contenham novas linhas ou outros caracteres incomuns.

pjh
fonte
1
Você poderia usar nullglob_shopt=$(shopt -p nullglob)e depois $nullglobcolocar de volta nullglobcomo era antes.
precisa saber é o seguinte
A sugestão de @gniourf_gniourf para usar $ (shopt -p nullglob) é boa. Geralmente, tento evitar o uso de substituição de comando ( $()ou backticks) porque é lento, principalmente no Cygwin, mesmo quando o comando usa apenas recursos internos. Além disso, o contexto do subshell no qual os comandos são executados às vezes pode fazer com que eles se comportem de maneiras inesperadas. Também tento evitar o armazenamento de comandos em variáveis ​​(como nullglob_shopt) porque coisas muito ruins podem acontecer se você errar o valor da variável.
Pjh
Agradeço a atenção aos detalhes que podem levar a falhas obscuras quando ignorados. Obrigado!
Ron Burk
Adoro que você tenha adotado uma maneira mais exclusiva de resolver o problema! É certo que, no Unix / Linux, há mais de uma maneira de 'descascar cat!'. Mesmo que isso dê mais trabalho, tem o benefício de mostrar os conceitos das pessoas. Tenha um +1!
Pryftan #
3

Nomes de arquivos incomuns (como um arquivo que contém o \ncaractere válido podem causar estragos nesse tipo de análise. Aqui está uma maneira de fazer isso no Perl:

perl -le '@sorted = map {$_->[0]} 
                    sort {$a->[1] <=> $b->[1]} 
                    map {[$_, -M $_]} 
                    @ARGV;
          print $sorted[0]
' b2*

Essa é uma transformação schwartziana usada lá.

Glenn Jackman
fonte
1
Que o schwartz esteja com você!
Nathan Monteleone
esta resposta pode funcionar, mas eu não confiaria, dada a documentação insuficiente.
Wolfgang Fahl
1

Você pode usar statcom um arquivo glob e um decorate-sort-undecorate com o tempo do arquivo adicionado na frente:

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-
dawg
fonte
Não. "stat: não é possível ler as informações do sistema de arquivos para '% m% t% N': não existe esse arquivo ou diretório"
Ken Ingram
Eu acho que isso pode ser para a versão Mac / FreeBSD stat, se eu estiver lembrando suas opções corretamente. Para obter resultados semelhantes em outras plataformas, você pode usarstat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Jeffrey Cash
1

Encantamento de funções de magia negra para aqueles que desejam a find ... xargs ... head ...solução acima, mas em formato de função fácil de usar, para que você não precise pensar:

#define the function
find_newest_file_matching_pattern_under_directory(){
    echo $(find $1 -name $2 -print0 | xargs -0 ls -1 -t | head -1)
}

#setup:
#mkdir /tmp/files_to_move
#cd /tmp/files_to_move
#touch file1.txt
#touch file2.txt

#invoke the function:
newest_file=$( find_newest_file_matching_pattern_under_directory /tmp/files_to_move/ bc* )
echo $newest_file

Impressões:

file2.txt

Qual é:

O nome do arquivo com o registro de data e hora modificado mais antigo do arquivo no diretório especificado, que corresponde ao padrão especificado.

Eric Leschinski
fonte
1

Use o comando find.

Supondo que você esteja usando o Bash 4.2 ou superior, use -printf '%T+ %p\n'o valor do registro de data e hora do arquivo.

find $DIR -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

Exemplo:

find ~/Downloads -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

Para um script mais útil, consulte o script find-latest aqui: https://github.com/l3x/helpers

l3x
fonte
para trabalhar com nomes de arquivos que contêm espaços, altere cut -d '' -f2,3,4,5,6,7,8,9 ...
valodzka
0

Existe uma maneira muito mais eficiente de conseguir isso. Considere o seguinte comando:

find . -cmin 1 -name "b2*"

Este comando localiza o arquivo mais recente produzido exatamente há um minuto com a pesquisa curinga em "b2 *". Se você deseja arquivos dos últimos dois dias, será melhor usar o comando abaixo:

find . -mtime 2 -name "b2*"

O "." representa o diretório atual. Espero que isto ajude.

Naufal
fonte
9
Na verdade, ele não encontra o "padrão de correspondência de arquivos mais recente" ... apenas encontra todos os padrões de correspondência de arquivos criados um minuto atrás ou modificados dois dias atrás.
GnP 12/09
Esta resposta foi baseada na pergunta colocada. Além disso, você pode ajustar o comando para verificar o arquivo mais recente que chegou há um dia ou mais. Depende do que você está tentando fazer.
Naufal 12/09
"ajustar" não é a resposta. é como postar isso como resposta: "Basta ajustar o comando find e encontrar a resposta, dependendo do que você deseja fazer".
Kennet Celeste
Não tenho certeza sobre o comentário desnecessário. Se você acha que minha resposta não é fundamentada, forneça o motivo adequado para que minha resposta não faça sentido nos EXEMPLOS. Se não for possível, evite comentar mais.
Naufal
1
Sua solução exige que você saiba quando o arquivo mais recente foi criado. Isso não estava na pergunta, então não, sua resposta não se baseia na pergunta.
Bloke Down The Pub