2011-05-04 6 views
75

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

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

voglio che il file più recente che inizia con 'b2'. Come faccio a farlo in bash? Ho bisogno di avere questo nel mio script ~/.bash_profile.

+1

vedere http://superuser.com/questions/294161/unix-linux-find-and-sort-by-date-modified per ulteriori suggerimenti sulla risposta. L'ordinamento è il passaggio chiave per ottenere il tuo file più recente –

risposta

139

Il comando ls ha un parametro -t da ordinare in base al tempo. Puoi quindi prendere il primo (il più nuovo) con head -1.

ls -t b2* | head -1 

Ma attenzione: Why you shouldn't parse the output of ls

La mia opinione personale: l'analisi ls è pericoloso solo quando i nomi dei file possono contenere caratteri divertenti come spazi o nuove righe. Se è possibile garantire che i nomi dei file non contengano caratteri divertenti, l'analisi di ls è abbastanza sicura.

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

Ecco come farlo "giusto":. How can I find the latest (newest, earliest, oldest) file in a directory?

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

Nota per gli altri: se stai facendo questo per una directory, devi aggiungere l'opzione -d a ls, come questo 'ls -td | head -1 ' –

+4

Il collegamento [parsing LS] (http://mywiki.wooledge.org/ParsingLs) dice di non eseguire questa operazione e raccomanda i metodi in [BashFAQ 99] (http://mywiki.wooledge.org/BashFAQ/099). Sto cercando un 1-liner piuttosto che qualcosa a prova di proiettile da includere in una sceneggiatura, quindi continuerò a analizzare ls in modo ingiusto come @lesmana. – Eponymous

+0

@Eponymous: Se stai cercando un solo liner senza usare il fragile 'ls',' printf "% s \ n" b2 * | la testa -1' lo farà per te. –

2

nomi di file insoliti (come ad esempio un file contenente il \n carattere valido può devastare 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* 

Questo è un Schwartzian transform ha usato

+0

Possa lo schwartz essere con voi! –

+0

questa risposta potrebbe funzionare ma non mi fiderei di ciò data la scarsa documentazione. –

+1

scarsa documentazione? viene fornito con un wikipage ... –

4

Questa è una possibile implementazione del. necessaria funzione di Bash:

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

Si utilizza solo builtin Bash, e dovrebbe gestire i file i cui nomi contengono ritorni a capo o altri caratteri insoliti.

+0

Si potrebbe usare 'nullglob_shopt = $ (shopt -p nullglob)' e poi più tardi '$ nullglob' per rimettere' nullglob' com'era prima. –

+0

Il suggerimento di @gniourf_gniourf per usare $ (shopt -p nullglob) è buono. Generalmente cerco di evitare l'uso della sostituzione di comando ('$()' o backtick) perché è lento, in particolare con Cygwin, anche quando il comando utilizza solo i builtin. Inoltre, il contesto di subshell in cui i comandi vengono eseguiti a volte può causare il loro comportamento in modi imprevisti. Cerco anche di evitare di memorizzare comandi in variabili (come 'nullglob_shopt') perché possono accadere cose molto brutte se si ottiene il valore sbagliato della variabile. – pjh

+0

Apprezzo l'attenzione ai dettagli che possono portare a un oscuro fallimento quando trascurato. Grazie! –

2

C'è un modo molto più efficace per raggiungere questo obiettivo. Si consideri il seguente comando:

find . -cmin 1 -name "b2*" 

Questo comando trova il file più recente prodotto esattamente un minuto fa con la ricerca con caratteri jolly su "b2 *". Se si desiderano file degli ultimi due giorni, è preferibile utilizzare il comando seguente:

find . -mtime 2 -name "b2*" 

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

+5

Questo in realtà non trova il "modello di corrispondenza file più recente" ... trova solo tutti i file corrispondenti al modello creato un minuto fa o modificato due giorni fa. – GnP

+0

Questa risposta era basata sulla domanda posta. Inoltre, è possibile modificare il comando per guardare il file più recente che è venuto in un giorno fa. Dipende da cosa stai cercando di fare. – Naufal

2

La combinazione di find e ls funziona bene per

  • nomi di file senza ritorni a capo
  • non molto grande quantità di file nomi di file
  • non molto lunghi

La soluzione:

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

Diciamo scomposizione:

Con find Possiamo abbinare tutti i file interessanti come questo:

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

quindi utilizzando -print0 possiamo passare tutti i nomi dei file in modo sicuro alla ls in questo modo:

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

ls -t ordinerà i file dal tempo di modifica (prima il più recente) e stampalo uno su una linea. È possibile utilizzare -c per ordinare per ora di creazione. Nota: questo si interromperà con i nomi di file contenenti newline.

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

Nota:xargs utilizzare i limiti di sistema per la dimensione dell'elenco di argomenti. Se supera questa dimensione, xargs chiamerà più volte ls. Questo interromperà l'ordinamento e probabilmente anche l'output finale. Esegui

xargs --show-limits 

per controllare i limiti del sistema.

Problemi correlati