web-dev-qa-db-fra.com

Fonction Bash pour trouver le dernier modèle de correspondance de fichier

Dans Bash, j'aimerais créer une fonction qui renvoie le nom du fichier le plus récent correspondant à un modèle donné. Par exemple, j'ai un répertoire de fichiers comme:

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

Je veux le fichier le plus récent qui commence par «b2». Comment puis-je faire cela à bash? J'ai besoin de cela dans mon script ~/.bash_profile.

100
jlconlin

La commande ls a un paramètre -t pour trier par heure. Vous pouvez ensuite récupérer le premier (le plus récent) avec head -1.

ls -t b2* | head -1

Mais méfiez-vous: Pourquoi vous ne devriez pas analyser la sortie de ls

Mon opinion personnelle: analyser ls n'est dangereux que lorsque les noms de fichiers peuvent contenir des caractères amusants tels que des espaces ou des nouvelles lignes. Si vous pouvez garantir que les noms de fichiers ne contiendront pas de caractères amusants, alors l'analyse de ls sera relativement sûre.

Si vous développez un script destiné à être utilisé par de nombreuses personnes sur de nombreux systèmes et dans différentes situations, il est vivement conseillé de ne pas analyser ls.

Voici comment le faire "bien": Comment trouver le dernier fichier (le plus récent, le plus ancien, le plus ancien) dans un répertoire?

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

La combinaison de find et ls fonctionne bien pour

  • noms de fichiers sans nouvelles lignes
  • pas très grande quantité de fichiers
  • noms de fichiers pas très longs

La solution:

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

Décomposons:

Avec find, nous pouvons faire correspondre tous les fichiers intéressants comme ceci:

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

puis, en utilisant -print0, nous pouvons passer tous les noms de fichiers en toute sécurité à la ls comme ceci:

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

ls -t va trier les fichiers par date de modification (le plus récent en premier) et l’imprimer un par une. Vous pouvez utiliser -c pour trier par heure de création. Note : ceci rompra avec les noms de fichiers contenant des nouvelles lignes.

Finalement, head -1 nous obtient le premier fichier de la liste triée.

Remarque: xargs utilise les limites système relatives à la taille de la liste d'arguments. Si cette taille dépasse, xargs appellera ls plusieurs fois. Cela rompra le tri et probablement aussi le résultat final. Courir 

xargs  --show-limits

pour vérifier les limites sur votre système.

Remarque 2: utilisez find . -maxdepth 1 -name "my-pattern" -print0 si vous ne souhaitez pas rechercher de fichiers dans des sous-dossiers.

9
Boris Brodski

Ceci est une implémentation possible de la fonction Bash requise:

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

Il utilise uniquement les commandes intégrées Bash et doit gérer les fichiers dont les noms contiennent des nouvelles lignes ou d'autres caractères inhabituels.

4
pjh

Les noms de fichiers inhabituels (tels qu'un fichier contenant le caractère \n valide peuvent causer des dégâts avec ce type d'analyse. Voici un moyen de le faire en Perl:

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

C'est une transformation Schwartzian utilisée ici.

2
glenn jackman

Il existe un moyen beaucoup plus efficace d'y parvenir. Considérez la commande suivante:

find . -cmin 1 -name "b2*"

Cette commande trouve le dernier fichier créé il y a exactement une minute avec la recherche générique sur "b2 *". Si vous voulez des fichiers des deux derniers jours, vous ferez mieux d'utiliser la commande ci-dessous:

find . -mtime 2 -name "b2*"

Le "." représente le répertoire en cours. J'espère que cela t'aides.

1
Naufal

Incantation de fonction de magie noire pour ceux qui veulent la solution find ... xargs ... head ... ci-dessus, mais sous forme de fonction facile à utiliser afin que vous n'ayez pas à penser:

#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

Impressions:

file2.txt

Lequel est:

Le nom de fichier avec l'horodatage modifié le plus ancien du fichier dans le répertoire donné correspondant au modèle donné.

0
Eric Leschinski

Vous pouvez utiliser stat avec un fichier glob et un décorate-sort-undecorate avec l'heure du fichier ajoutée au premier plan:

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