Funzione Bash per trovare il modello di corrispondenza file più recente


141

In Bash, vorrei creare una funzione che restituisca il nome file del file più recente che corrisponda a un determinato modello. Ad esempio, ho una directory di file come:

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

Voglio il file più recente che inizia con "b2". Come posso farlo in bash? Devo averlo nella mia ~/.bash_profilesceneggiatura.


4
consultare superuser.com/questions/294161/… per ulteriori suggerimenti sulla risposta. L'ordinamento è il passaggio chiave per ottenere il tuo file più recente
Wolfgang Fahl il

Risposte:


229

Il lscomando ha un parametro -tper ordinare in base al tempo. È quindi possibile afferrare il primo (il più recente) con head -1.

ls -t b2* | head -1

Ma attenzione: perché non dovresti analizzare l'output di ls

La mia opinione personale: l'analisi lsè pericolosa solo quando i nomi dei file possono contenere personaggi divertenti come spazi o righe. Se puoi garantire che i nomi dei file non conterranno caratteri divertenti, l'analisi lsè abbastanza sicura.

Se stai sviluppando uno script che dovrebbe essere eseguito da molte persone su molti sistemi in molte situazioni diverse, ti consiglio vivamente di non analizzare ls.

Ecco come farlo "giusto": Come posso trovare il file più recente (più recente, più vecchio, più vecchio) in una directory?

unset -v latest
for file in "$dir"/*; do
  [[ $file -nt $latest ]] && latest=$file
done

8
Nota ad altri: se lo stai facendo per una directory, aggiungi l'opzione -d a ls, come questa 'ls -td <pattern> | testa -1 '
ken.ganong

5
Il link di analisi LS dice di non farlo e raccomanda i metodi in BashFAQ 99 . Sto cercando un 1-liner piuttosto che qualcosa a prova di proiettile da includere in una sceneggiatura, quindi continuerò a analizzare in modo non sicuro come @lesmana.
Eponimo

1
@Eponimo: se stai cercando una fodera senza usare il fragile ls, printf "%s\n" b2* | head -1lo farà per te.
David Ongaro,

2
@DavidOngaro La domanda non dice che i nomi dei file sono numeri di versione. Si tratta di tempi di modifica. Anche con l'assunzione del nome file b2.10_5_2uccide questa soluzione.
Eponimo

1
La tua unica linea mi sta dando la risposta giusta, ma il modo "giusto" in realtà mi sta dando il file più vecchio . Qualche idea sul perché?
NewNameStat

15

La combinazione di finde lsfunziona bene per

  • nomi di file senza newline
  • quantità non molto grande di file
  • nomi di file non molto lunghi

La soluzione:

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

Analizziamolo:

Con findpossiamo abbinare tutti i file interessanti come questo:

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

quindi utilizzando -print0possiamo passare tutti i nomi di file in modo sicuro a lsquesto:

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

findqui è possibile aggiungere ulteriori parametri e modelli di ricerca

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

ls -tordina i file in base al tempo di modifica (prima il più recente) e li stampa uno alla volta. È possibile utilizzare -cper ordinare in base al momento della creazione. Nota : questo si interromperà con i nomi di file contenenti newline.

Finalmente head -1ci ottiene il primo file nell'elenco ordinato.

Nota: xargs utilizzare i limiti di sistema per la dimensione dell'elenco degli argomenti. Se questa dimensione supera, xargschiamerà lspiù volte. Ciò interromperà l'ordinamento e probabilmente anche l'output finale. Correre

xargs  --show-limits

per controllare i limiti del tuo sistema.

Nota 2: utilizzare find . -maxdepth 1 -name "my-pattern" -print0se non si desidera cercare file nelle sottocartelle.

Nota 3: Come sottolineato da @starfry - l' -rargomento per xargssta impedendo la chiamata di ls -1 -t, se nessun file è stato trovato da find. Grazie per il suggerimento.


2
Questo è meglio delle soluzioni basate su LS, poiché funziona per directory con moltissimi file, dove soffoca.
Marcin Zukowski,

find . -name "my-pattern" ... -print0mi dàfind: paths must precede expression: `...'
Jaakko il

Oh! ...sta per "più parametri". Omettilo, se non ti serve.
Boris Brodski,

2
Ho scoperto che questo può restituire un file che non corrisponde al modello se non ci sono file che corrispondono al modello. Succede perché find non passa nulla a xargs che quindi invoca ls senza elenchi di file, facendolo funzionare su tutti i file. La soluzione è quella di aggiungere -ralla riga di comando xargs che dice a xargs di non eseguire la sua riga di comando se non riceve nulla sul suo input standard.
Starfry

@starfry grazie! Bella presa. Ho aggiunto -ralla risposta.
Boris Brodski,

7

Questa è una possibile implementazione della funzione Bash richiesta:

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

Utilizza solo i built-in di Bash e dovrebbe gestire i file i cui nomi contengono nuove righe o altri caratteri insoliti.


1
Potresti usare nullglob_shopt=$(shopt -p nullglob)e poi $nullglobper rimettere nullglobcom'era prima.
gniourf_gniourf

Il suggerimento di @gniourf_gniourf di usare $ (shopt -p nullglob) è buono. In genere cerco di evitare l'uso della sostituzione dei comandi ( $()o dei backtick) perché è lento, in particolare con Cygwin, anche quando il comando usa solo builtin. Inoltre, il contesto della subshell in cui vengono eseguiti i comandi può talvolta causare comportamenti inaspettati. Cerco anche di evitare di memorizzare i comandi in variabili (come nullglob_shopt) perché possono accadere cose molto brutte se il valore della variabile viene errato.
pjh,

Apprezzo l'attenzione ai dettagli che possono portare a oscuri fallimenti se trascurati. Grazie!
Ron Burk,

Adoro che tu abbia scelto un modo più unico per risolvere il problema! È una certezza che in Unix / Linux esiste più di un modo per "scuoiare il cat!". Anche se questo richiede più lavoro, ha il vantaggio di mostrare i concetti delle persone. Prendi un +1!
Pryftan,

3

Nomi di file insoliti (come un file contenente il \ncarattere valido possono creare scompiglio con questo tipo di analisi. Ecco un modo per farlo in Perl:

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

Questa è una trasformazione Schwartziana usata lì.


1
Possa lo schwartz essere con te!
Nathan Monteleone,

questa risposta potrebbe funzionare ma non mi fiderei, data la scarsa documentazione.
Wolfgang Fahl,

1

È possibile utilizzare statcon un file glob e un decora-ordina-undecorate con il tempo di file aggiunto nella parte anteriore:

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-

no. "stat: impossibile leggere le informazioni sul file system per '% m% t% N': nessun file o directory del genere"
Ken Ingram

Penso che questo potrebbe essere per la versione Mac / FreeBSD di stat, se ricordo bene le sue opzioni. Per ottenere un output simile su altre piattaforme, è possibile utilizzarestat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Jeffrey Cash

1

Incantesimo della funzione Magia oscura per coloro che desiderano la find ... xargs ... head ...soluzione sopra, ma in una forma di funzione facile da usare in modo da non dover pensare:

#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

stampe:

file2.txt

Che è:

Il nome file con il timestamp modificato più vecchio del file nella directory specificata che corrisponde al modello specificato.


1

Usa il comando trova.

Supponendo di utilizzare Bash 4.2+, utilizzare -printf '%T+ %p\n'per il valore data / ora del file.

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

Esempio:

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

Per uno script più utile, consultare lo script di ricerca più recente qui: https://github.com/l3x/helpers


per lavorare con nomi di file che contengono spazi change cut -d '' -f2,3,4,5,6,7,8,9 ...
valodzka

0

C'è un modo molto più efficiente per raggiungere questo obiettivo. Considera il seguente comando:

find . -cmin 1 -name "b2*"

Questo comando trova l'ultimo file prodotto esattamente un minuto fa con la ricerca jolly su "b2 *". Se vuoi i file degli ultimi due giorni, ti conviene usare il comando seguente:

find . -mtime 2 -name "b2*"

Il "." rappresenta la directory corrente. Spero che questo ti aiuti.


9
Questo in realtà non trova il "modello di corrispondenza file più recente" ... trova solo tutti i modelli di corrispondenza file creati un minuto fa o modificati due giorni fa.
GnP,

Questa risposta si basava sulla domanda posta. Inoltre, puoi modificare il comando per esaminare l'ultimo file che è arrivato circa un giorno fa. Dipende da cosa stai cercando di fare.
Naufal,

"tweaking" non è la risposta. è come postare questo come una risposta: "Basta modificare il comando find e trovare la risposta a seconda di ciò che si desidera fare".
Kennet Celeste,

Non sono sicuro del commento non necessario. Se ritieni che la mia risposta non sia comprovata, ti preghiamo di fornire una ragione adeguata per cui la mia risposta non ha senso con ESEMPI. Se non sei in grado di farlo, ti preghiamo di astenersi dal commentare ulteriormente.
Naufal,

1
La tua soluzione richiede di sapere quando è stato creato l'ultimo file. Non era nella domanda, quindi no, la tua risposta non si basa sulla domanda posta.
Bloke Down The Pub,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.