web-dev-qa-db-fra.com

Pourquoi les variables telles que $ PS1 ne sont-elles pas dans printenv?

D'après ce que je peux dire, printenv affiche les variables d'environnement, mais pourquoi ne vois-je pas d'autres variables comme PS1 pour personnaliser l'invite de shell?

En quoi consiste exactement printenv et pourquoi ne pas capter PS1? Existe-t-il une commande de sortie plus complète faisant plus que printenv?

6
AJJ

En effet, PS1 n'est pas normalement exporté.

Les variables d'environnement sont utilisées pour définir l'environnement d'exécution des processus enfants. puisque PS1 n'a réellement de signification que dans un shell interactif, il n'est normalement pas importé de l'exporter - il ne s'agit que d'une simple variable shell .

Si vous démarrez un enfant interactif Shell , il lira et définira son PS1 à partir du fichier de ressources du shell, tel que ~/.bashrc.

Si vous export PS1, vous le verrez dans la sortie printenv. Sinon, vous pouvez voir des variables Shell simples à l’aide de la bash intégrée set comme décrit ici Comment lister tous les noms de variables et leurs valeurs actuelles?

6
steeldriver

Existe-t-il une commande de sortie plus complète faisant plus que printenv?

printenv imprime seulement variables d’environnement , ce qui peut être considéré comme un avantage. Mais si vous souhaitez également imprimer des variables Shell, utilisez echo "$x" (ou printf '%s\n' "$x", qui est plus robuste ) au lieu de printenv x.

l'explication de steeldriver de ces questions est utile et correcte, mais je présente le sujet d'une autre manière ici.

printenv est une commande externe - non intégrée à votre shell, mais un programme distinct de celui-ci. Il affiche ses propres variables d’environnement, qui sont celles qu’il hérite du shell que vous utilisez pour l’exécuter. Cependant, les shell ne transmettent pas toutes leurs variables à leurs environnements sous-processus '. Au lieu de cela, ils maintiennent une distinction entre les variables qui sont variables d'environnement ​​et celles qui ne le sont pas. (Ceux qui ne le sont pas sont souvent appelés Variables Shell.)


Variables Shell

Pour voir comment cela fonctionne, essayez ces commandes, qui sont incluses dans () afin qu'elles agissent indépendamment 1  d'un autre. Individuellement, chacune de ces commandes fonctionne de la même manière lorsque vous l'exécutez sans le (), mais les variables que vous créez dans les commandes précédentes existeraient toujours dans les commandes ultérieures. L'exécution des commandes dans les sous-shell empêche cela.

La création d'une nouvelle variable, puis l'exécution d'une commande externe, ne la transmet pas à l'environnement de la commande. Sauf dans le cas inhabituel où vous avez déjà une variable d'environnement x, cette commande ne produit aucun résultat:

(x=foo; printenv x)

La variable is assignée dans le shell, cependant. Cette commande génère foo:

(x=foo; echo "$x")

Le shell prend en charge la syntaxe pour transmettre une variable dans l'environnement d'une commande sans affectant l'environnement du shell en cours. Ceci génère foo:

x=foo printenv x

(Cela fonctionne également dans un sous-shell, bien sûr --(x=foo printenv x)--, mais je l'ai montré sans le () car, lorsque vous utilisez cette syntaxe, rien n'est défini pour votre Shell actuel. commandes d'être affectés.)

Ceci affiche foo, puis bar:

(x=bar; x=foo printenv x; echo "$x")

Exportation

Lorsque vous exportez une variable, celle-ci est automatiquement transmise aux environnements de toutes les commandes externes ultérieures exécutées à partir du même shell. La commande export effectue cette opération. Vous pouvez l'utiliser avant de définir la variable, après l'avoir définie, ou même de définir la variable in la commande export elle-même. Tous ces éléments impriment foo:

(x=foo; export x; printenv x)
(export x; x=foo; printenv x)
(export x=foo; printenv x)

Il n'y a pas de commande unexport. Même si vous pouvez exporter une variable avant de la définir, la désélectionner est également annulée, ce qui revient à dire que cela n’imprime rien, plutôt que d’afficher bar:

(x=foo; export x; unset x; x=bar; printenv x)

Mais modification la valeur d'une variable après son exportation ne affecte la valeur exportée. Ceci affiche foo, puis bar:

(export x=foo; printenv x; x=bar; printenv x)

Comme d’autres processus, votre Shell hérite lui-même des variables d’environnement de son processus parent. Ces variables sont présentes initialement dans l'environnement de votre shell et elles sont automatiquement exportées - ou rester exportées, si vous choisissez de le penser de cette façon. Ceci imprime foo (rappelez-vous, VAR=val cmd exécute cmd avec VAR défini sur val dans son environnement):

x=foo bash -c 'printenv x'

Les variables définies dans les processus enfants n'affectent pas le processus parent, même si elles sont exportées. Ceci affiche foo (pas bar):

(x=foo; bash -c 'export x=bar'; echo "$x")

Sous-coquilles

Un sous-shell est aussi un processus enfant 2 ; ceci affiche également foo:

(x=foo; (export x=bar); echo "$x")

Cela devrait expliquer pourquoi j'ai inclus la plupart de ces commandes dans () pour les exécuter dans des sous-shell.

Les sous-coques sont spéciales, cependant. Contrairement aux autres sous-processus, tels que ceux créés lorsque vous exécutez une commande externe telle que printenv ou bash, n sous-shell hérite de la plupart de l'état de son shell parent . En particulier, les sous-shell héritent des variables non exportées . Tout comme (x=foo; echo "$x") imprime foo, il en va de même de (x=foo; (echo "$x")).

La variable non exportée n'est toujours pas exportée dans le sous-shell - à moins que vous ne l'exportiez - donc, tout comme (x=foo; printenv x) n'imprime rien, il en va de même de (x=foo; (printenv x)).

Un sous-shell est un type spécial de sous-processus qui est un shell. Tous les sous-processus qui sont des shells ne sont pas des sous-shells. Le shell créé en exécutant bash est pas un sous-shell et il n'hérite pas des variables non exportées. Donc, cette commande affiche une ligne vide (car echo imprime une nouvelle ligne même s’elle est appelée avec un argument vide):

(x=foo; bash -c 'echo "$x"')

Pourquoi PS1 n'est pas une variable d'environnement (et ne devrait normalement pas en être une)

Enfin, en ce qui concerne les raisons pour lesquelles les variables d'invite telles que PS1 sont des variables Shell mais non des variables d'environnement, les raisons sont les suivantes:

  1. Ils ne sont nécessaires que dans Shell, pas dans d'autres programmes.
  2. Ils sont définis pour chaque shell interactif et les shells non interactifs n'en ont pas du tout besoin. C'est-à-dire qu'ils n'ont pas besoin d'être hérités.
  3. Tenter de passer PS1 à un nouveau shell échouera généralement car , le shell réinitialisera généralement PS1.

Le point 3 mérite un peu plus d'explications, mais si vous n'essayez jamais de transformer PS1 en variable d'environnement, vous n'avez probablement pas vraiment besoin de connaître les détails.

Lorsque Bash démarre de manière non interactive, il désactive PS1.

Lorsqu'un Bash Shell non interactif démarre, il 3  nsetsPS1. Ceci affiche une ligne vierge (pas foo):

PS1=foo bash -c 'echo "$PS1"'

Pour vérifier qu'il est réellement non défini, et pas seulement défini, mais vide, vous pouvez exécuter ceci, qui affiche unset:

PS1=foo bash -c 'if [[ -v PS1 ]]; then echo set; else echo unset; fi'

Pour vérifier que cela est indépendant d'un autre comportement au démarrage, vous pouvez essayer de passer toute combinaison de --login, --norc ou --posix avant -c, ou de définir BASH_ENV sur le chemin d'accès à un script (par exemple, BASH_ENV=~/.bashrc PS1=foo bash ...) ou ENV si vous avez passé --posix. En aucun cas, un shell non interactif Bash ne peut pas annuler PS1.

Cela signifie que si vous exportez PS1 et exécutez un shell non interactif qui lui-même exécute un shell interactif, la valeur PS1 ne sera pas définie à l'origine. Pour cette raison - et aussi parce que d'autres shells autres que Bash (comme Ksh) ne se comportent pas tous de la même manière, et la façon dont vous écrivez PS1 pour Bash ne fonctionne pas toujours pour ces shells - je déconseille de tenter de faire de PS1 un variable d'environnement. Editez simplement ~/.bashrc pour définir l'invite de votre choix.

Lorsque Bash démarre de manière interactive, il souvent ​​définit ou réinitialise PS1.

Inversement, si vous nset]PS1 et exécutez un interpréteur de commandes Bash interactif, même si vous l'empêchez d'exécuter des commandes à partir de scripts de démarrage en transmettant --norc, il restera automatiquement set ​​PS1 à valeur par défaut. Exécuter env -u PS1 bash --norc vous donne un shell Bash interactif avec PS1 défini sur \s-\v\$. Etant donné que Bash étend \s au nom du shell et \v au numéro de version, ceci affiche bash-4.3$ comme invite sur Ubuntu 16.04 LTS. Notez que définir la valeur de PS1 en tant que chaîne vide n'est pas la même chose que le désactiver. Comme expliqué ci-dessous, l'exécution de PS1= bash vous donne un shell interactif avec un comportement de démarrage étrange. Évitez d'exporter PS1 lorsqu'il est défini sur la chaîne vide, pour une utilisation pratique, à moins que vous ne compreniez et souhaitiez ce comportement.

Toutefois, si vous définissez PS1 et exécutez un interpréteur de commandes Bash interactif - et qu'il ne soit pas annulé par un interpréteur de commandes non interactif - il conservera cette valeur ... jusqu'à ce qu'un script de démarrage tel que le /etc/profile global (pour les shells de connexion) ou /etc/bash.bashrc, ou votre ~/.profile, ~/.bash_login ou ~/.bash_profile par utilisateur (tous pour les shells de connexion) ou ~/.bashrc par l'utilisateur le réinitialise.

Même si vous éditez ces fichiers pour les empêcher de définir PS1-- ce que, dans le cas de /etc/profile et /etc/bash.bashrc, je déconseille de toute façon, car ils affectent tous les utilisateurs - vous ne pouvez pas vraiment vous fier à cela. Comme indiqué ci-dessus, les shells interactifs démarrés à partir de shells non interactifs n'auront pas PS1, sauf si vous devez le réinitialiser et le réexporter dans le shell non interactif. En outre, vous devriez réfléchir à deux fois avant de procéder car il est courant que le code Shell (y compris les fonctions Shell que vous avez définies) vérifie PS1 pour déterminer si le shell dans lequel il s'exécute est interactif ou non.

Vérifier PS1 est un moyen courant de déterminer si le shell actuel est interactif.

C’est pourquoi il est si important pour les shells Bash non interactifs 4  pour nset ​​PS1 automatiquement. Comme dans la section 6.3.2 Ce Shell est-il interactif? du Manuel de référence de Bash dit:

Les scripts [S] tartup peuvent examiner la variable PS1; il est non défini dans les shells non interactifs et défini dans des shells interactifs.

Pour voir comment cela fonctionne, voir l'exemple ici. Ou consultez les utilisations du monde réel dans Ubuntu. Par défaut, /etc/profile dans Ubuntu comprend:

if [ "$PS1" ]; then
  if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
    # The file bash.bashrc already sets the default PS1.
    # PS1='\h:\w\$ '
    if [ -f /etc/bash.bashrc ]; then
      . /etc/bash.bashrc
    fi
  else
    if [ "`id -u`" -eq 0 ]; then
      PS1='# '
    else
      PS1='$ '
    fi
  fi
fi

/etc/bash.bashrc, qui ne devrait absolument rien faire lorsque le shell est non interactif, a:

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

Subtilités de différentes méthodes de vérification de l'interactivité:

Pour atteindre le même objectif, /etc/skel/.bashrc, qui est copié dans les répertoires de base des utilisateurs lors de la création de leurs comptes (votre ~/.bashrc est donc probablement similaire), a:

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

C’est l’autre moyen courant de vérifier si un shell est interactif: voyez si le texte obtenu par en développement le paramètre spécial- (en écrivant $-) contient la lettre i. Habituellement, cela a exactement le même effet. Supposons toutefois que vous n’ayez pas modifié le code présenté ci-dessus qui apparaît par défaut dans les scripts de démarrage de Bash dans Ubuntu, et que:

  1. vous exportez PS1 en tant que variable d’environnement, et
  2. il est défini, mais à la valeur vide, et
  3. vous démarrez un Bash Shell interactif ...

Ensuite, /etc/profile (s'il s'agit d'un shell de connexion) ou /etc/bash.bashrc n'exécutera pas les commandes qu'ils exécutent habituellement pour les shells interactifs. ~/.bashrc le sera toujours.

Si vous souhaitez vérifier si un shell est interactif à l'aide de PS1 et obtenir la bonne réponse même si PS1 est défini mais vide, vous pouvez utiliser [[ -v PS1 ]] ou [ -v PS1 ]/test -v PS1. Notez cependant que le mot clé [[ et le test -v des constructions intégrées [ et test sont propres à Bash. Tous les autres coquillages à la Bourne ne les acceptent pas. Donc, vous devriez not ​​les utiliser dans des scripts tels que ~/.profile et /etc/profile qui pourraient s’exécuter dans d’autres shells (ou par un gestionnaire d’affichage lorsque vous vous connectez graphiquement), à moins que le script ne contienne autre chose qui vérifie quel shell est en cours d'exécution et n'exécute les commandes spécifiques à Bash que lorsque ce shell est Bash (par exemple, en cochant $BASH_VERSION).


Remarques

1 Cet article explique les sous-coques en détail. .2.4.3 Commandes de regroupement du manuel de référence de Bash explique la syntaxe ().

2  Notez qu'il existe circonstances sous lesquelles les commandes exécutées dans des sous-shell, même avec la syntaxe (), n'est pas utilisée. Par exemple, lorsque vous avez commandes séparées par | dans un pipeline , Bash les exécute dans un sous-shell (à moins que lastpipeoption du shell soit défini).

3  Sauf pour sous-coquilles . On peut soutenir que cela n’est même pas une exception, car les sous-shell ne "démarrent" pas au sens habituel de ce que nous entendons par là. (Ils n'ont pas vraiment de comportement d'initialisation significatif.) Notez que lorsque vous exécutez bash-- avec ou sans arguments - dans un shell Bash, cela crée un sous-processus qui est un shell, mais il s'agit de not = un sous-shell.

4  Notez que tous les obus - pas même tous les obus à la Bourne - se comportent de cette manière. Mais Bash le fait, et il est très courant que le code Bash, y compris le code dans les scripts de démarrage, l’utilise.

4
Eliah Kagan