L'expansion avec * .txt dans le shell ne fonctionne pas si aucun fichier .txt n'existe

10

Je jouais avec l'expansion et j'ai remarqué un comportement particulier. J'ai essayé de faire:

echo ./*.txt

Et je n'avais aucun fichier .txt dans mon répertoire actuel. La sortie que j'ai obtenue était:

./*.txt

Je suis juste curieux: pourquoi ai-je eu ça? Je m'attendais à ne pas obtenir de sortie.

PS: Quand j'avais un .txtfichier, l'expansion était correctement interprétée. En d'autres termes, disons que j'avais un fichier smthn.txt, l'écho a effectivement fait écho current_directory/smthn.txt.

Un vagabond ignorant
la source

Réponses:

15

Adaptation à partir de la page de manuel du shell bash,

bash recherche dans chaque mot les caractères *,? et [. Si l'un de ces caractères apparaît, le mot est considéré comme un modèle et remplacé par une liste alphabétique de noms de fichiers correspondant au modèle. Si aucun nom de fichier correspondant n'est trouvé et que l'option shell nullglob n'est pas activée, le mot reste inchangé. Si l'option nullglob est définie et qu'aucune correspondance n'est trouvée, le mot est supprimé.

Dans ce cas, je suppose que nullglob n'est pas activé, donc le mot reste inchangé - d'où la sortie que vous voyez.

ColinB
la source
2
Vous pouvez modifier le comportement comme suit: shopt -s nullglobproduira des chaînes vides pour des modèles inégalés et shopt -u nullglob(réglage standard) produira le modèle lui-même.
PerlDuck
13

Je m'attendais à ne pas obtenir de sortie.

Si nullglobc'était la valeur par défaut, de nombreuses commandes se comporteraient de manière assez inattendue, car il est (peut-être malheureusement) courant que les commandes traitent le cas de zéro argument de nom de fichier d'une manière qualitativement différente de celle d' un ou plusieurs arguments de nom de fichier.

Supposons que vous ayez activé nullglob( shopt -s nullglob) et que vous vous trouviez dans un répertoire où aucun fichier ne correspond *.txt. Ensuite, il *.txts'agira en fait de rien - pas un champ vide, mais pas de champs du tout - comme vous vous y attendiez. Mais cela aurait ces résultats:

  • ls *.txtrépertorierait tous les fichiers du répertoire actuel (à l'exception des fichiers masqués), car c'est ce qui se lsproduit lorsque vous ne lui passez aucun argument de nom de fichier.
  • cat *.txtlirait à partir de l'entrée standard , car lorsqu'il catn'a aucun argument de nom de fichier, c'est comme si vous couriez cat -. S'il s'exécute de manière interactive, il attend en attente d'entrée. De nombreuses commandes se comportent de cette façon.
  • cp *.txt dest/échouerait avec l'erreur cp: missing destination file operand after 'dest/'. Ce n'est pas un désastre, mais c'est déroutant et tout à fait différent du succès silencieux qui est probablement souhaité.
  • file *.txt, et divers autres programmes sans comportement spécial dans le cas d'arguments de nom de fichier zéro, échouaient toujours avec un message d'erreur ou d'utilisation quand aucun n'était transmis.
  • Même les cas qui se sentent intuitivement comme s'ils devraient travailler ne le feraient pas souvent. printf 'Got file: "%s"\n' *.txtimprimerait Got file: ""au lieu de rien.
  • Défaut accidentel de citer les événements de *, ?et [qui ne sont pas destinés à être élargi par le shell produirait plus souvent des résultats manifestement faux, mais d' une manière qui pourrait être difficile à comprendre. Par exemple, si aucun nom de fichier dans le répertoire courant ne commençait par gedit, alors apt list gedit*(où apt list 'gedit*'c'était prévu) deviendrait juste apt listet listerait tous les packages disponibles.

Il est donc bon que vous n'obteniez pas ce comportement sans le demander. La situation pratique la plus courante qui est en fait simplifiée nullglobest probablement for f in *.txt. Voir aussi cette question (à laquelle la réponse de Sergiy Kolodyazhnyy est liée).

La question la plus difficile à répondre est pourquoi - failgloboù c'est une erreur d'extension d'avoir un glob qui ne correspond à aucun fichier - n'est pas la valeur par défaut dans bash. Je crois que la réponse de Sergiy Kolodyazhnyy saisit la raison de cela même sans y répondre directement. Conserver des globes non expansifs sans produire une erreur d'expansion est (peut-être malheureusement) le comportement standardisé, et c'est aussi un comportement traditionnel, et donc attendu. Bien que bash n'essaie pas d'être entièrement compatible POSIX à moins qu'il ne soit appelé avec le nom shou transmis l' --posixoption, bon nombre de ses choix de conception même lorsqu'il n'est pas en mode POSIX suivent directement POSIX. Ils ont dû choisir un comportement et il y a des inconvénients à aller à l'encontre des attentes des utilisateurs.


Je pense que c'est l'aspect le moins influent historiquement de la question, donc je l'ai gardé pour la fin ... mais il convient de mentionner qu'il y a quelque chose de conceptuellement étrange dans le nullglobcomportement.

nullglobsemble élégant au premier abord parce que, syntaxiquement , il ne traite pas le cas des fichiers correspondant à zéro différemment du cas d'un, deux ou tout autre numéro. Les commandes que nous exécutons, pour lesquelles les globs se développent en arguments, n'ont pas tendance à les traiter de la même manière, comme détaillé ci-dessus. Mais syntaxiquement, cela semble au moins juste, ce qui, à mon avis, est la motivation de votre question.

Et pourtant, il y a une autre incohérence plus subtile qui nullglobne résout pas - qu'elle amplifie en fait. Le cas des caractères de globulation zéro ("caractères génériques") est traité différemment de celui d'un, de deux ou de tout autre nombre. Par exemple, avec shopt -s nullglob, si ab?d?fne correspond à aucun fichier, il est supprimé; s'il ab?dne correspond à aucun fichier, il est supprimé; mais s'il abne correspond à aucun fichier (c'est-à-dire s'il n'y a pas de fichier dont le nom est exactement ab), il n'est toujours pas supprimé. Bien sûr, ce serait un désastre s'il était supprimé, car il pourrait ne pas être destiné du tout à faire référence à un fichier existant dans le répertoire courant; il pourrait même ne pas faire référence à un fichier. Mais cela élimine toujours tout espoir de cohérence totale.

Les trois comportements fournis par bash - la valeur par défaut de traiter les globes qui ne correspondent à aucun fichier comme s'ils n'étaient pas des globes et de les transmettre non expansés, le comportement que vous attendiez de les traiter (si vous pardonnez cette étrange tournure de phrase) comme signifiant tout le zéro des fichiers qui correspondent ( nullglob), et le comportement sûr de les considérer comme des erreurs ( failglob) - tous représentent des approches différentes de l'ambiguïté inhérente au shell ne pouvant pas savoir si un mot particulier est destiné à être un nom de fichier. Le shell effectue ses extensions sans savoir comment les commandes particulières que vous appelez avec lui traiteront leurs arguments.

C'est l'un des nombreux exemples de séparation des préoccupations . Dans les systèmes dont la conception suit la philosophie Unix, chaque pièce est destinée à faire une chose et à bien la faire . Le shell traite le texte en commandes et arguments et appelle ces commandes, dont la plupart sont externes au shell lui-même. Cela a tendance à être beaucoup plus agréable et plus polyvalent que les systèmes où les commandes externes sont elles - mêmes responsables de l'exécution de ces transformations (comme avec les processeurs de commandes traditionnels sous DOS et Windows). Mais il a ses inconvénients occasionnels.

Eliah Kagan
la source
Apparemment, bash-4.3.39 (2) n'en a pas failglob. Il ne peut donc pas être par défaut car il n'a pas toujours été pris en charge.
Ruslan
6

La raison principale est que c'est un comportement standard spécifié par POSIX - la norme qui couvre le langage de commande du shell et entre autres la correspondance de modèles (shells tels que bash, dashshell - la valeur par défaut d'Ubuntu /bin/sh, et kshsuivez cette norme). De la section 2.13.3 Modèles utilisés pour l'extension du nom de fichier :

Si le modèle ne correspond à aucun nom de fichier ou chemin d'accès existant, la chaîne de modèle doit rester inchangée.

Bien sûr, cela a un effet secondaire - le nom de fichier correspondant peut être littéralement *.txt. L' nullgloboption dans bashet zshpeut aider: si cette option est activée via shopt -s nullglob(et si elle n'est pas activée par défaut qui s'applique à cette question), globstar sera étendu à une chaîne vide lorsqu'aucun nom de fichier correspondant ne sera trouvé. ksh93a son propre mécanisme avancé de mise en correspondance de motifs qui produit le même effet~(N)*.txt

Voir aussi Pourquoi nullglob n'est-il pas par défaut?

Sergiy Kolodyazhnyy
la source