Des shells comme Bash et Zsh développent les caractères génériques en arguments, autant d'arguments que de motifs:
$ echo *.txt
1.txt 2.txt 3.txt
Mais que se passe-t-il si je veux seulement que le premier match soit retourné, pas tous les matchs?
$ echo *.txt
1.txt
Les solutions spécifiques au shell ne me dérangent pas, mais j'aimerais une solution qui fonctionne avec des espaces dans les noms de fichiers.
Réponses:
Une méthode efficace dans bash consiste à développer un tableau et à ne générer que le premier élément:
(Vous pouvez même simplement
echo $files
, un index manquant est traité comme [0].)Ceci gère en toute sécurité les espaces / tab / newline et autres métacaractères lors du développement des noms de fichiers. Notez que les paramètres régionaux en vigueur peuvent modifier le "premier".
Vous pouvez également le faire de manière interactive avec une fonction d'achèvement de bash :
Cela lie la
_echo
fonction pour compléter les arguments de laecho
commande (écrasant 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 passera.Le code est légèrement compliqué, plutôt que de définir ou de supposer
nullglob
(shopt -s nullglob
) nous vérifions que nouscompgen -G
pouvons étendre le glob à quelques correspondances, puis nous développons en toute sécurité dans un tableau, et définissons enfin COMPREPLY pour que la citation soit robuste.Vous pouvez faire ceci en partie (développer un glob par programme) avec les bash
compgen -G
, mais ce n'est pas robuste car il sort en sortie de stdout.Comme d'habitude, l'achèvement est plutôt complexe, cela rompt l'achèvement d'autres choses, y compris les variables d'environnement (voir la
_bash_def_completion()
fonction ici pour plus de détails sur l'émulation du comportement par défaut).Vous pouvez également simplement utiliser en
compgen
dehors d'une fonction d'achèvement:Un point à noter est que "~" n’est pas un glob, il est traité par bash dans une phase d’expansion distincte, comme le sont $ variables et d’autres expansions.
compgen -G
ne fait que balayer le nom de fichier, maiscompgen -W
vous donne toute l’extension par défaut de bash, bien qu’elle soit peut-être trop étendue (y compris``
et$()
). Contrairement à-G
, le-W
est cité en toute sécurité (je ne peux pas expliquer la disparité). Comme le but de-W
est de développer les jetons, cela signifie qu'il étendra "un" en "même" même si aucun fichier de ce type n'existe, ce n'est donc peut-être pas idéal.Ceci est plus facile à comprendre, mais peut avoir des effets secondaires indésirables:
Ensuite:
touch $'curious \n filename'
echo curious*
tabNotez l'utilisation de
printf %q
pour citer en toute sécurité les valeurs.Une dernière option consiste à utiliser une sortie délimitée par 0 avec les utilitaires GNU (voir la FAQ de bash ):
Cette option vous donne un peu plus de contrôle sur l’ordre de tri (l’ordre lors de l’extension d’un glob est sujet à votre locale /
LC_COLLATE
et peut se plier ou non à la casse), mais c’est plutôt un gros marteau pour un problème aussi petit ;-)la source
Dans zsh, utilisez le
[1]
qualificatif glob . Notez que même si ce cas spécial renvoie au maximum une correspondance, il reste une liste et les objets globaux ne sont pas développés dans des contextes qui attendent un mot unique, tel que assignations (affectations de tableau exceptées).En ksh ou bash, vous pouvez farcir la liste complète des correspondances dans un tableau et utiliser le premier élément.
Dans n'importe quel shell, vous pouvez définir les paramètres de position et utiliser le premier.
Cela écrase les paramètres de position. Si vous ne le souhaitez pas, vous pouvez utiliser un sous-shell.
Vous pouvez également utiliser une fonction qui possède son propre ensemble de paramètres de position.
la source
*.txt([1,n])
Essayer:
Remarque: le développement de nom de fichier est trié en fonction de la séquence de classement en vigueur dans les paramètres régionaux en cours.
la source
Une solution simple:
Ou utilisez
printf
si vous préférez.la source
Je suis juste tombé sur cette vieille question en me demandant la même chose. J'ai fini avec ceci:
echo $(ls *.txt | head -n1)
Vous pouvez bien sûr remplacer
head
partail
et-n1
avec tout autre numéro.Ce qui précède ne fonctionnera pas si vous travaillez avec des fichiers dont le nom contient des nouvelles lignes. Pour travailler avec de nouvelles lignes, vous pouvez utiliser l’un de ces types:
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)la source
Un cas d’utilisation que je rencontre souvent est la définition du répertoire supérieur / inférieur après une expansion globale (par exemple, un répertoire contenant des kits de développement logiciel (SDK) ou des outils de compilation). Dans cette situation, je souhaite généralement enregistrer ce nom de répertoire dans une variable à utiliser à quelques endroits dans un script shell.
Cette commande le fait généralement pour moi:
Avertissement: L'expansion de Glob ne va pas trier vos dossiers par semver; tu as été prévenu. C'est très bien si vous n'avez
Dockerfile
qu'une seule version d'un répertoire, mais cette version des répertoires peut varier d'une image à l'autrela source
mkdir "$(echo one; echo two)"
et voyez ce que je veux dire.tail
?dirname
ne prend qu'un seul chemin, vous ne pouvez donc pas compter sur plusieurs noms de chemins sans savoir que votre implémentation le supporte.