如何获取另一个进程的环境变量?


24

如果我检查/proc/1/environ一下,可以看到进程1的环境变量的以空字节分隔的字符串。我想将这些变量带入当前环境。是否有捷径可寻?

proc手册页给我一个片段,这有助于将打印出每个环境变量一行一行地的基础(cat /proc/1/environ; echo) | tr '\000' '\n'。这可以帮助我验证内容是否正确,但是我真正需要做的是将这些变量输入到当前的bash会话中。

我怎么做?

Answers:


23

下面的代码会将每个环境变量转换为一条export语句,并用引号将其正确读取以读取到shell中(LS_COLORS例如,因为其中可能包含分号),然后将其获取。

[ 不幸的是,printfin /usr/bin通常不支持该功能%q,因此我们需要调用内置的一个bash。]

. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ)

我建议. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ),它也将正确处理带引号的变量。
John Kugelman支持Monica 2014年

@JohnKugelman非常感谢您的改进,使用"$@"代替'{}'。对于那些对--他改进的答案中的参数感到疑惑的人:将位置参数bash -c command_string指定为$0,从开始,而"$@"扩展为包括以开头的参数$1。参数--将分配给$0
Mark Plotnick

10

在其中,bash您可以执行以下操作。这将适用于变量的所有可能内容,并避免eval

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

这将在运行的shell中将读取的变量声明为shell变量。要将变量导出到正在运行的shell环境中:

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

10

在这个答案中,我假设一个系统/proc/$pid/environ以指定的PID返回进程的环境,变量定义之间为空字节。(因此是Linux,Cygwin或Solaris(?))。

sh

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

(非常简单,就像zsh一样:没有命令输入重定向<FILE等效于cat FILE。命令替换的输出经过参数扩展,其标志 ps:\000:表示“拆分为空字节”,并且@含义是“如果整个内容都用双引号引起来,则将每个数组元素都作为一个单独的字段”(概括"$@")。

Bash,mksh

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

(在这些shell中,传递给空的定界符会read导致将空字节PWD用作分隔符。我将其用作临时变量名,以避免破坏另一个最终可能会被导入的变量。尽管从技术上讲PWD,您也可以导入,但它只会保留直到下一个cd。)

POSIX

POSIX可移植性对于这个问题并不是很有趣,因为它仅适用于具有 /proc/PID/environ。所以问题是Solaris sed支持什么?或者Solaris是否拥有/proc/PID/environ,它不习惯使用,但是我在Solaris功能方面落后了很多,所以今天可能会。在Linux上,GNU实用程序和BusyBox都是null安全的,但带有警告。

如果我们确实坚持POSIX可移植性,则不需要POSIX文本实用程序来处理空字节,因此这很困难。这是一个假设awk支持空字节作为记录定界符的解决方案(nawk和gawk都支持,而BusyBox awk则支持,但mawk不支持)。

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

BusyBox的AWK(这是在嵌入式Linux系统中常见的版本)不支持的空字节而不是设置RS"\0"在一个BEGIN块以及上面没有命令行语法; 但是它确实支持-v 'RS="\0"'。我没有调查原因,这看起来像我版本中的错误(Debian wheezy)。

(将所有行以空分隔的记录括在单引号中"\047"在转义值内的单引号后,在单引号中。)

注意事项

请注意,其中任何一个都可能尝试设置只读变量(如果您的外壳程序具有只读变量)。


我终于回到了这一点。我想出了一种万无一失的方法,可以在所有我熟悉的shell中简单地执行此操作或执行任何null处理。看到我的新答案。
mikeserv

6

我为此转了一圈。我对空字节的可移植性感到沮丧。没有一种可靠的方法可以在外壳中处理它们,这让我感到不舒服。所以我一直在寻找。事实是,我找到了几种方法来做到这一点,而我的其他答案中只提到了其中几种。但是结果至少是两个这样的shell函数:

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

首先,我将讨论\0定界。实际上,这很容易做到。功能如下:

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

基本上od采用stdin与写入到其stdout它接收在每行一个十六进制的每个字节。

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

我敢打赌,您可以猜测是哪个\0null,对吗?像这样写出来,很容易用any 处理sedsed只需保存每行中的最后两个字符,直到遇到空值为止,此时它将以printf友好的格式代码替换中间的换行符并打印字符串。结果是\0null十六进制字节字符串的定界数组。看:

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
.

我将上述内容传递给,tee以便您可以同时看到命令怀疑的输出和printf的处理结果。我希望您会注意到,该子外壳实际上也没有被引用,但printf仍仅在\0null定界符处分割。看:

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
.

该扩展也没有引号-引号与否无关紧要。这是因为除了\n每次生成的一条举绳外,咬合值都是无分隔的sed打印一个字符串。分词不适用。这就是使这成为可能的原因:

_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

上述功能用途_zedlmt要么${pcat}为可以在被发现的任何过程的环境采购的字节码准备的流/proc,或者直接.dot ${psrc}在当前外壳相同,或不带参数,以显示相同的处理后的输出到终端等setprintenv会。您需要的是$pid- 任何可读的/proc/$pid/environ文件都可以。

您可以这样使用它:

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

但是,人类友好友善之间有什么区别?嗯,不同之处在于,这个答案与这里的其他答案(包括我的其他答案)不同。其他所有答案都取决于以某种方式引用外壳程序来处理所有边缘情况。它根本不能很好地工作。请相信我- 我已经尝试过。看:

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

没有数量的时髦字符或包含的引号可以解决此问题,因为直到获取内容的那一刻才评估每个值的字节。而且我们已经知道它至少可以作为一个值工作-此处没有解析或引用保护的必要,因为这是原始值的逐字节复制。

该函数首先评估$var名称,并等待检查完成,然后.dot再在文件描述符3上获取提交给它的here-doc。这很简单。和POSIX便携式。好吧,至少\ 0null处理是POSIX可移植的-/ process文件系统显然是Linux特定的。这就是为什么有两个功能的原因。


3

使用source过程替换

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

不久:

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

使用eval命令替换

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

sed呼叫可以替换为awk呼叫:

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

但是请不要忘记,它不会清除pid 1中没有的任何环境变量。


@ fr00tyl00p的答案中的导出是否多余?如果不是,原因似乎很重要
Dane O'Connor 2014年

是的,需要导出。我会解决的。
PavelŠimerda2014年

3
所有这些命令都会阻塞包含换行符和(取决于命令)其他字符的值。
吉尔(Gilles)'所以

正确。无论如何都会保留答案以供参考。
PavelŠimerda2014年

3

值得注意的是,进程可以具有不是有效的Bash / Sh / * sh变量的环境变量-POSIX建议但不要求环境变量具有匹配的名称^[a-zA-Z0-9_][a-zA-Z0-9_]*$

要在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
}

同样,要加载它们:

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
}

这个问题只是偶尔出现,但是当它出现时...


0

我认为这是POSIX可移植的:

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

但是@Gilles有一个很好的意义- sed可能会处理null,但可能不会。因此,实际上也是这个(我真的这么认为) POSIX可移植方法:

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

不过,如果您拥有GNU,则sed只需执行以下操作:

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

  #BOTH METHODS OUTPUT:

在此处输入图片说明

好吧,除了 /dev/...未指定它是但您可以非常期望该语法在大多数Unices上具有相同的行为。

现在,如果这与您的其他问题有关,则您可能希望像这样使用它:

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

此处文档非常有帮助,因为它可以防止外壳与我们在子外壳中难以处理的任何引用产生.dot冲突,并且还为我们提供了一个可获取文件的可靠路径,而不是子外壳或外壳变量。这里的其他人使用的<(process substitution)bashism的工作方式几乎相同-只是绝对是匿名的,|pipe而POSIX只iohere为here-docs 指定一个,因此它可以是任何类型的文件,尽管实际上,它通常是temp文件。dash,另一方面,|pipes对于here-docs 使用匿名)。不过,关于进程替换的不幸的事情是,它也依赖于外壳程序-如果使用,这可能是一个特别烦人的问题init

|pipes当然这也适用,但是当|pipe's状态随着其子外壳消失时,最终您又会失去环境。然后,再次起作用:

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

sed语句本身的工作原理是保持每行内存,直到它到达最后的,那时它执行全局替换操作报价和插入新行,其中通过锚固在空为宜。真的很简单。

dash图片中,您会看到我选择避开\ mess并将GNU特定-r选项添加到sed。但这只是因为它的键入较少。如您所见,它可以任一种方式工作zsh图像中。

这里是zsh

在此处输入图片说明

这是在dash做同样的事情:

在此处输入图片说明

即使终端逃生也毫发无损地通过:

在此处输入图片说明


这不是POSIX便携式的,因为不需要sed处理空字节。(话虽这么说,POSIX可移植性是不是很有趣了这个问题,因为它仅适用于具有系统/proc/PID/environ所以,问题是什么的Solaris sed的支持-或Solaris是否有/proc/PID/environ,它并没有使用,但我的方式落后于Solaris功能的曲线,所以现在可能如此。)
Gilles'SO-不再是邪恶的

@Gilles否。但是sed必须处理十六进制的ascii,其中空字节为1。此外,我实际上只是在想是否还有更简单的方法可以做到这一点。
mikeserv

否,POSIX表示“输入文件应为文本文件”(用于sed和其他文本实用程序),并将文本文件定义为“包含以一行或多行组织的字符的文件 ”。这些行不包含NUL字符(…)”。顺便说一下,\xNN在POSIX中甚至不需要\OOO八进制语法(在C字符串和awk中是,但是在sed正则表达式中不需要)语法。
吉尔斯(Gilles)'所以

@吉尔斯,你有意思。我环顾四周,找不到以前想像的东西。所以我做了不同的事情。现在编辑。
mikeserv

据我所知,Solaris /proc/PID/environ毕竟没有(它在中有其他几个类似Linux的条目/proc/PID,但没有environ)。因此,便携式解决方案根本不需要超越Linux工具,这意味着GNU sed或BusyBox sed。两者都支持正则\x00表达式,因此您的代码可根据需要进行移植(但不包含POSIX)。但是它太复杂了。
吉尔(Gilles)'所以

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.