Bagaimana saya sumber variabel lingkungan proses lain?

24

Jika saya periksa, /proc/1/environsaya dapat melihat serangkaian 1variabel lingkungan proses null-byte-delimited . Saya ingin membawa variabel-variabel ini ke lingkungan saya saat ini. Apakah ada cara mudah untuk melakukan ini?

The prochalaman manual memberi saya potongan yang membantu akan mencetak masing-masing variabel lingkungan secara baris demi baris (cat /proc/1/environ; echo) | tr '\000' '\n'. Ini membantu saya memverifikasi konten yang benar, tetapi yang benar-benar perlu saya lakukan adalah sumber variabel-variabel ini ke sesi bash saya saat ini.

Bagaimana aku melakukan itu?

Dane O'Connor
sumber

Jawaban:

23

Berikut ini akan mengubah setiap variabel lingkungan menjadi exportpernyataan, yang dikutip dengan benar untuk dibaca menjadi shell (karena LS_COLORS, misalnya, kemungkinan memiliki titik koma di dalamnya), lalu sumbernya.

[The printfdi /usr/bin, sayangnya, umumnya tidak mendukung %q, jadi kita perlu untuk memanggil satu dibangun ke bash.]

. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ)
Tandai Plotnick
sumber
Saya sarankan . <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ), yang akan menangani variabel dengan tanda kutip di dalamnya dengan benar juga.
John Kugelman mendukung Monica
@JohnKugelman Terima kasih banyak untuk perbaikannya, menggunakan "$@"alih-alih '{}'. Bagi mereka yang bertanya-tanya tentang --argumen dalam jawaban yang ditingkatkan: argumen posisi bash -c command_stringditugaskan mulai dari $0, sementara "$@"memperluas untuk memasukkan argumen mulai dari $1. Argumen --ditugaskan $0.
Mark Plotnick
10

Di bashAnda dapat melakukan hal berikut. Ini akan berfungsi untuk semua konten yang mungkin dari variabel dan menghindari eval:

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

Ini akan mendeklarasikan variabel baca sebagai variabel shell di shell yang sedang berjalan. Untuk mengekspor variabel ke lingkungan shell yang sedang berjalan:

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

Dalam jawaban ini, saya menganggap sistem tempat /proc/$pid/environmengembalikan lingkungan proses dengan PID yang ditentukan, dengan byte nol antara definisi variabel. ( Jadi Linux, Cygwin atau Solaris (?) ).

Zsh

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

(Cukup sederhana seiring berjalannya zsh: pengalihan input tanpa perintah <FILE sama dengan cat FILE. Output dari substitusi perintah mengalami ekspansi parameter dengan flag yang ps:\000: berarti “split on null bytes”, dan @artinya “jika semuanya dalam tanda kutip ganda maka perlakukan setiap elemen array sebagai bidang terpisah ”(generalisasi "$@").)

Bash, mksh

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

(Dalam shell ini, pembatas kosong diteruskan ke readhasil dalam byte nol menjadi pemisah. Saya menggunakan PWDsebagai nama variabel sementara untuk menghindari clobber variabel lain yang mungkin akhirnya diimpor. Sementara Anda bisa secara teknis mengimpor PWDjuga, itu hanya akan tetap tinggal sampai selanjutnya cd.)

POSIX

Portabilitas POSIX tidak begitu menarik untuk pertanyaan ini, karena hanya berlaku untuk sistem yang memilikinya /proc/PID/environ. Jadi pertanyaannya adalah apa yang didukung Solaris - atau apakah Solaris memiliki /proc/PID/environ, itu tidak digunakan untuk tetapi saya jauh di belakang kurva pada fitur Solaris sehingga mungkin saat ini. Di Linux, utilitas GNU dan BusyBox keduanya null-safe, tetapi dengan peringatan.

Jika kita bersikeras pada portabilitas POSIX, tidak ada utilitas teks POSIX yang diperlukan untuk menangani byte nol, jadi ini sulit. Berikut adalah solusi yang mengasumsikan bahwa awk mendukung byte nol sebagai pembatas rekaman (nawk dan gawk lakukan, seperti halnya BusyBox awk, tetapi mawk tidak).

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

BusyBox awk (yang merupakan versi yang biasa ditemukan pada sistem Linux yang disematkan) tidak mendukung byte nol tetapi tidak diset RSke "\0"dalam BEGINblok dan bukan sintaks baris perintah di atas; Namun itu mendukung -v 'RS="\0"'. Saya belum menyelidiki mengapa, ini terlihat seperti bug dalam versi saya (Debian wheezy).

(Bungkus semua baris catatan yang dipisahkan nol dalam tanda kutip tunggal "\047", setelah lolos dari tanda kutip tunggal di dalam nilai.)

Peringatan

Berhati-hatilah bahwa semua ini dapat mencoba untuk mengatur variabel read-only (jika shell Anda memiliki variabel read-only).

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Saya akhirnya kembali ke ini. Saya menemukan cara yang sangat mudah untuk melakukan ini atau penanganan nol di semua shell saya tahu tentang cukup sederhana. Lihat jawaban baru saya.
mikeserv
6

Saya berputar-putar dengan ini. Saya frustrasi dengan portabilitas byte nol. Itu tidak cocok dengan saya bahwa tidak ada cara yang dapat diandalkan untuk menangani mereka dalam cangkang. Jadi saya terus mencari. Yang benar adalah saya menemukan beberapa cara untuk melakukan ini, hanya beberapa di antaranya yang dicatat dalam jawaban saya yang lain. Tetapi hasilnya setidaknya dua fungsi shell yang bekerja seperti ini:

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

Pertama saya akan berbicara tentang \0pembatas. Ini sebenarnya cukup mudah dilakukan. Inilah fungsinya:

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

Pada dasarnya odmengambil stdindan menulis ke stdoutsetiap byte yang diterimanya dalam heksadesimal satu per baris.

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

Saya yakin Anda bisa menebak yang mana \0null, kan? Ditulis seperti itu sangat mudah untuk menangani dengan setiap sed . sedhanya menyimpan dua karakter terakhir di setiap baris sampai menemukan nol di mana titik itu menggantikan baris baru menengah dengan printfkode format ramah dan mencetak string. Hasilnya adalah \0nullarray yang dibatasi string hex byte. Melihat:

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
.

Saya mengirim pipa di atas teesehingga Anda bisa melihat output susbstitusi perintah dan hasil printfpemrosesan. Saya harap Anda akan melihat bahwa subkulit sebenarnya tidak dikutip juga tetapi printfmasih dibagi hanya pada \0nullpembatas. Melihat:

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
.

Tidak ada kutipan pada ekspansi itu - tidak masalah apakah Anda mengutipnya atau tidak. Ini karena nilai gigitan yang \ndihasilkan tidak terpisahkan kecuali untuk satu baris yang dihasilkan untuk setiap kali sedmencetak string. Pemisahan kata tidak berlaku. Dan itulah yang memungkinkan ini terjadi:

_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

Fungsi di atas digunakan _zedlmtuntuk ${pcat}aliran kode byte yang disiapkan untuk sumber lingkungan dari setiap proses yang dapat ditemukan di /proc, atau untuk secara langsung .dot ${psrc}sama di shell saat ini, atau tanpa parameter, untuk menampilkan output yang diproses sama ke terminal seperti setatau printenvakan. Yang Anda butuhkan adalah $pid- file apa pun yang dapat dibaca /proc/$pid/environakan dilakukan.

Anda menggunakannya seperti ini:

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

Tetapi apa perbedaan antara ramah manusia dan sumber daya ? Nah, perbedaannya adalah apa yang membuat jawaban ini berbeda dari yang lain di sini - termasuk yang lain. Setiap jawaban lainnya tergantung pada shell mengutip dalam beberapa cara atau yang lain untuk menangani semua kasus tepi. Itu tidak bekerja dengan baik. Tolong percayalah - saya sudah MENCOBA. Melihat:

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

TIDAK ada jumlah karakter yang funky atau kutipan yang terkandung dapat mematahkan ini karena byte untuk setiap nilai tidak dievaluasi sampai saat konten diambil. Dan kita sudah tahu itu berfungsi sebagai nilai setidaknya sekali - tidak ada penguraian atau perlindungan kutipan yang diperlukan di sini karena ini adalah salinan byte-untuk-byte dari nilai aslinya.

Fungsi pertama mengevaluasi $varnama - nama dan menunggu untuk menyelesaikan cek sebelum .dotsumber di sini-dok di-deskriptor pada file 3. Sebelum sumber itu, itu seperti apa itu. Ini bukti bodoh. Dan POSIX portable. Yah, setidaknya penanganan \ 0null adalah POSIX portable - filesystem / proses jelas spesifik untuk Linux. Dan itu sebabnya ada dua fungsi.

mikeserv
sumber
3

Menggunakan sourcedan memproses substitusi :

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

Segera:

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

Menggunakan evaldan memerintahkan substitusi :

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

The sedpanggilan dapat diganti dengan awkpanggilan:

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

Tapi jangan lupa bahwa itu tidak menghapus variabel lingkungan yang tidak ada dalam pid 1.

Pavel Šimerda
sumber
Apakah ekspor berlebihan dalam jawaban @ fr00tyl00p? Karena jika tidak sepertinya cukup penting
Dane O'Connor
Ya, ekspor diperlukan. Saya akan memperbaikinya.
Pavel Šimerda
3
Semua perintah ini tercekik pada nilai yang berisi baris baru dan (tergantung pada perintah) karakter lainnya.
Gilles 'SANGAT berhenti menjadi jahat'
Benar. Akan tetap menyimpan jawaban untuk referensi.
Pavel Šimerda
3

Perlu dicatat bahwa proses dapat memiliki variabel lingkungan yang tidak valid variabel Bash / Sh / * sh - POSIX merekomendasikan tetapi tidak mengharuskan variabel lingkungan memiliki nama yang cocok ^[a-zA-Z0-9_][a-zA-Z0-9_]*$.

Untuk menghasilkan daftar variabel yang kompatibel dengan shell dari lingkungan proses lain, di 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
}

Demikian pula, untuk memuatnya:

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
}

Masalah ini muncul hanya sesekali tetapi ketika ...

solidsnack
sumber
0

Saya pikir ini adalah POSIX portable:

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

Tapi @Gilles membuat poin yang bagus - sedmungkin akan menangani nulls, tapi mungkin tidak. Jadi ada ini (saya benar-benar berpikir begitu kali ini) sebenarnya metode portabel POSIX, juga:

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

Namun, jika Anda memiliki GNU, sedAnda hanya perlu:

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

  #BOTH METHODS OUTPUT:

masukkan deskripsi gambar di sini

Nah, POSIX portable yang kecuali untuk /dev/...yang tidak ditentukan tetapi Anda bisa berharap sintaks itu berlaku sama di sebagian besar Unix.

Sekarang jika ini ada hubungannya dengan pertanyaan Anda yang lain , Anda mungkin ingin menggunakannya seperti ini:

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

Di sini-doc sangat membantu dalam hal itu membuat shell dari bercinta dengan salah satu mengutip kami bekerja keras untuk menangani di subkulit dan juga memberikan kita jalan diandalkan untuk .dotsourceable file yang bukan, sekali lagi, subkulit atau shell variabel. Yang lain di sini menggunakan <(process substitution)bashism yang bekerja dengan cara yang sama - hanya saja itu jelas anonim |pipesedangkan POSIX hanya menentukan iohereuntuk di sini-docs dan jadi itu bisa semua jenis file, meskipun, dalam praktiknya, biasanya tempfile. ( dash,di sisi lain, memang menggunakan anonim |pipesuntuk di sini-docs) . Hal yang disayangkan tentang substitusi proses, apakah itu juga tergantung pada shell - yang mungkin merupakan masalah yang sangat mengganggu jika Anda bekerja dengannya init.

Ini juga bekerja dengan baik |pipes, tetapi kemudian Anda kehilangan lingkungan lagi pada akhirnya ketika |pipe'snegara menguap dengan subkulitnya. Kemudian lagi, ini bekerja:

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

The sedPernyataan itu sendiri bekerja dengan memegang setiap baris dalam memori hingga mencapai yang terakhir, pada saat ia melakukan menggantikan global penanganan mengutip dan memasukkan baris baru di mana yang sesuai dengan penahan pada nulls. Cukup sederhana kok.

Pada dashgambar Anda akan melihat saya memilih untuk menghindari \ mess dan menambahkan opsi GNUkhusus -runtuk sed. Tapi itu hanya karena kurang mengetik. Cara kerjanya baik, seperti yang Anda lihat pada zshgambar.

Inilah zsh:

masukkan deskripsi gambar di sini

Dan inilah yang dashmelakukan hal yang sama:

masukkan deskripsi gambar di sini

Bahkan lolos terminal terjadi tanpa cedera:

masukkan deskripsi gambar di sini

mikeserv
sumber
Ini bukan POSIX-portable karena sed tidak diperlukan untuk menangani null byte. (Yang dikatakan, portabilitas POSIX tidak begitu menarik untuk pertanyaan ini, karena hanya berlaku untuk sistem yang memiliki /proc/PID/environ. Jadi pertanyaannya adalah apa yang didukung Solaris - atau apakah Solaris memiliki /proc/PID/environ, tidak digunakan untuk tetapi saya jalan di belakang kurva pada fitur Solaris sehingga mungkin saat ini.)
Gilles 'SO- stop being evil'
@Gilles No. Tetapi seddiperlukan untuk menangani heksadesimal ascii, di mana byte nol adalah satu. Selain itu, saya sebenarnya hanya berpikir jika cara ini masih jauh lebih mudah.
mikeserv
Tidak, POSIX mengatakan bahwa “File input harus berupa file teks” (untuk perangkat teks dan utilitas teks lainnya) dan mendefinisikan file teks sebagai “file yang berisi karakter yang disusun dalam satu atau beberapa baris. Garis tidak mengandung karakter NUL (...) ”. Dan omong-omong \xNNsintaks tidak diperlukan dalam POSIX, bahkan \OOOsintaks oktal (dalam string C dan awk, ya, tetapi tidak dalam regexps sed).
Gilles 'SO- stop being evil'
@Gilles, Anda benar. Saya mencari-cari dan saya tidak bisa menemukan apa yang saya pikir saya bisa sebelumnya. Jadi saya melakukannya secara berbeda. Editing sekarang.
mikeserv
Sejauh yang saya tahu, Solaris tidak memiliki /proc/PID/environsemuanya (ia memiliki beberapa entri seperti Linux lainnya /proc/PID, tetapi tidak environ). Jadi solusi portabel tidak perlu melampaui alat Linux, artinya GNU sed atau BusyBox sed. Keduanya mendukung \x00di regexp, sehingga kode Anda se portabel yang diperlukan (tetapi tidak POSIX). Ini terlalu rumit.
Gilles 'SANGAT berhenti menjadi jahat'
-2
eval \`(cat /proc/1/environ; echo) | tr '\000' '\n'\`
Martin Drautzburg
sumber
1
Ini tidak berfungsi jika nilai apa pun mengandung karakter khusus shell.
Gilles 'SO- berhenti bersikap jahat'