Comprendre la substitution de commandes Read-a-File de Bash

11

J'essaie de comprendre comment Bash traite exactement la ligne suivante:

$(< "$FILE")

Selon la page de manuel de Bash, cela équivaut à:

$(cat "$FILE")

et je peux suivre le raisonnement de cette deuxième ligne. Bash effectue une expansion variable $FILE, entre la substitution de commandes, transmet la valeur de $FILEà cat, cat sort le contenu de $FILEla sortie standard, la substitution de commandes se termine en remplaçant la ligne entière par la sortie standard résultant de la commande à l'intérieur et Bash tente de l'exécuter comme une commande simple.

Cependant, pour la première ligne que j'ai mentionnée ci-dessus, je la comprends comme suit: Bash effectue la substitution de variables $FILE, Bash s'ouvre $FILEpour la lecture sur l'entrée standard, l'entrée standard est en quelque sorte copiée sur la sortie standard , la substitution de commandes se termine et Bash tente d'exécuter la norme résultante production.

Quelqu'un peut-il m'expliquer comment le contenu de $FILEstdin passe de stdout?

Stanley Yu
la source

Réponses:

-3

Le <n'est pas directement un aspect de la substitution de commandes bash . Il s'agit d'un opérateur de redirection (comme un tube), que certains shells autorisent sans commande (POSIX ne spécifie pas ce comportement).

Ce serait peut-être plus clair avec plus d'espaces:

echo $( < $FILE )

c'est effectivement * le même que le plus sûr pour POSIX

echo $( cat $FILE )

... ce qui est également efficace *

echo $( cat < $FILE )

Commençons par cette dernière version. Cela s'exécute catsans arguments, ce qui signifie qu'il lira à partir de l'entrée standard. $FILEest redirigé vers l'entrée standard en raison de <, donc catmet son contenu est mis dans la sortie standard. La $(command)substitution remplace ensuite catla sortie de 'en arguments pour echo.

Dans bash(mais pas dans la norme POSIX), vous pouvez utiliser <sans commande. bash(et zshet kshmais pas dash) interprètera cela comme si cat <, sans invoquer un nouveau sous-processus. Comme c'est natif du shell, c'est plus rapide que d'exécuter littéralement la commande externe cat. * C'est pourquoi je dis "effectivement le même que".

Adam Katz
la source
Donc, dans le dernier paragraphe, lorsque vous dites " bashinterprètera cela comme cat filename", voulez-vous dire que ce comportement est spécifique à la substitution de commandes? Parce que si je cours tout < filenameseul, bash ne s'en sort pas. Il ne sortira rien et me renverra à une invite.
Stanley Yu
Une commande est toujours nécessaire. @cuonglm modifié mon texte original de cat < filenamela cat filenameque je m'y oppose et peut revenir.
Adam Katz
1
Un tuyau est un type de fichier. L'opérateur de shell |crée un tube entre deux sous-processus (ou, avec certains shells, d'un sous-processus vers l'entrée standard du shell). L'opérateur shell $(…)crée un tube à partir d'un sous-processus vers le shell lui-même (et non vers son entrée standard). L'opérateur shell <n'implique pas de canal, il ouvre uniquement un fichier et déplace le descripteur de fichier vers l'entrée standard.
Gilles 'SO- arrête d'être méchant'
3
< filen'est pas le même que cat < file(sauf zshlà où c'est comme $READNULLCMD < file). < fileest parfaitement POSIX et s'ouvre juste filepour la lecture puis ne fait rien (il fileest donc tout de suite proche). C'est $(< file)ou `< file`c'est un opérateur spécial de ksh, zshet bash(et le comportement n'est pas spécifié dans POSIX). Voir ma réponse pour plus de détails.
Stéphane Chazelas
2
Pour mettre le commentaire de @ StéphaneChazelas sous un autre jour: en première approximation, ce $(cmd1) $(cmd2)sera typiquement le même que $(cmd1; cmd2). Mais regardez le cas où cmd2est < file. Si nous disons $(cmd1; < file), le fichier n'est pas lu, mais, avec $(cmd1) $(< file), il l'est. Il est donc incorrect de dire que $(< file)c'est juste un cas ordinaire $(command)avec une commande de < file.   $(< …)est un cas particulier de substitution de commandes et non une utilisation normale de la redirection.
Scott
14

$(<file)(fonctionne également avec `<file`) est un opérateur spécial du shell Korn copié par zshet bash. Cela ressemble beaucoup à la substitution de commandes, mais ce n'est pas vraiment le cas.

Dans les shells POSIX, une commande simple est:

< file var1=value1 > file2 cmd 2> file3 args 3> file4

Toutes les parties sont facultatives, vous pouvez avoir des redirections uniquement, des commandes uniquement, des affectations uniquement ou des combinaisons.

S'il y a des redirections mais pas de commande, les redirections sont effectuées (donc un > files'ouvrirait et tronquerait file), mais alors rien ne se passe. Donc

< file

Ouvre fileen lecture, mais rien ne se passe car il n'y a pas de commande. Donc, le fileest fermé et c'est tout. S'il $(< file)s'agissait d'une simple substitution de commande , elle se développerait à néant.

Dans la spécification POSIX , dans $(script), si se scriptcompose uniquement de redirections, cela produit des résultats non spécifiés . C'est pour permettre ce comportement spécial du shell Korn.

Dans ksh (ici testé avec ksh93u+), si le script se compose d'une et d'une seule commande simple (bien que les commentaires soient autorisés avant et après) qui ne se compose que de redirections (pas de commande, pas d'affectation) et si la première redirection est un stdin (fd 0) entrée uniquement ( <, <<ou <<<) redirection, donc:

  • $(< file)
  • $(0< file)
  • $(<&3)(également en $(0>&3)fait car c'est en fait le même opérateur)
  • $(< file > foo 2> $(whatever))

mais non:

  • $(> foo < file)
  • ni $(0<> file)
  • ni $(< file; sleep 1)
  • ni $(< file; < file2)

puis

  • tous sauf la première redirection sont ignorés (ils sont analysés)
  • et il s'étend au contenu du fichier / heredoc / herestring (ou tout ce qui peut être lu à partir du descripteur de fichier si vous utilisez des choses comme <&3) moins les caractères de fin de ligne.

comme si, $(cat < file)sauf que

  • la lecture se fait en interne par la coque et non par cat
  • aucun tuyau ni processus supplémentaire n'est impliqué
  • en conséquence de ce qui précède, puisque le code à l'intérieur n'est pas exécuté dans un sous-shell, toute modification reste par la suite (comme dans $(<${file=foo.txt})ou $(<file$((++n))))
  • les erreurs de lecture (mais pas les erreurs lors de l'ouverture de fichiers ou la duplication de descripteurs de fichiers) sont silencieusement ignorées.

Dans zsh, il est le même , sauf que ce comportement spécial est déclenché quand il ne n'y a qu'une seule redirection d'entrée de fichier ( <fileou 0< file, non <&3, <<<here, < a < b...)

Cependant, sauf lors de l'émulation d'autres shells, dans:

< file
<&3
<<< here...

c'est quand il n'y a que des redirections d'entrée sans commandes, en dehors de la substitution de commandes, zshexécute le $READNULLCMD(un pager par défaut), et quand il y a des redirections d'entrée et de sortie, le $NULLCMD( catpar défaut), donc même s'il $(<&3)n'est pas reconnu comme spécial , il fonctionnera toujours comme kshsi en appelant un pager pour le faire (ce pager agissant comme catpuisque sa sortie standard sera un pipe).

Cependant , alors que kshl » $(< a < b)élargiraient au contenu de a, dans zsh, il élargit le contenu de aet b(ou tout simplement bsi l' multiosoption est désactivée), $(< a > b)copiaient aà bet d' élargir à rien, etc.

bash a un opérateur similaire mais avec quelques différences:

  • les commentaires sont autorisés avant mais pas après:

    echo "$(
       # getting the content of file
       < file)"
    

    fonctionne mais:

    echo "$(< file
       # getting the content of file
    )"
    

    se développe à rien.

  • comme dans zsh, un seul fichier redirection stdin, bien qu'il n'y ait pas de retour à a $READNULLCMD, alors $(<&3), $(< a < b)effectuez les redirections mais ne développez rien.

  • pour une raison quelconque, bien qu'il bashn'invoque pas cat, il bifurque toujours un processus qui alimente le contenu du fichier via un tube, ce qui en fait beaucoup moins d'optimisation que dans d'autres shells. C'est en effet comme un $(cat < file)catserait un builtin cat.
  • en conséquence de ce qui précède, toute modification effectuée à l'intérieur est perdue par la suite (dans le $(<${file=foo.txt}), mentionné ci-dessus par exemple, cette $filecession est perdue par la suite).

Dans bash, IFS= read -rd '' var < file (fonctionne également dans zsh) est un moyen plus efficace de lire le contenu d'un fichier texte dans une variable. Il présente également l'avantage de conserver les caractères de nouvelle ligne de fin. Voir aussi $mapfile[file]dans zsh(dans le zsh/mapfilemodule et uniquement pour les fichiers normaux) qui fonctionne également avec les fichiers binaires.

Notez que les variantes basées sur pdksh kshont quelques variantes par rapport à ksh93. D'intérêt, dans mksh(un de ces coquilles dérivées de pdksh), dans

var=$(<<'EOF'
That's multi-line
test with *all* sorts of "special"
characters
EOF
)

est optimisé en ce que le contenu du document ici (sans les caractères de fin) est développé sans qu'un fichier temporaire ou un canal ne soit utilisé comme c'est le cas autrement pour les documents ici, ce qui en fait une syntaxe de citation multi-lignes efficace.

Pour être portable sur toutes les versions de ksh, zshet bash, le mieux est de se limiter à $(<file)éviter les commentaires et en gardant à l'esprit que les modifications des variables apportées à l'intérieur peuvent ou non être conservées.

Stéphane Chazelas
la source
Est-il exact qu'il $(<)s'agit d'un opérateur sur les noms de fichiers? Est <dans $(<)un opérateur de redirection, ou pas un opérateur seul, et doit faire partie de l'opérateur entier $(<)?
Tim
@Tim, peu importe comment vous voulez les appeler. $(<file)est destiné à s'étendre au contenu de filela même manière que le $(cat < file)ferait. La façon dont cela se fait varie d'un shell à l'autre, ce qui est décrit en détail dans la réponse. Si vous le souhaitez, vous pouvez dire que c'est un opérateur spécial qui est déclenché lorsque ce qui ressemble à une substitution de commande (syntaxiquement) contient ce qui ressemble à une redirection stdin unique (syntaxiquement), mais encore une fois avec des mises en garde et des variations en fonction du shell comme indiqué ici .
Stéphane Chazelas
@ StéphaneChazelas: Fascinant, comme d'habitude; Je l'ai mis en signet. Alors, n<&met n>&mfaire la même chose? Je ne le savais pas, mais je suppose que ce n'est pas trop surprenant.
Scott
@Scott, oui, ils font tous les deux un dup(m, n). Je peux voir des preuves que ksh86 utilise stdio et d'autres fdopen(fd, "r" or "w"), donc cela aurait pu avoir de l'importance à l'époque. Mais utiliser stdio dans un shell n'a pas beaucoup de sens, donc je ne m'attends pas à ce que vous trouviez un shell moderne où cela fera une différence. Une différence est que >&nc'est dup(n, 1)(court pour 1>&n), tandis que <&nest dup(n, 0)(court pour 0<&n).
Stéphane Chazelas
Droite. Sauf, bien sûr, que la forme à deux arguments de l'appel de duplication de descripteur de fichier est appelée dup2(); dup()ne prend qu'un seul argument et, comme open(), utilise le descripteur de fichier le plus bas disponible. (Aujourd'hui, j'ai appris qu'il y a une dup3()fonction .)
Scott
8

Parce bashque cela le fait en interne pour vous, développez le nom de fichier et transfère le fichier en sortie standard, comme si vous deviez le faire $(cat < filename). C'est une fonctionnalité bash, peut-être que vous devez regarder dans le bashcode source pour savoir exactement comment cela fonctionne.

Voici la fonction pour gérer cette fonctionnalité (à partir bashdu code source, du fichier builtins/evalstring.c):

/* Handle a $( < file ) command substitution.  This expands the filename,
   returning errors as appropriate, then just cats the file to the standard
   output. */
static int
cat_file (r)
     REDIRECT *r;
{
  char *fn;
  int fd, rval;

  if (r->instruction != r_input_direction)
    return -1;

  /* Get the filename. */
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing++;
  fn = redirection_expand (r->redirectee.filename);
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing--;

  if (fn == 0)
    {
      redirection_error (r, AMBIGUOUS_REDIRECT);
      return -1;
    }

  fd = open(fn, O_RDONLY);
  if (fd < 0)
    {
      file_error (fn);
      free (fn);
      return -1;
    }

  rval = zcatfd (fd, 1, fn);

  free (fn);
  close (fd);

  return (rval);
}

Une note qui $(<filename)n'est pas exactement équivalente à $(cat filename); ce dernier échouera si le nom de fichier commence par un tiret -.

$(<filename)était à l'origine de ksh, et a été ajouté à bashde Bash-2.02.

cuonglm
la source
1
cat filenameéchouera si le nom de fichier commence par un tiret car cat accepte les options. Vous pouvez contourner cela sur la plupart des systèmes modernes avec cat -- filename.
Adam Katz
-1

Considérez la substitution de commandes comme l'exécution d'une commande comme d'habitude et le vidage de la sortie au point où vous exécutez la commande.

La sortie des commandes peut être utilisée comme arguments d'une autre commande, pour définir une variable, et même pour générer la liste des arguments dans une boucle for.

foo=$(echo "bar")définira la valeur de la variable $foosur bar; la sortie de la commande echo bar.

Substitution de commande

iyrin
la source
1
Je pense qu'il ressort assez clairement de la question que le PO comprend les bases de la substitution de commandes; la question concerne le cas particulier de $(< file), et il n'a pas besoin d'un tutoriel sur le cas général. Si vous dites que $(< file)c'est juste un cas ordinaire $(command)avec une commande de < file, alors vous dites la même chose qu'Adam Katz et vous vous trompez tous les deux.
Scott