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.
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 ;-)
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.
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)