Comment la redirection de file bash vers le standard diffère-t-elle du shell (`sh`) sous Linux?

J'ai écrit un script qui change d'user en cours d'exécution, et l'a exécuté en utilisant la redirection de file vers le standard. Donc, user-switch.sh est …

 #!/bin/bash whoami sudo su -l root whoami 

Et le courir avec bash me donne le comportement que j'attends

 $ bash < user-switch.sh vagrant root 

Cependant, si j'exécute le script avec sh , j'obtiens une sortie différente

 $ sh < user-switch.sh vagrant vagrant 

Pourquoi bash < user-switch.sh donne une sortie différente de celle de sh < user-switch.sh ?

Remarques:

  • arrive sur deux boîtes différentes exécutant Debian Jessie

Un script similaire, sans sudo , mais des résultats similaires:

 $ cat script.sh #!/bin/bash sed -e 's/^/--/' whoami $ bash < script.sh --whoami $ dash < script.sh itvirta 

Avec bash , le rest du script va en input à sed , avec un dash , la coquille l'interprète.

Running strace sur ceux-ci: dash lit un bloc du script (huit Ko ici, plus que suffisant pour contenir tout le script), puis génère sed :

 read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36 stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|... 

Ce qui signifie que le descripteur de file est à la fin du file, et sed ne verra aucune input. La partie restante étant tamponnée dans le dash . (Si le script était plus long que la taille de bloc de 8 Ko, la partie restante serait lue par sed .)

Bash, d'autre part, cherche à la fin de la dernière command:

 read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36 stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0 ... lseek(0, -7, SEEK_CUR) = 29 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|... 

Si l'input provient d'un tuyau, comme ici:

 $ cat script.sh | bash 

le rembobinage ne peut pas être effectué, car les tuyaux et les sockets ne sont pas recherchés. Dans ce cas, Bash revient à la lecture de l'input d' un caractère à la fois pour éviter de le surcharger. ( fd_to_buffered_stream() dans input.c ) Faire un appel système complet pour chaque octet n'est pas très efficace en principe. Dans la pratique, je ne pense pas que les lectures seront géniales, comparées par exemple au fait que la plupart des choses que le shell implique impliquent de nouveaux process entiers.

Une situation similaire est la suivante:

 echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1' 

Le sous-shell doit s'assurer que read ne lit que la première nouvelle ligne, de sorte que la head voit la ligne suivante. (Cela fonctionne aussi avec le dash de dash .)


En d'autres termes, Bash utilise des longueurs supplémentaires pour prendre en charge la lecture de la même source pour le script lui-même et pour les commands exécutées à partir de celui-ci. dash ne fait pas. Le zsh et le ksh93 empackageés dans Debian vont avec Bash là-dessus.

Le shell lit le script à partir de l'input standard. Dans le script, vous exécutez une command qui veut également lire l'input standard. Quelle input va aller où? Vous ne pouvez pas le dire de manière fiable .

La façon dont les shells fonctionnent est qu'ils lisent un morceau de code source, l'parsingnt et s'ils trouvent une command complète, exécutez la command, puis continuez avec le rest du morceau et le rest du file. Si le morceau ne contient pas une command complète (avec un caractère de fin à la fin – je pense que tous les shells lisent jusqu'à la fin d'une ligne), le shell lit un autre morceau, et ainsi de suite.

Si une command du script tente de lire à partir du même descripteur de file que le shell lit le script, la command finda tout ce qui vient après le dernier morceau lu. Cet location est imprévisible: il dépend de la taille de bloc choisie par le shell et peut dépendre non seulement du shell et de sa version, mais aussi de la configuration de la machine, de la memory disponible, etc.

Bash search la fin du code source d'une command dans le script avant d'exécuter la command. Ce n'est pas quelque chose sur lequel vous pouvez countr, non seulement parce que les autres shells ne le font pas, mais aussi parce que cela ne fonctionne que si le shell lit un file régulier. Si le shell lit à partir d'un tube (par exemple ssh remote-host.example.com <local-script-file.sh ), datatables lues sont lues et ne peuvent pas être lues.

Si vous souhaitez transmettre une input à une command du script, vous devez le faire explicitement, généralement avec un document ici . (Un document ici est généralement le plus pratique pour une input multiligne, mais n'importe quelle méthode le fera). Le code que vous avez écrit ne fonctionne que dans quelques shells, seulement si le script est passé en input au shell à partir d'un file normal; si vous vous whoami ce que le second whoami soit transmis en tant que consortingbution à sudo … , réfléchissez bien, en gardant à l'esprit que la plupart du time, le script n'est pas passé à l'input standard du shell.

 #!/bin/bash whoami sudo su -l root <<'EOF' whoami EOF 

Notez que cette décennie, vous pouvez utiliser sudo -i root . Running sudo su est un hack du passé.