Wie beziehe ich die Umgebungsvariablen eines anderen Prozesses?

24

Wenn ich prüfe, /proc/1/environsehe ich eine durch Null-Bytes getrennte Zeichenfolge der 1Umgebungsvariablen des Prozesses . Ich möchte diese Variablen in meine aktuelle Umgebung integrieren. Gibt es eine einfache Möglichkeit, dies zu tun?

Die procManpage gibt mir einen Ausschnitt, mit dem sich jede Umgebungsvariable zeilenweise ausdrucken lässt (cat /proc/1/environ; echo) | tr '\000' '\n'. Dies hilft mir zu überprüfen, ob der Inhalt korrekt ist, aber ich muss diese Variablen wirklich in meine aktuelle Bash-Sitzung einbinden.

Wie mache ich das?

Dane O'Connor
quelle

Antworten:

23

Im Folgenden wird jede Umgebungsvariable in eine exportAnweisung konvertiert , die ordnungsgemäß zum Einlesen in eine Shell in Anführungszeichen gesetzt ist (da sie LS_COLORSbeispielsweise wahrscheinlich Semikolons enthält), und anschließend als Quelle verwendet.

[Das printfin /usr/binwird leider im Allgemeinen nicht unterstützt %q, daher müssen wir dasjenige aufrufen, in das es eingebaut ist bash.]

. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ)
Mark Plotnick
quelle
Ich schlage vor . <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ), dass Variablen mit Anführungszeichen auch richtig behandelt werden.
John Kugelman unterstützt Monica am
@ JohnKugelman Vielen Dank für die Verbesserung, mit "$@"statt '{}'. Für diejenigen, die sich über das --Argument in seiner verbesserten Antwort wundern : Positionsargumente, bash -c command_stringdie ab zugewiesen werden sollen $0, während "$@"es um Argumente erweitert wird, die ab beginnen $1. Das Argument --wird zugewiesen $0.
Mark Plotnick
10

In bashkönnen Sie Folgendes tun. Dies funktioniert für alle möglichen Inhalte der Variablen und vermeidet eval:

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

Dadurch werden die gelesenen Variablen als Shell-Variablen in der laufenden Shell deklariert. So exportieren Sie die Variablen stattdessen in die aktuelle Shell-Umgebung:

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

In dieser Antwort gehe ich von einem System aus, in dem /proc/$pid/environdie Umgebung des Prozesses mit der angegebenen PID mit Null-Bytes zwischen Variablendefinitionen zurückgegeben wird. ( Also Linux, Cygwin oder Solaris (?) ).

Zsh

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

(Ziemlich einfach wie zsh: Eine Eingabeumleitung ohne Befehl <FILE entspricht cat FILE. Die Ausgabe der Befehlssubstitution wird durch Parameter erweitert, wobei die Flags ps:\000: „Auf null Bytes aufteilen“ und @„Wenn das Ganze in doppelten Anführungszeichen steht, dann behandeln “ bedeuten jedes Array-Element als separates Feld ”(verallgemeinernd "$@").

Bash, mksh

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

(In diesen Shells hat ein leeres Trennzeichen zur Folge, readdass Null-Bytes als Trennzeichen verwendet werden. Ich verwende es PWDals temporären Variablennamen, um zu vermeiden, dass eine andere Variable überladen wird, die möglicherweise importiert wird. Auch wenn Sie sie technisch importieren können PWD, bleibt sie nur bis zum der nächste cd.)

POSIX

Die POSIX-Portabilität ist für diese Frage nicht besonders interessant, da sie nur für Systeme gilt, die über Folgendes verfügen /proc/PID/environ. Die Frage ist also, was Solaris sed unterstützt - oder ob Solaris /proc/PID/environdies früher getan hat , aber ich bin bei den Solaris-Funktionen weit hinter den Erwartungen zurück, so dass dies heute der Fall sein könnte. Unter Linux sind GNU-Dienstprogramme und BusyBox beide nullsicher, jedoch mit Einschränkungen.

Wenn wir auf POSIX-Portabilität bestehen, ist keines der POSIX-Textdienstprogramme für die Verarbeitung von Null-Bytes erforderlich, was schwierig ist. Hier ist eine Lösung, die davon ausgeht, dass awk ein Null-Byte als Datensatzbegrenzer unterstützt (nawk und gawk ebenso wie BusyBox awk, mawk jedoch nicht).

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

BusyBox awk (die die Version allgemein auf eingebettete Linux Systemen zu finden ist) unterstützen Null - Bytes , aber nicht einstellen , RSum "\0"in einem BEGINBlock und die Befehlszeile nicht Syntax oben; es unterstützt jedoch -v 'RS="\0"'. Ich habe nicht untersucht, warum, das sieht aus wie ein Fehler in meiner Version (Debian wheezy).

(Wrap alle Linien null getrennte Datensätze in Apostrophe "\047", nachdem die einfachen Anführungszeichen innerhalb Werte zu entkommen.)

Vorbehalte

Beachten Sie, dass in diesen Fällen möglicherweise versucht wird, schreibgeschützte Variablen festzulegen (wenn Ihre Shell schreibgeschützte Variablen enthält).

Gilles 'SO - hör auf böse zu sein'
quelle
Darauf bin ich endlich zurückgekommen. Ich fand ein narrensicheres Mittel heraus, um dies zu tun, oder eine Nullbehandlung in all den Shells, die ich kenne, ziemlich einfach. Siehe meine neue Antwort.
mikeserv
6

Ich bin damit herumgegangen. Ich war mit der Portabilität von Null-Bytes frustriert. Es war nicht gut für mich, dass es keine zuverlässige Möglichkeit gab, mit ihnen in einer Muschel umzugehen. Also suchte ich weiter. Die Wahrheit ist, dass ich verschiedene Wege gefunden habe, von denen nur einige in meiner anderen Antwort aufgeführt sind. Das Ergebnis waren jedoch mindestens zwei Shell-Funktionen, die folgendermaßen funktionieren:

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

Zuerst werde ich über die \0Abgrenzung sprechen . Das ist eigentlich ganz einfach. Hier ist die Funktion:

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

Nimmt odim Grunde genommen jedes Byte stdinund schreibt stdoutes in hexadezimal eins pro Zeile.

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

Ich wette, Sie können sich vorstellen, welches das ist \0null, oder? So geschrieben ist es einfach mit jedem umzugehen sed . sedSpeichert nur die letzten zwei Zeichen in jeder Zeile, bis eine Null gefunden wird. An diesem Punkt werden die Zwischenzeilen durch einen benutzerfreundlichen Formatcode ersetzt printfund die Zeichenfolge gedruckt. Das Ergebnis ist ein \0nullbegrenztes Array von Hex-Byte-Zeichenfolgen. Aussehen:

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
.

Ich habe das Obige weitergeleitet, teedamit Sie sowohl die Ausgabe der Befehlsaufhebung als auch das Ergebnis der printfVerarbeitung sehen können. Ich hoffe, Sie werden feststellen, dass die Subshell auch nicht in Anführungszeichen steht, sondern printfnur am \0nullBegrenzer geteilt ist. Aussehen:

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
.

Auch zu dieser Erweiterung gibt es keine Anführungszeichen - es spielt keine Rolle, ob Sie sie zitieren oder nicht. Dies liegt daran, dass die \nBisswerte mit Ausnahme der einen E-Zeile, die jedes Mal generiert wird, wenn sedeine Zeichenfolge gedruckt wird, nicht getrennt durchlaufen werden . Wortteilung gilt nicht. Und das macht es möglich:

_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

Die obige Funktion verwendet _zedlmtentweder ${pcat}einen vorbereiteten Strom von Byte-Code für das Beschaffen von Umgebungen für einen beliebigen Prozess, der sich in der aktuellen Shell befindet, oder /procdirekt .dot ${psrc}denselben oder ohne Parameter, um eine verarbeitete Ausgabe desselben für das Terminal anzuzeigen setoder printenvwird. Alles was Sie brauchen ist eine $pid- jede lesbare /proc/$pid/environDatei reicht aus.

Du benutzt es so:

#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} 

Aber was ist der Unterschied zwischen menschenfreundlich und säuerlich ? Nun, der Unterschied besteht darin, was diese Antwort anders macht als alle anderen hier - einschließlich meiner anderen. Jede andere Antwort hängt davon ab, dass die Shell auf die eine oder andere Weise zitiert, um alle Randfälle zu behandeln. Es funktioniert einfach nicht so gut. Bitte glauben Sie mir - ich habe es versucht. Aussehen:

_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")

KEINE Menge von unkonventionellen Zeichen oder Anführungszeichen kann dies stören, da die Bytes für jeden Wert erst ausgewertet werden, wenn der Inhalt in dem Moment bezogen wird, in dem er stammt. Und wir wissen bereits, dass es mindestens einmal als Wert funktioniert hat - hier ist kein Parsen oder Anführungszeichen erforderlich, da dies eine byteweise Kopie des ursprünglichen Werts ist.

Die Funktion wertet zuerst die $varNamen aus und wartet, bis die Überprüfungen abgeschlossen sind, bevor .dotdas hier angegebene Dokument in Dateideskriptor 3 eingespeist wird. Es ist narrensicher. Und POSIX portabel. Nun, zumindest die \ 0null-Behandlung ist POSIX-portierbar - das / process-Dateisystem ist offensichtlich Linux-spezifisch. Und deshalb gibt es zwei Funktionen.

mikeserv
quelle
3

Verwendung sourceund Prozessersetzung :

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

Kurz:

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

Verwenden evalund Ersetzen von Befehlen :

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

Der sedAnruf kann durch einen awkAnruf ersetzt werden:

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

Vergessen Sie jedoch nicht, dass keine Umgebungsvariablen gelöscht werden, die nicht in PID 1 enthalten sind.

Pavel Šimerda
quelle
Ist der Export in der Antwort von @ fr00tyl00p redundant? Denn wenn nicht, scheint es ziemlich wichtig
Dane O'Connor
Ja, der Export wird benötigt. Ich werde es reparieren.
Pavel Šimerda
3
Alle diese Befehle verschlucken sich an Werten, die Zeilenumbrüche und (je nach Befehl) andere Zeichen enthalten.
Gilles 'SO- hör auf böse zu sein'
Richtig. Bewahrt die Antwort trotzdem als Referenz auf.
Pavel Šimerda
3

Es ist zu beachten, dass Prozesse Umgebungsvariablen haben können, die keine gültigen Bash / Sh / * sh-Variablen sind - POSIX empfiehlt, erfordert jedoch nicht, dass Umgebungsvariablen übereinstimmende Namen haben ^[a-zA-Z0-9_][a-zA-Z0-9_]*$.

So erstellen Sie in Bash eine Liste von Variablen, die mit der Shell einer anderen Prozessumgebung kompatibel sind:

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
}

Ebenso, um sie zu laden:

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
}

Dieses Problem tritt nur gelegentlich auf, aber wenn es auftritt ...

SolidSnack
quelle
0

Ich denke das ist POSIX portable:

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

Aber @Gilles macht einen guten Punkt - sedwird wahrscheinlich mit Nullen umgehen, aber vielleicht auch nicht. Also gibt es diese (ich denke wirklich, diesmal) tatsächlich auch tragbare POSIX-Methode:

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

Wenn Sie jedoch GNU haben sed, müssen Sie nur Folgendes tun:

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

  #BOTH METHODS OUTPUT:

Bildbeschreibung hier eingeben

Nun, POSIX Portable ist bis auf das, /dev/...was nicht spezifiziert ist, aber Sie können erwarten, dass sich diese Syntax auf den meisten Unices gleich verhält.

Wenn dies irgendetwas mit Ihrer anderen Frage zu tun hat, möchten Sie sie vielleicht so verwenden:

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

Das Here-Doc ist äußerst hilfreich, da es verhindert, dass die Shell mit einem der Zitate, mit denen wir in der Subshell so hart arbeiten, in Konflikt gerät, und uns außerdem einen zuverlässigen Pfad zu einer .dotSourcing- Datei und nicht zu einer Subshell oder einer Shell bietet Variable. Andere verwenden hier den <(process substitution)Bashismus, der auf die gleiche Weise funktioniert - nur ist er definitiv anonym, |pipewährend POSIX nur einen Wert ioherefür here-docs angibt und daher jede Art von Datei sein kann, obwohl es in der Praxis normalerweise eine tempDatei ist. ( dash,verwendet jedoch anonym |pipesfür here-docs) . Das Unglückliche an der Prozessersetzung ist jedoch, dass sie auch von der Shell abhängt - was besonders ärgerlich sein kann, wenn Sie damit arbeiten init.

Das funktioniert |pipesnatürlich auch mit , aber dann verliert man am Ende die Umwelt wieder, wenn der |pipe'sZustand mit seiner Unterschale verdunstet. Das funktioniert dann wieder:

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

Die sedAnweisung selbst hält jede Zeile im Speicher, bis die letzte erreicht ist. Zu diesem Zeitpunkt führt sie eine globale Ersetzungsbehandlung durch, bei der Anführungszeichen gesetzt und gegebenenfalls neue Zeilen eingefügt werden, indem die Nullen verankert werden. Ziemlich einfach.

Auf dem dashBild sehen Sie, dass ich mich dafür entschieden habe, das \ mess zu vermeiden, und die GNUspezifische -rOption hinzugefügt habe sed. Aber das ist nur, weil es weniger zu tippen war. Es funktioniert so oder so, wie Sie im zshBild sehen können.

Hier ist zsh:

Bildbeschreibung hier eingeben

Und hier machen dashwir das Gleiche:

Bildbeschreibung hier eingeben

Sogar Fluchtterminals bleiben unversehrt:

Bildbeschreibung hier eingeben

mikeserv
quelle
Dies ist nicht POSIX-portierbar, da sed nicht zur Verarbeitung von Null-Bytes erforderlich ist. (Abgesehen davon ist die POSIX-Portabilität für diese Frage nicht so interessant, da sie nur für Systeme gilt, auf denen bereits installiert ist /proc/PID/environ. Die Frage ist also, was Solaris sed unterstützt - oder ob Solaris dies bisher /proc/PID/environnicht getan hat , aber ich bin es.) hinter der Kurve auf Solaris-Funktionen, so könnte es heutzutage.)
Gilles 'SO- aufhören, böse zu sein'
@ Gilles Nein. Wird sedjedoch benötigt, um ASCII-Hexadezimalwerte zu verarbeiten, von denen das Nullbyte eins ist. Außerdem dachte ich eigentlich nur, ob das noch viel einfacher geht.
mikeserv
Nein, POSIX gibt an, dass „die Eingabedateien Textdateien sein sollen“ (für sed und andere Textdienstprogramme) und definiert Textdateien als „Datei, die Zeichen enthält, die in eine oder mehrere Zeilen unterteilt sind. Die Zeilen enthalten keine NUL-Zeichen (…) “. \xNNÜbrigens ist die Syntax in POSIX nicht erforderlich, nicht einmal die \OOOOktalsyntax (in C-Strings und in awk, ja, aber nicht in sed-Regexps).
Gilles 'SO- hör auf böse zu sein'
@ Gilles du hast einen Punkt. Ich sah mich um und konnte nicht finden, was ich vorher für möglich gehalten hatte. Also habe ich es anders gemacht. Bearbeiten Sie jetzt.
mikeserv
Soweit ich das beurteilen kann, hat Solaris doch nicht /proc/PID/environ(es hat mehrere andere Linux-ähnliche Einträge /proc/PID, aber nicht environ). Eine tragbare Lösung muss also nicht über Linux-Tools hinausgehen, dh GNU sed oder BusyBox sed. Beide unterstützen \x00reguläre Ausdrücke, sodass Ihr Code so portabel wie nötig ist (jedoch nicht POSIX). Es ist jedoch zu komplex.
Gilles 'SO- hör auf böse zu sein'
-2
eval \`(cat /proc/1/environ; echo) | tr '\000' '\n'\`
Martin Drautzburg
quelle
1
Dies funktioniert nicht, wenn ein Wert ein Shell-Sonderzeichen enthält.
Gilles 'SO - hör auf, böse zu sein'