web-dev-qa-db-fra.com

Comment puis-je obtenir la première correspondance de l'extension générique?

Des shells tels que Bash et Zsh développent des caractères génériques en arguments, autant d'arguments que le modèle:

$ echo *.txt
1.txt 2.txt 3.txt

Mais que se passe-t-il si je veux seulement que le premier match soit renvoyé, pas tous les matches?

$ echo *.txt
1.txt

Cela ne me dérange pas des solutions spécifiques à Shell, mais je voudrais une solution qui fonctionne avec des espaces dans les noms de fichiers.

44
Flimm

Une méthode robuste dans bash consiste à développer dans un tableau et à générer le premier élément uniquement:

pattern="*.txt"
files=( $pattern )
echo "${files[0]}"  # printf is safer!

(Vous pouvez même simplement echo $files, Un index manquant est traité comme [0].)

Cela gère en toute sécurité l'espace/tabulation/nouvelle ligne et d'autres métacaractères lors de l'expansion des noms de fichiers. Notez que les paramètres régionaux en vigueur peuvent modifier ce qu'est le "premier".

Vous pouvez également le faire de manière interactive avec une fonction de complétion bash :

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}   # string to expand

    if compgen -G "$cur*" > /dev/null; then
        local files=( ${cur:+$cur*} )   # don't expand empty input as *
        [ ${#files} -ge 1 ] && COMPREPLY=( "${files[0]}" )
    fi
}
complete -o bashdefault -F _echo echo

Cela lie la fonction _echo Pour terminer les arguments à la commande echo (en remplaçant l'achèvement normal). Un "*" supplémentaire est ajouté dans le code ci-dessus, vous pouvez simplement appuyer sur tab sur un nom de fichier partiel et j'espère que la bonne chose se produira.

Le code est légèrement compliqué, plutôt que défini ou supposé nullglob (shopt -s nullglob) Nous vérifions compgen -G peut étendre le glob à certaines correspondances, puis nous développons en toute sécurité dans un tableau, et définissons finalement COMPREPLY pour que la citation soit robuste.

Vous pouvez partiellement faire cela (étendre un glob par programme) avec compgen -G De bash, mais ce n'est pas robuste car il sort sans guillemet à stdout.

Comme d'habitude, l'achèvement est plutôt difficile, cela rompt l'achèvement d'autres choses, y compris les variables d'environnement (voir la fonction _bash_def_completion()ici pour les détails de l'émulation du comportement par défaut).

Vous pouvez également simplement utiliser compgen en dehors d'une fonction de complétion:

files=( $(compgen -W "$pattern") )

Un point à noter est que "~" n'est pas un glob, il est géré par bash dans une étape distincte de l'expansion, tout comme les variables $ et autres extensions. compgen -G Ne fait que remplacer le nom de fichier, mais compgen -W Vous donne toute l'expansion par défaut de bash, mais peut-être trop d'extensions (y compris `` Et $()). Contrairement à -G, Le -W est cité en toute sécurité (je ne peux pas expliquer la disparité). Étant donné que le but de -W Est d'étendre les jetons, cela signifie qu'il étendra "a" à "a" même si aucun fichier de ce type n'existe, donc ce n'est peut-être pas l'idéal.

Ceci est plus facile à comprendre, mais peut avoir des effets secondaires indésirables:

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    local files=( $(compgen -W "$cur") ) 
    printf -v COMPREPLY %q "${files[0]}"  
}

Alors:

touch $'curious \n filename'

echo curious*tab

Notez l'utilisation de printf %q Pour citer les valeurs en toute sécurité.

Une dernière option consiste à utiliser une sortie délimitée par 0 avec les utilitaires GNU (voir bash FAQ ):

pattern="*.txt"
while IFS= read -r -d $'\0' filename; do 
    printf '%q' "$filename"; 
    break; 
done < <(find . -maxdepth 1 -name "$pattern" -printf "%f\0" | sort -z )

Cette option vous donne un peu plus de contrôle sur l'ordre de tri (l'ordre lors de l'expansion d'un glob sera soumis à vos paramètres régionaux/LC_COLLATE Et peut ou peut ne pas plier la casse), mais est sinon un marteau assez grand pour une telle un petit problème ;-)

31
mr.spuratic

Dans zsh, utilisez le [1]qualificatif glob . Notez que même si ce cas spécial renvoie au plus une correspondance, il s'agit toujours d'une liste et les globes ne sont pas développés dans des contextes qui attendent un seul mot tel que des affectations (affectations de tableau mises à part).

echo *.txt([1])

Dans ksh ou bash, vous pouvez remplir toute la liste des correspondances dans un tableau et utiliser le premier élément.

tmp=(*.txt)
echo "${tmp[0]}"

Dans n'importe quel shell, vous pouvez définir les paramètres de position et utiliser le premier.

set -- *.txt
echo "$1"

Cela détruit les paramètres de position. Si vous ne le souhaitez pas, vous pouvez utiliser un sous-shell.

echo "$(set -- *.txt; echo "$1")"

Vous pouvez également utiliser une fonction, qui possède son propre ensemble de paramètres de position.

set_to_first () {
  eval "$1=\"\$2\""
}
set_to_first f *.txt
echo "$f"

Essayer:

for i in *.txt; do printf '%s\n' "$i"; break; done
1.txt

Remarque: l'expansion du nom de fichier est triée en fonction de la séquence de classement en vigueur dans les paramètres régionaux actuels.

6
cuonglm

Une solution simple:

sh -c 'echo "$1"' sh *.txt

Ou utilisez printf si vous préférez.

Je suis juste tombé sur cette vieille question en me demandant la même chose. Je me suis retrouvé avec ceci:

echo $(ls *.txt | head -n1)

Vous pouvez bien sûr remplacer head par tail et -n1 Par n'importe quel autre numéro.


Ce qui précède ne fonctionnera pas si vous travaillez avec des fichiers dont le nom contient des sauts de ligne. Pour travailler avec des sauts de ligne, vous pouvez utiliser n'importe lequel de ces éléments:

  • ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g'(Ne fonctionne pas sur BSD)
  • ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
  • ls -b *.txt | head -n1 | Perl -pe 's/\\n/\n/g'
  • echo -e "$(ls -b *.txt | head -n1)"(Fonctionne avec n'importe quel caractère spécial)
1
user149485