GNU/Linux >> Tutoriels Linux >  >> Linux

Pourquoi un shell de connexion « sudo -i » casse-t-il un argument de chaîne de commande Here-doc ?

Dans la séquence de cinq commandes ci-dessous, toutes dépendent de guillemets simples pour transmettre une éventuelle substitution de variable au bash appelé shell plutôt que le shell appelant. L'utilisateur appelant est xx , mais le shell appelé sera exécuté en tant qu'utilisateur yy . La première commande remplace $HOME par la valeur du shell appelant car le shell appelé n'est pas un shell de connexion. La deuxième commande remplace la valeur de $HOME chargée par un shell de connexion, c'est donc la valeur appartenant à l'utilisateur yy . La troisième commande ne repose pas sur une valeur $HOME et crée un fichier dans le répertoire personnel deviné de l'utilisateur yy .

Pourquoi la quatrième commande échoue-t-elle ? L'intention est qu'il écrit le même fichier, mais en s'appuyant sur la variable $HOME appartenant à l'utilisateur yy pour s'assurer qu'il se retrouve bien dans son répertoire personnel. Je ne comprends pas pourquoi un shell de connexion rompt le comportement d'une commande here-doc transmise sous la forme d'une chaîne statique entre guillemets simples. L'échec de la cinquième commande vérifie que ce problème ne concerne pas la substitution de variable.

[email protected] ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
[email protected] ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
[email protected] ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
[email protected] ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
[email protected] ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')

Ces commandes ont été émises sur un système Linux Mint 18.3 Cinnamon 64 bits, basé sur Ubuntu 16.04 (Xenial Xerus).

Mise à jour : L'aspect ici-doc ne fait que brouiller le problème. Voici une simplification du problème :

$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2

Pourquoi la première de ces deux commandes conserve-t-elle le saut de ligne et pas la seconde ? sudo est commun aux deux commandes, mais semble s'échapper/filtrer/interpoler différemment en fonction de rien d'autre que de l'option "-i".

Réponse acceptée :

La documentation pour -i indique :

Le -i (simuler la connexion initiale) exécute le shell spécifié par l'entrée de la base de données de mots de passe de l'utilisateur cible en tant que shell de connexion. Cela signifie que les fichiers de ressources spécifiques à la connexion tels que .profile ou .login seront lus par le shell. Si une commande est spécifiée, elle est transmise au shell pour exécution via l'option -c du shell.

Autrement dit, il exécute véritablement le shell de connexion de l'utilisateur, puis transmet la commande que vous avez donnée sudo en utilisant -ccontrairement quoi sudo cmd arg arg fait généralement sans le -i option. Normalement, sudo utilise juste l'un des exec* fonctionne directement pour démarrer le processus lui-même, sans shell intermédiaire et tous les arguments sont passés exactement tels quels.

Avec -i , il configure l'environnement, exécute le shell de l'utilisateur en tant que shell de connexion et reconstruit la commande que vous avez demandé d'exécuter en tant qu'argument de bash -c . Dans votre cas, il exécute (disons, approximativement) /bin/bash -c "bash -c ' ... '" (imaginez une citation qui fonctionne).

Le problème réside dans la façon dont sudo transforme la commande que vous avez écrite en quelque chose que -c peut gérer , expliqué dans la section suivante. La dernière section présente quelques solutions possibles, et entre les deux se trouve une technique de débogage et de vérification,

Pourquoi cela se produit-il ?

Lors du passage de la commande à -c , il a besoin d'un prétraitement pour qu'il fasse ce qu'il faut, ce que sudo fait avant d'exécuter le shell. Par exemple, si votre commande est :

sudo -iu yy echo 'three   spaces'

alors ces espaces doivent être échappés pour que la commande signifie la même chose (c'est-à-dire pour que l'argument unique ne soit pas divisé en deux mots). Ce qui finit par être exécuté est :

/bin/bash -c 'echo three   spaces'

Commençons par votre commande simplifiée :

sudo bash -c 'echo 1
echo 2'

Dans ce cas, sudo change d'utilisateur, puis exécute execvp("bash", ["bash", "-c", "echo 1necho 2"]) (pour une syntaxe littérale de tableau inventée).

Connexe :Comment obtenir l'achèvement bash pour les alias de commande ?

Avec -i :

sudo -i bash -c 'echo 1
echo 2'

à la place, il change d'utilisateur, puis exécute execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"]) , où \ équivaut à un littéral et n est un saut de ligne. Il a échappé aux espaces et à la nouvelle ligne dans votre commande principale en les faisant précéder de barres obliques inverses.

C'est-à-dire qu'il existe un shell de connexion externe, qui a deux arguments :-c et votre commande entière, reconstruite sous une forme que le shell est censé comprendre correctement. Malheureusement, ce n'est pas le cas. Le bash interne commande essaie finalement de s'exécuter :

echo 1
echo 2

où la première ligne physique se termine par une continuation de ligne (barre oblique inverse suivie d'une nouvelle ligne), qui est entièrement supprimée. La ligne logique est alors juste echo 1echo 2 , qui ne fait pas ce que vous vouliez.

Il y a un argument selon lequel il s'agit d'une faille dans sudo étant donné le comportement standard des paires antislash-nouvelle ligne. Je pense qu'il devrait être prudent de les laisser sans échappée ici.

La même chose se produit pour votre commande avec un document ici. Il fonctionne comme, à peu près :

/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'

Linux
  1. Pourquoi Bashrc vérifie-t-il si le shell actuel est interactif ?

  2. Script de shell Linux :nombre hexadécimal en chaîne binaire

  3. Changer le shell par défaut sous Linux

  4. Que signifie la syntaxe |&en langage shell ?

  5. Pourquoi popen() invoque-t-il un shell pour exécuter un processus ?

Tutoriel de commande Linux chsh pour les débutants (5 exemples)

Pourquoi le $shlvl commence-t-il au niveau 2 dans les shells sans connexion mais au niveau 1 dans les shells de connexion dans Rhel 7 ?

Pourquoi la commande Linux wall ne diffuse-t-elle pas d'argument de chaîne ?

Pourquoi Ctrl + V ne colle-t-il pas dans Bash (shell Linux) ?

`ssh <host>` est un shell de connexion, mais `ssh <host> <command>` ne l'est pas ?

Pourquoi la commande sudo prend-elle longtemps à s'exécuter ?