序列化环境变量的子集

Serialize a subset of environment variables

我正在尝试导出一些环境变量以供 TomCat 进程使用。

有几种方法可以做到这一点(我知道如何解决 总体 问题),但我不知道如何解决这个问题 shell任务。

Tomcat 建议您的所有环境自定义应按 "$CATALINA_HOME/bin/setenv.sh" 导出。

这整个东西将被塞进一个 Docker 容器中,所以唯一的参数化能力将通过 Docker env 变量(让我们假设我不想使用卷来完成这个任务在构建过程中安装或创建 setenv.sh)。

首先,观察 docker run -e 可用于将环境传递到容器中:

 docker run -eMY_VAR=SUP alpine env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=a528b6fc264b
MY_VAR=SUP
no_proxy=*.local, 169.254/16
HOME=/root

如果我们想将那个环境的 all 复制到 setenv.sh,就这么简单:

SETENV="/usr/local/tomcat/bin/setenv.sh"
echo '#!/bin/sh' > "$SETENV"
echo 'export -p' >> "$SETENV"
env >> "$SETENV"

但是复制所有东西多少有点违背了setenv.sh的要点——也就是说,给你的tomcat进程一个clean 环境,只有 有意 自定义。


因此,我们可以就 "which env vars are ones that we want to pass through to setenv.sh" 的约定达成一致。以 MY_.

为前缀的所有内容

现在 我们遇到了一个有趣的 shell 问题。

env | grep '^MY_' | sed 's/^MY_/EXPORT /'

这让我们非常接近。输出看起来像:

 docker run -e MY_VAR=hey alpine sh -c "env | grep '^MY_' | sed 's/^MY_/EXPORT /'" 
EXPORT VAR=hey

因此,我们从 env 命令 select 编辑:只有以 MY_ 为前缀的环境变量。我们可以将该输出重定向到 setenv.sh.

为什么说"pretty close"?看起来我们完成了,对吧?


试试这个尺码:

 docker run -e MY_VAR='multi                                                    
quote> line  
quote> string' alpine sh -c "env | grep '^MY_' | sed 's/^MY_/EXPORT /'"
EXPORT VAR=multi

该脚本仅适用于一个简单的可能性子集。即 我们只导出了多行字符串的第一行。

为方便起见:多行字符串的 env 输出如下所示:

 docker run -e MY_VAR='multi
line
string' alpine env                                              
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=0d0afaac6bec
MY_VAR=multi
line
string
no_proxy=*.local, 169.254/16
HOME=/root

我对尝试使用 awk 解决这个问题犹豫不决;可能还有其他我没有考虑过的字符串转义并发症。

我想知道是否有更好的方法来 select 并序列化导出环境的子集?


编辑:我不小心将此标记为 bash 问题,而我的本意是提出 sh 问题。具体来说,我的目的是获得一些东西,除了 alpine docker 图像附带的那些之外,没有任何依赖关系。即 BusyBox shsedgrepawkenv.

我保留了 bash 标签,以免惩罚在 bash-only 问题时提交的初始答案。

但我会优先考虑与 sh 兼容的答案,尤其是仅适用于 BusyBox UNIX 实用程序的答案。

怎么样

declare -p ${!MY_*}

declare -p ${!MY_*} | sed -r 's/^declare (-[^ ]*)* MY_/export /'

declare -p ${!MY_*} | sed 's/^declare \(-[^ ]*\)* MY_/export /'

编辑 posix 兼容版本:

一些 envprintenv 接受 -0 选项以 [=19=] 而不是换行结束每个输出行。于是

env -0  | perl -ne 'BEGIN{$/="[=13=]";$\="\n";$q="7"}next unless /^MY_/;chomp;s/$q/$q\$q$q/;s/=/=$q/;s/$/$q/;print'

工作原理

$/ : input record separator
$\ : output record separator
$q : variable to store single quote (7) because of surrounding single quotes in command
next : to filter "MY_" variables
chomp : removes the input separator
s/// : quote substitution

编辑:posix shell

中 perl 版本的变体
env -0 | xargs -0 sh -c 'for entry; do [[ $entry = MY_* ]] || continue; printf "%s=7%s7\n" "${entry%%=*}" "$(echo "${entry#*=}" | sed '\''s/\x27/\x27\\x27\x27/g'\'' )"; done' -

alpine 图片未随 bash 发货。

您可以使用此脚本提取所有 MY_* 变量,包括换行符:

docker run -e MY_FOO=bar -e MY_VAR="multi' export MY_INJECTED='val" -e MY_VAR2=$'multi
0MY_line=val
string' alpine sh -c "awk -v RS='' -F= '/^MY_/{k=$1; sub(/^[^=]+=/, \"\"); 
gsub(/7/, \"7\\\0477\"); printf \"export %s=7%s7\n\", k, $0
}' /proc/self/environ"

这将输出:

export MY_FOO='bar'
export MY_VAR='multi'\'' export MY_INJECTED='\''val'
export MY_VAR2='multi
0MY_line=val
string'

awk 的工作原理如下:

  • -v RS='':将记录分隔符设置为 </code> 也适用于 nul 字节(假设您的值中没有 <code>
  • -F=:设置字段分隔符为=
  • /^MY_/:只处理以MY_开头的记录
  • 在变量k
  • 中存储变量名或</code> <li>使用 <code>sub 函数获取 [=27=]= 之后的部分
  • 使用 print 格式输出以便可以在 $CATALINA_HOME/bin/setenv.sh 文件中使用。
  • 7 用于打印单引号

假设 GNU grep:

grep --null '^MY_' </proc/self/environ

...将以 NUL 分隔的形式发出您的环境变量(换行符完好无损)。


同样,如果你有 bash:

while IFS= read -r -d '' vardef; do
  [[ $vardef = MY_* ]] && printf '%s[=11=]' "$vardef"
done </proc/self/environ

请注意,如果这些变量是在同一个 shell 会话中设置的,您可能需要为 /proc/self/environ 创建一个子进程以进行更新:

(while IFS= read -r -d '' vardef; do
   [[ $vardef = MY_* ]] && printf '%s[=12=]' "$vardef"
 done </proc/self/environ)

所以你需要几样东西:

  • 枚举环境变量和select一个子集。
  • 对于每个 selected 环境变量,发出将变量设置为所需值的 sh 代码。

如果您想以可以读回的形式导出所有变量,您可以使用 export -p,但仅将某些变量解析为 select 比较困难。使用 export -p 的一种方法是取消设置其他变量。这仅在环境变量的 none 是只读的情况下有效,但您可以通过 运行 一个单独的 shell 实例(与子 shell 相反)来解决这个问题.

要收集要取消设置的变量列表,您只需要获取所有环境变量列表的超集,并删除要保留的变量即可。您可以通过过滤 env 输出轻松做到这一点。我用一个简单的 grep 来做到这一点,如果您的包含标准比“以特定前缀开头”更复杂,您可能想要使用更复杂的代码。

由于变量包含换行符后跟有效变量名和等号而导致的偶尔误报只会导致对不存在的变量调用 unset,这什么都不做。所需变量已从排除列表中删除,因此最终输出将永远不会遗漏所需变量。

excluded=$(env | LC_ALL=C sed -n 's/^\([A-Z_a-z][0-9A-Z_a-z]*\)=.*//p' |
           grep -v 'MY_')
sh -c 'unset ; export -p' sh "$excluded" >setenv.sh

如果调用时 PATH 在环境中,Dash 会打印一个额外的 export PATH(没有值)。如果这让您感到困扰,请将 sh -c … 更改为 (unset PATH; sh -c …)