Comment puis-je trouver les variables d'environnement d'un autre processus?

24

Si j'examine, /proc/1/environje peux voir une chaîne délimitée par des octets null 1des variables d'environnement du processus . Je voudrais introduire ces variables dans mon environnement actuel. Y a-t-il un moyen facile de faire ceci?

La procpage de manuel me donne un extrait qui aide à imprimer chaque variable d'environnement ligne par ligne (cat /proc/1/environ; echo) | tr '\000' '\n'. Cela m'aide à vérifier que le contenu est correct, mais ce que je dois vraiment faire, c'est source ces variables dans ma session bash actuelle.

Comment je fais ça?

Dane O'Connor
la source

Réponses:

23

Ce qui suit convertira chaque variable d'environnement en une exportinstruction, correctement citée pour la lecture dans un shell (car LS_COLORS, par exemple, est susceptible d'avoir des points-virgules), puis la source.

[Le printfin /usr/bin, malheureusement, ne prend généralement pas en charge %q, nous devons donc appeler celui intégré bash.]

. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ)
Mark Plotnick
la source
Je suggère . <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ), qui gérera également les variables avec des guillemets correctement.
John Kugelman soutient Monica le
@JohnKugelman Merci beaucoup pour l'amélioration, en utilisant "$@"au lieu de '{}'. Pour ceux qui s'interrogent sur l' --argument de sa réponse améliorée: les arguments de position à bash -c command_stringsont attribués à partir de $0, tandis que se "$@"développe pour inclure les arguments à partir de $1. L'argument --est affecté à $0.
Mark Plotnick
10

Dans bashvous pouvez faire ce qui suit. Cela fonctionnera pour tous les contenus possibles des variables et évitera eval:

while IFS= read -rd '' var; do declare +x "$var"; done </proc/$PID/environ

Cela déclarera les variables lues comme variables shell dans le shell en cours d'exécution. Pour exporter les variables dans l'environnement shell en cours d'exécution à la place:

while IFS= read -rd '' var; do export "$var"; done </proc/$PID/environ
Graeme
la source
10

Dans cette réponse, je suppose un système où /proc/$pid/environrenvoie l'environnement du processus avec le PID spécifié, avec des octets nuls entre les définitions de variables. ( Donc Linux, Cygwin ou Solaris (?) ).

Zsh

export "${(@ps:\000:)$(</proc/$pid/environ)}"

(Assez simple comme le fait zsh: une redirection d'entrée sans commande <FILE est équivalente à cat FILE. La sortie de la substitution de commande subit une expansion des paramètres avec les drapeaux ps:\000: signifiant "divisé en octets nuls" et @signifiant "si le tout est entre guillemets, alors traitez chaque élément du tableau comme un champ séparé »(généralisation "$@").)

Bash, mksh

while IFS= read -r -d "" PWD; do export "$PWD"; done </proc/$pid/environ
PWD=$(pwd)

(Dans ces shells, un délimiteur vide transmis à readdes octets nuls étant des séparateurs. J'utilise PWDcomme nom de variable temporaire pour éviter d'altérer une autre variable qui pourrait finir par être importée. Bien que vous puissiez techniquement importerPWD , elle ne resterait en place que jusqu'à ce que le suivant cd.)

POSIX

La portabilité POSIX n'est pas intéressante pour cette question, car elle ne s'applique qu'aux systèmes qui en ont /proc/PID/environ. La question est donc de savoir ce que Solaris sed prend en charge - ou si Solaris a/proc/PID/environ , il ne l'a pas utilisé auparavant, mais je suis loin derrière la courbe des fonctionnalités de Solaris, ce qui pourrait le cas de nos jours. Sous Linux, les utilitaires GNU et BusyBox sont tous deux sans danger, mais avec des mises en garde.

Si nous insistons sur la portabilité POSIX, aucun des utilitaires de texte POSIX n'est requis pour gérer les octets nuls, c'est donc difficile. Voici une solution qui suppose que awk prend en charge un octet nul comme délimiteur d'enregistrement (nawk et gawk le font, tout comme BusyBox awk, mais pas mawk).

eval $(</proc/$pid/environ awk -v RS='\0' '{gsub("\047", "\047\\\047\047"); print "export \047" $0 "\047"}')

BusyBox awk (qui est la version couramment trouvée sur les systèmes Linux embarqués) prend en charge les octets nuls mais ne se définit RSpas "\0"dans un BEGINbloc et pas la syntaxe de ligne de commande ci-dessus; mais il prend en charge-v 'RS="\0"' . Je n'ai pas cherché pourquoi, cela ressemble à un bogue dans ma version (Debian wheezy).

(Enveloppez toutes les lignes des enregistrements séparés par des "\047"valeurs nulles entre guillemets simples , après avoir échappé les guillemets simples à l'intérieur des valeurs.)

Avertissements

Sachez que l'un de ces paramètres peut tenter de définir des variables en lecture seule (si votre shell possède des variables en lecture seule).

Gilles 'SO- arrête d'être méchant'
la source
J'y suis finalement revenu. J'ai trouvé un moyen infaillible de faire cela ou toute manipulation nulle dans tous les shells que je connais assez simplement. Voir ma nouvelle réponse.
mikeserv
6

J'ai tourné en rond avec ça. J'étais frustré par la portabilité des octets nuls. Cela ne me convenait pas qu'il n'y avait aucun moyen fiable de les manipuler dans une coquille. J'ai donc continué à chercher. La vérité est que j'ai trouvé plusieurs façons de le faire, dont seulement quelques-unes sont mentionnées dans mon autre réponse. Mais les résultats ont été au moins deux fonctions shell qui fonctionnent comme ceci:

_pidenv ${psrc=$$} ; _zedlmt <$near_any_type_of_file

Je vais d'abord parler de la \0délimitation. C'est en fait assez facile à faire. Voici la fonction:

_zedlmt() { od -t x1 -w1 -v  | sed -n '
    /.* \(..\)$/s//\1/
    /00/!{H;b};s///
    x;s/\n/\\x/gp;x;h'
}

Fondamentalement odprend stdinet écrit à son stdoutchaque octet qu'il reçoit en hexadécimal une par ligne.

printf 'This\0is\0a\0lot\0\of\0\nulls.' |
    od -t x1 -w1 -v
    #output
0000000 54
0000001 68
0000002 69
0000003 73
0000004 00
0000005 69
0000006 73
    #and so on

Je parie que vous pouvez deviner quel est le \0nullbon, non? Écrit comme ça, il est facile à manipuler avec tout sed . sedenregistre simplement les deux derniers caractères de chaque ligne jusqu'à ce qu'il rencontre une valeur nulle à quel point il remplace les sauts de ligne intermédiaires par printfun code de format convivial et imprime la chaîne. Le résultat est un \0nulltableau délimité de chaînes d'octets hexadécimales. Regardez:

printf %b\\n $(printf 'Fewer\0nulls\0here\0.' |
    _zedlmt | tee /dev/stderr)
    #output
\x46\x65\x77\x65\x72
\x6e\x75\x6c\x6c\x73
\x68\x65\x72\x65
\x2e
Fewer
nulls
here
.

J'ai expliqué ce qui précède pour teeque vous puissiez voir à la fois la sortie de la substitution de commande et le résultat du printftraitement de. J'espère que vous remarquerez que le sous-shell n'est pas cité non plus, mais qu'il est printftoujours divisé uniquement au niveau du \0nulldélimiteur. Regardez:

printf %b\\n $(printf \
        "Fe\n\"w\"er\0'nu\t'll\\'s\0h    ere\0." |
_zedlmt | tee /dev/stderr)
    #output
\x46\x65\x0a\x22\x77\x22\x65\x72
\x27\x6e\x75\x09\x27\x6c\x6c\x27\x73
\x68\x20\x20\x20\x20\x65\x72\x65
\x2e
Fe
"w"er
'nu     'll's
h    ere
.

Aucune citation sur cette extension non plus - peu importe si vous la citez ou non. Cela est dû au fait que les valeurs de morsure ne sont pas séparées, à l'exception de la ligne \nélectronique générée pour chaque fois qui sedimprime une chaîne. Le fractionnement de mots ne s'applique pas. Et c'est ce qui rend cela possible:

_pidenv() { ps -p $1 >/dev/null 2>&1 &&
        [ -z "${1#"$psrc"}" ] && . /dev/fd/3 ||
        cat <&3 ; unset psrc pcat
} 3<<STATE
        $( [ -z "${1#${pcat=$psrc}}" ] &&
        pcat='$(printf %%b "%s")' || pcat="%b"
        xeq="$(printf '\\x%x' "'=")"
        for x in $( _zedlmt </proc/$1/environ ) ; do
        printf "%b=$pcat\n" "${x%%"$xeq"*}" "${x#*"$xeq"}"
        done)
#END
STATE

La fonction ci-dessus utilise _zedlmtsoit ${pcat}un flux préparé de code d'octet pour l'approvisionnement de l'environnement de tout processus qui peut être trouvé dans /proc, soit directement .dot ${psrc}le même dans le shell actuel, ou sans paramètre, pour afficher une sortie traitée de celui-ci vers le terminal comme setou le printenvfera. Tout ce dont vous avez besoin est d'un $pid- n'importe quel/proc/$pid/environ fichier lisible fera l'affaire.

Vous l'utilisez comme ceci:

#output like printenv for any running process
_pidenv $pid 

#save human friendly env file
_pidenv $pid >/preparsed/env/file 

#save unparsed file for sourcing at any time
_pidenv ${pcat=$pid} >/sourcable/env.save 

#.dot source any pid's $env from any file stream    
_pidenv ${pcat=$pid} | sh -c '. /dev/stdin'

#feed any pid's env in on a heredoc filedescriptor
su -c '. /dev/fd/4' 4<<ENV
    $( _pidenv ${pcat=$pid} )
ENV

#.dot sources any $pid's $env in the current shell
_pidenv ${psrc=$pid} 

Mais quelle est la différence entre convivial et sourcable ? Eh bien, la différence est ce qui rend cette réponse différente de toutes les autres ici - y compris la mienne. Toutes les autres réponses dépendent du shell citant d'une manière ou d'une autre pour gérer tous les cas de bord. Cela ne fonctionne tout simplement pas si bien. Croyez-moi, j'ai ESSAYÉ. Regardez:

_pidenv ${pcat=$$}
    #output
LC_COLLATE=$(printf %b "\x43")
GREP_COLOR=$(printf %b "\x33\x37\x3b\x34\x35")
GREP_OPTIONS=$(printf %b "\x2d\x2d\x63\x6f\x6c\x6f\x72\x3d\x61\x75\x74\x6f")
LESS_TERMCAP_mb=$(printf %b "\x1b\x5b\x30\x31\x3b\x33\x31\x6d")
LESS_TERMCAP_md=$(printf %b "\x1b\x5b\x30\x31\x3b\x33\x31\x6d")
LESS_TERMCAP_me=$(printf %b "\x1b\x5b\x30\x6d")
LESS_TERMCAP_se=$(printf %b "\x1b\x5b\x30\x6d")
LESS_TERMCAP_so=$(printf %b "\x1b\x5b\x30\x30\x3b\x34\x37\x3b\x33\x30\x6d")
LESS_TERMCAP_ue=$(printf %b "\x1b\x5b\x30\x6d")

AUCUNE quantité de caractères géniaux ou de guillemets contenus ne peut casser cela car les octets de chaque valeur ne sont évalués qu'au moment même où le contenu est recherché. Et nous savons déjà que cela a fonctionné comme valeur au moins une fois - il n'y a pas de protection d'analyse ou de citation nécessaire ici car il s'agit d'une copie octet par octet de la valeur d'origine.

La fonction évalue d'abord les $varnoms et attend que les vérifications soient terminées avant de se .dotprocurer le here-doc qui l'a alimenté sur le descripteur de fichier 3. Avant de le rechercher, c'est à quoi il ressemble. C'est infaillible. Et POSIX portable. Eh bien, au moins la gestion \ 0null est POSIX portable - le système de fichiers / process est évidemment spécifique à Linux. Et c'est pourquoi il y a deux fonctions.

mikeserv
la source
3

Utilisation sourceet substitution de processus :

source <(sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ)

Prochainement:

. <(sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ)

Utilisation evalet substitution de commandes :

eval `sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ`

L' sedappel peut être remplacé par un awkappel:

awk -vRS='\x00' '{ print "export", $0 }' /proc/1/environ

Mais n'oubliez pas qu'il n'efface pas les variables d'environnement qui ne sont pas dans le pid 1.

Pavel Šimerda
la source
L'export est-il redondant dans la réponse de @ fr00tyl00p? Sinon, cela semble assez important
Dane O'Connor
Oui, l'exportation est nécessaire. Je vais arranger ça.
Pavel Šimerda
3
Toutes ces commandes s'étouffent avec des valeurs qui contiennent des sauts de ligne et (selon la commande) d'autres caractères.
Gilles 'SO- arrête d'être méchant'
Correct. Gardera la réponse pour référence de toute façon.
Pavel Šimerda
3

Il convient de noter que les processus peuvent avoir des variables d'environnement qui ne sont pas des variables Bash / Sh / * sh valides - POSIX recommande mais n'exige pas que les variables d'environnement aient des noms correspondant ^[a-zA-Z0-9_][a-zA-Z0-9_]*$.

Pour générer une liste de variables compatibles avec le shell à partir d'un autre environnement de processus, dans Bash:

function env_from_proc {
  local pid="$1" skipped=( )
  cat /proc/"$pid"/environ | while read -r -d "" record
  do
    if [[ $record =~ ^[a-zA-Z_][a-zA-Z0-9_]*= ]]
    then printf "export %q\n" "$record"
    else skipped+=( "$record" )
    fi
  done
  echo "Skipped non-shell-compatible vars: ${skipped[@]%%=*}" >&2
}

De même, pour les charger:

function env_from_proc {
  local pid="$1" skipped=( )
  while read -r -d "" record
  do
    if [[ $record =~ ^[a-zA-Z_][a-zA-Z0-9_]*= ]]
    then export "$(printf %q "$record")"
    else skipped+=( "$record" )
    fi
  done < /proc/"$pid"/environ
  echo "Skipped non-shell-compatible vars: ${skipped[@]%%=*}" >&2
}

Ce problème ne survient qu'occasionnellement mais quand il le fait ...

solidsnack
la source
0

Je pense que c'est POSIX portable:

. <<ENV /dev/stdin
    $(sed -n 'H;${x;s/\(^\|\x00\)\([^=]*.\)\([^\x00]*\)/\2\x27\3\x27\n/gp}' \
       /proc/$pid/environ)
ENV

Mais @Gilles fait un bon point - sedgérera probablement les valeurs nulles, mais peut-être pas. Donc, il y a cette méthode portable POSIX (je pense vraiment cette fois) :

s=$$SED$$
sed 's/'\''/'$s'/;1s/^./'\''&/' </proc/"$$"/environ |
tr '\0' "'" |
sed 's/'\''/&\n&/g' |
sed '1d;$d;s/^\('\''\)\([^=]*.\)/\2\1/;s/'$s'/'\\\''/g'

Pourtant, si vous avez GNU, il sedvous suffit de faire:

sed -z 's/^[^=]*./&'\''/;s/$/'\''\n/' </proc/"$$"/environ

  #BOTH METHODS OUTPUT:

entrez la description de l'image ici

Eh bien, POSIX portable, à l'exception de celui /dev/...qui n'est pas spécifié, mais vous pouvez vous attendre à ce que cette syntaxe se comporte de la même manière sur la plupart des Unices.

Maintenant, si cela a quelque chose à voir avec votre autre question , vous pouvez l'utiliser comme ceci:

nsenter -m -u -i -n -p -t $PID /bin/bash 5<<ENV --rcfile=/dev/fd/5 
    $(sed -z 's/^[^=]*./&'\''/;s/$/'\''\n/' </proc/"$$"/environ)
ENV

Le document ici est extrêmement utile en ce qu'il empêche le shell de visser avec l'une des citations que nous travaillons si dur à gérer dans le sous-shell et nous fournit également un chemin fiable vers un fichier.dot source plutôt que, encore une fois, un sous-shell ou un shell variable. D'autres ici utilisent le bashisme qui fonctionne à peu près de la même manière - seulement c'est définitivement un anonyme alors que POSIX ne spécifie qu'un pour ici-docs et il peut donc s'agir de n'importe quel type de fichier, bien qu'en pratique, c'est généralement un fichier. ( en revanche, utilise l'anonymat pour here-docs) . La chose malheureuse à propos de la substitution de processus, cependant, est également dépendante du shell - ce qui pourrait être un problème particulièrement ennuyeux si vous travaillez avec .<(process substitution)|pipeioheretempdash,|pipesinit

Cela fonctionne également avec |pipesbien sûr, mais vous perdez à nouveau l'environnement à la fin lorsque l' |pipe'sétat s'évapore avec sa sous-couche. Là encore, cela fonctionne:

sed '...;a\exec <>/dev/tty' /proc/$pid/environ | sh -i 

le sed instruction elle-même fonctionne en maintenant chaque ligne en mémoire jusqu'à ce qu'elle atteigne la dernière, date à laquelle elle effectue un remplacement global en gérant les citations et en insérant des sauts de ligne le cas échéant en ancrant sur les valeurs nulles. Assez simple vraiment.

Dans l' dashimage, vous verrez que j'ai choisi d'éviter le \ mess et ajouté l' option GNUspécifique -rà sed. Mais c'est juste parce que c'était moins à taper. Cela fonctionne de toute façon, comme vous pouvez le voir dans lezsh image.

Voici zsh:

entrez la description de l'image ici

Et voici dashfaire la même chose:

entrez la description de l'image ici

Même les échappées terminales sont indemnes:

entrez la description de l'image ici

mikeserv
la source
Ce n'est pas portable POSIX car sed n'est pas requis pour gérer les octets nuls. (Cela étant dit, la portabilité POSIX n'est pas si intéressante pour cette question, car elle ne s'applique qu'aux systèmes qui en ont /proc/PID/environ. La question est donc de savoir ce que Solaris sed prend en charge - ou si Solaris l'a /proc/PID/environ, il ne l'a pas utilisé, mais je suis bien derrière la courbe des fonctionnalités de Solaris, ce qui pourrait se produire de nos jours.)
Gilles 'SO- arrête d'être méchant'
@Gilles No. Mais sedest requis pour gérer l'ascii hexadécimal, dont l'octet nul est un. En plus, je me suis juste demandé si c'était encore plus facile de faire ça.
mikeserv
Non, POSIX dit que «les fichiers d'entrée doivent être des fichiers texte» (pour sed et d'autres utilitaires de texte) et définit les fichiers texte comme «fichier contenant des caractères organisés en une ou plusieurs lignes. Les lignes ne contiennent pas de caractères NUL (…) ». Et d'ailleurs, la \xNNsyntaxe n'est pas requise dans POSIX, pas même la \OOOsyntaxe octale (dans les chaînes C et dans awk, oui, mais pas dans les expressions rationnelles sed).
Gilles 'SO- arrête d'être méchant'
@Gilles, vous avez raison. J'ai regardé partout et je n'ai pas pu trouver ce que je pensais pouvoir avant. Je l'ai donc fait différemment. Modification maintenant.
mikeserv
Autant que je sache, Solaris n'en a pas /proc/PID/environaprès tout (il contient plusieurs autres entrées de type Linux /proc/PID, mais pas environ). Une solution portable n'a donc pas besoin d'aller au-delà des outils Linux après tout, c'est-à-dire GNU sed ou BusyBox sed. Les deux prennent en charge \x00dans une expression régulière, donc votre code est aussi portable que nécessaire (mais pas POSIX). C'est cependant trop complexe.
Gilles 'SO- arrête d'être méchant'
-2
eval \`(cat /proc/1/environ; echo) | tr '\000' '\n'\`
Martin Drautzburg
la source
1
Cela ne fonctionne pas si une valeur contient un caractère spécial shell.
Gilles 'SO- arrête d'être méchant'