Jak zdobyć zmienne środowiskowe innego procesu?

24

Jeśli zbadam /proc/1/environ, zobaczę ciąg rozdzielany znakami zerowymi 1zmiennych środowiskowych procesu . Chciałbym wprowadzić te zmienne do mojego obecnego środowiska. Czy jest na to łatwy sposób?

procStrona człowiek daje mi fragment, który pomaga być wydrukować każdą zmienną środowiskową w oparciu o wiersz po wierszu (cat /proc/1/environ; echo) | tr '\000' '\n'. Pomaga mi to sprawdzić, czy zawartość jest poprawna, ale tak naprawdę muszę zrobić te źródła w mojej bieżącej sesji bash.

W jaki sposób mogę to zrobić?

Dane O'Connor
źródło

Odpowiedzi:

23

Poniższe konwertuje każdą zmienną środowiskową na exportinstrukcję, odpowiednio cytowaną do odczytu w powłoce (ponieważ LS_COLORSna przykład prawdopodobnie zawierają w niej średniki), a następnie źródła.

[ printfW /usr/bin, niestety, zazwyczaj nie obsługuje %q, więc musimy wywołać jeden wbudowany w bash.]

. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ)
Mark Plotnick
źródło
Sugeruję . <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ), który również odpowiednio obsługuje zmienne z cudzysłowami.
John Kugelman wspiera Monikę
@JohnKugelman Bardzo dziękuję za ulepszenie, używając "$@"zamiast '{}'. Dla tych, którzy zastanawiają się nad --argumentem w poprawionej odpowiedzi: argumenty pozycyjne bash -c command_stringsą przypisywane począwszy od $0, a "$@"rozszerza się o argumenty zaczynające się od $1. Argument --zostaje przypisany do $0.
Mark Plotnick
10

W bashmożesz wykonać następujące czynności. Będzie to działać dla wszystkich możliwych treści zmiennych i pozwoli uniknąć eval:

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

Spowoduje to zadeklarowanie odczytanych zmiennych jako zmiennych powłoki w uruchomionej powłoce. Aby zamiast tego wyeksportować zmienne do działającego środowiska powłoki:

while IFS= read -rd '' var; do export "$var"; done </proc/$PID/environ
Graeme
źródło
10

W tej odpowiedzi zakładam system, w którym /proc/$pid/environzwraca środowisko procesu o określonym PID, z bajtami zerowymi między definicjami zmiennych. ( Więc Linux, Cygwin lub Solaris (?) ).

Zsh

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

(Całkiem proste, jak mówi zsh: przekierowanie wejściowe bez polecenia <FILE jest równoważne cat FILE. Wyjście zastępowania polecenia podlega rozszerzeniu parametru z flagami ps:\000: oznaczającymi „podziel na bajty zerowe” i @„jeśli wszystko jest w podwójnych cudzysłowach, to potraktuj każdy element tablicy jako osobne pole ”(uogólnianie "$@").)

Bash, mksh

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

(W tych powłokach pusty separator przekazywany do separatorów readpowoduje, że puste bajty są zerowe. Używam PWDjako nazwy zmiennej tymczasowej, aby uniknąć zablokowania innej zmiennej, która mogłaby zostać zaimportowana. Chociaż technicznie można również importować PWD, pozostanie ona ustawiona tylko do następny cd.)

POSIX

Przenośność POSIX nie jest tak interesująca w przypadku tego pytania, ponieważ dotyczy tylko systemów, które mają /proc/PID/environ. Pytanie brzmi: co obsługuje Solaris sed - czy też Solaris /proc/PID/environgo nie wykorzystał, ale jestem daleko w tyle za funkcjami Solaris, więc może teraz. W Linuksie narzędzia GNU i BusyBox są zerowo bezpieczne, ale z zastrzeżeniami.

Jeśli nalegamy na przenośność POSIX, żadne z narzędzi tekstowych POSIX nie jest wymagane do obsługi bajtów zerowych, więc jest to trudne. Oto rozwiązanie, które zakłada, że ​​awk obsługuje bajt zerowy jako separator rekordów (nawk i gawk robią, podobnie jak awy BusyBox, ale mawk nie).

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

BusyBox awk (który jest wersją powszechnie spotykane na wbudowanych systemów Linux) obsługuje NULL bajty ale nie ustawienie RSsię "\0"w BEGINbloku, a nie linia składnia polecenia powyżej; jednak obsługuje -v 'RS="\0"'. Nie badałem dlaczego, to wygląda na błąd w mojej wersji (Debian wheezy).

(Zawiń wszystkie wiersze rekordów rozdzielonych zerami w pojedyncze cudzysłowy "\047", po ucieczce pojedynczych cudzysłowów w wartościach.)

Ostrzeżenia

Uważaj, aby którykolwiek z nich mógł próbować ustawić zmienne tylko do odczytu (jeśli twoja powłoka ma zmienne tylko do odczytu).

Gilles „SO- przestań być zły”
źródło
W końcu do tego wróciłem. Wymyśliłem niezawodny sposób na wykonanie tego lub jakiegokolwiek zerowania we wszystkich powłokach, o których wiem po prostu. Zobacz moją nową odpowiedź.
mikeserv
6

Chodziłem z tym po kółku. Byłem sfrustrowany przenośnością zerowych bajtów. Nie podobało mi się to, że nie było niezawodnego sposobu na obsłużenie ich w skorupce. Więc szukałem dalej. Prawda jest taka, że ​​znalazłem kilka sposobów, aby to zrobić, z których tylko kilka zostało odnotowanych w mojej drugiej odpowiedzi. Ale rezultatem były co najmniej dwie funkcje powłoki, które działają w ten sposób:

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

Najpierw porozmawiam o \0rozgraniczeniu. To jest naprawdę łatwe do zrobienia. Oto funkcja:

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

Zasadniczo odpobiera stdini zapisuje do stdoutkażdego otrzymanego bajtu w systemie szesnastkowym po jednym w wierszu.

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

Założę się, że możesz zgadnąć, co jest \0null, prawda? Tak napisane jest łatwe w obsłudze z każdym sed . sedpo prostu zapisuje dwa ostatnie znaki w każdej linii, aż napotka zero, w którym to miejscu zastępuje nowe znaki pośrednie printfkodem przyjaznego formatu i wypisuje ciąg. Wynikiem jest \0nullrozdzielona tablica ciągów bajtów szesnastkowych. Popatrz:

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
.

Przetokowałem powyższe, aby teemożna było zobaczyć zarówno wynik działania polecenia, jak i wynik printfjego przetwarzania. Mam nadzieję, że zauważysz, że podpowłoka faktycznie nie jest cytowana, ale printfnadal dzieli się tylko na \0nullograniczniku. Popatrz:

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
.

Brak cytatów na temat tego rozszerzenia - nie ma znaczenia, czy go zacytujesz, czy nie. Wynika to z tego, że wartości zgryzu są \nrozdzielane osobno, z wyjątkiem jednej linii ewline generowanej za każdym razem, gdy seddrukuje łańcuch. Podział słów nie ma zastosowania. I to sprawia, że ​​jest to możliwe:

_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

Powyższa funkcja używa _zedlmtalbo ${pcat}do przygotowanego strumienia kodu bajtowego do pozyskiwania środowiska dowolnego procesu, który można znaleźć w /proc, lub bezpośrednio do .dot ${psrc}tego samego w bieżącej powłoce lub bez parametru, aby wyświetlić przetworzone wyjście tego samego na terminalu, takie jak setlub printenvbędzie. Wszystko czego potrzebujesz to $pid- zrobi każdy czytelny /proc/$pid/environplik.

Używasz go w ten sposób:

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

Ale jaka jest różnica między człowiekiem przyjaznym a zasobnym ? Różnica polega na tym, że ta odpowiedź różni się od wszystkich tutaj - w tym mojej drugiej. Każda inna odpowiedź zależy w jakiś sposób od cytowania powłoki w celu obsłużenia wszystkich przypadków krawędzi. Po prostu nie działa tak dobrze. Proszę uwierz mi - SPRÓBOWAŁEM. Popatrz:

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

ŻADNA liczba funky znaków lub zawartych cytatów nie może tego zepsuć, ponieważ bajty dla każdej wartości nie są obliczane aż do momentu, gdy zawartość jest pobierana. Wiemy już, że przynajmniej raz działała jako wartość - nie ma tu potrzeby analizy składni ani cytowania, ponieważ jest to kopia bajt po bajcie oryginalnej wartości.

Ta funkcja najpierw ocenia $varnazwy i czeka na zakończenie kontroli, zanim .dotsourcing dokona tego tutaj, podając go deskryptorowi pliku 3. Zanim zacznie źródła, tak to wygląda. Jest głupi. I przenośny POSIX. Cóż, przynajmniej obsługa \ 0null jest przenośna POSIX - system plików / process jest oczywiście specyficzny dla Linuksa. I dlatego są dwie funkcje.

mikeserv
źródło
3

Używanie sourcei proces zastępowania :

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

Wkrótce:

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

Używanie evali zastępowanie poleceń :

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

sedPołączenie może być zastąpione przez awkwywołanie:

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

Ale nie zapominaj, że nie usuwa żadnych zmiennych środowiskowych, które nie są w pid 1.

Pavel Šimerda
źródło
Czy eksport jest zbędny w odpowiedzi @ fr00tyl00p? Bo jeśli nie, wydaje się to dość ważne
Dane O'Connor,
Tak, eksport jest potrzebny. Naprawię to.
Pavel Šimerda
3
Wszystkie te polecenia dławią wartości zawierające znaki nowej linii i (w zależności od polecenia) inne znaki.
Gilles 'SO - przestań być zły'
Poprawny. I tak zachowa odpowiedź.
Pavel Šimerda
3

Warto zauważyć, że procesy mogą mieć zmienne środowiskowe, które nie są poprawnymi zmiennymi Bash / Sh / * sh - POSIX zaleca, ale nie wymaga, aby zmienne środowiskowe miały identyczne nazwy ^[a-zA-Z0-9_][a-zA-Z0-9_]*$.

Aby wygenerować listę zmiennych kompatybilnych z powłoką ze środowiska innego procesu, w 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
}

Podobnie, aby je załadować:

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
}

Ten problem pojawia się tylko sporadycznie, ale kiedy ...

solidsnack
źródło
0

Myślę, że to przenośny POSIX:

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

Ale @Gilles ma rację - sedprawdopodobnie poradzi sobie z zerami, ale może nie. Więc jest to (Naprawdę tak myślę tym razem) przenośna metoda POSIX:

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

Jeśli jednak masz GNU sed, musisz tylko:

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

  #BOTH METHODS OUTPUT:

wprowadź opis zdjęcia tutaj

Cóż, POSIX jest przenośny, z wyjątkiem tego, /dev/...który nie jest określony, ale można oczekiwać, że ta składnia będzie zachowywać się tak samo w większości Unices.

Teraz, jeśli ma to coś wspólnego z twoim drugim pytaniem , możesz użyć go w następujący sposób:

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

Dokument tutaj jest niezwykle pomocny, ponieważ zapobiega przykręcaniu powłoki przez cytowanie, które tak ciężko pracujemy w podpowłoce, a także zapewnia nam niezawodną ścieżkę do pliku.dot źródłowego zamiast, znowu, podpowłoki lub powłoki zmienna. Inni tutaj używają bashizmu, który działa w ten sam sposób - tyle że zdecydowanie jest anonimowy, podczas gdy POSIX określa tylko dokumentację tutaj, więc może to być dowolny typ pliku, choć w praktyce jest to zwykle plik. ( z drugiej strony używa anonimowości dla dokumentów tutaj)<(process substitution)|pipeioheretempdash,|pipes . Jednak niefortunną rzeczą w zastępowaniu procesów jest to, że zależy to również od powłoki - co może być szczególnie denerwujące, jeśli pracujesz init.

Działa to |pipesoczywiście również, ale w końcu tracisz środowisko, gdy |pipe'sstan wyparowuje wraz z jego podpowłoką. To znowu działa:

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

Sama sedinstrukcja działa, utrzymując każdą linię w pamięci, aż dojdzie do ostatniej, w tym czasie wykonuje globalną obsługę zastępowania, cytowanie i wstawianie nowych linii, gdzie jest to właściwe, przez zakotwiczenie na zerach. Naprawdę dość proste.

Na dashzdjęciu zobaczysz, że zdecydowałem się uniknąć \ bałaganu i dodałem GNUkonkretną -ropcję do sed. Ale to tylko dlatego, że mniej było pisać. Działa to w obie strony, jak widać na zshobrazku.

Oto zsh:

wprowadź opis zdjęcia tutaj

A oto dashrobi to samo:

wprowadź opis zdjęcia tutaj

Nawet ucieczki terminali przechodzą bez szwanku:

wprowadź opis zdjęcia tutaj

mikeserv
źródło
To nie jest przenośne POSIX, ponieważ sed nie jest wymagany do obsługi bajtów zerowych. (To powiedziawszy, przenośność POSIX nie jest tak interesująca dla tego pytania, ponieważ dotyczy tylko systemów, które mają /proc/PID/environ. Więc pytanie brzmi, co obsługuje Solaris sed - lub czy Solaris ma /proc/PID/environ, nie przywykł, ale jestem w pewnym sensie za zakrętem na temat funkcji Solaris, więc może dziś).
Gilles „SO- przestań być zły”
@Gilles Nie. Ale sedjest wymagany do obsługi ascii w systemie szesnastkowym, z czego bajt zerowy to jeden. Poza tym właściwie pomyślałem, czy nadal jest to o wiele łatwiejszy sposób.
mikeserv
Nie, POSIX mówi, że „Pliki wejściowe powinny być plikami tekstowymi” (dla sed i innych narzędzi tekstowych) i definiuje pliki tekstowe jako „plik zawierający znaki zorganizowane w jeden lub więcej wierszy. Wiersze nie zawierają znaków NUL (…) ”. Nawiasem mówiąc, \xNNskładnia nie jest wymagana w POSIX, nawet \OOOskładni ósemkowej (w ciągach C i w awk, tak, ale nie w wyrażeniach regularnych sed).
Gilles 'SO - przestań być zły'
@Gilles masz rację. Rozejrzałem się i nie mogłem znaleźć tego, co wcześniej myślałem. Zrobiłem to inaczej. Edycja teraz.
mikeserv
O ile wiem, Solaris nie ma /proc/PID/environprzecież (ma kilka innych wpisów podobnych do Linuksa /proc/PID, ale nie ma environ). Zatem przenośne rozwiązanie wcale nie musi wykraczać poza narzędzia Linux, co oznacza GNU sed lub BusyBox sed. Oba obsługują \x00wyrażenia regularne, więc kod jest tak przenośny, jak to konieczne (ale nie POSIX). Jest to jednak zbyt skomplikowane.
Gilles 'SO - przestań być zły'
-2
eval \`(cat /proc/1/environ; echo) | tr '\000' '\n'\`
Martin Drautzburg
źródło
1
To nie działa, jeśli jakakolwiek wartość zawiera znak specjalny powłoki.
Gilles „SO- przestań być zły”