【发布时间】:2010-11-21 06:31:57
【问题描述】:
在我的 bash 脚本中,有很多变量,我必须做一些事情来将它们保存到文件中。 我的问题是如何列出我的脚本中声明的所有变量并得到这样的列表:
VARIABLE1=abc
VARIABLE2=def
VARIABLE3=ghi
【问题讨论】:
在我的 bash 脚本中,有很多变量,我必须做一些事情来将它们保存到文件中。 我的问题是如何列出我的脚本中声明的所有变量并得到这样的列表:
VARIABLE1=abc
VARIABLE2=def
VARIABLE3=ghi
【问题讨论】:
set 会输出变量,不幸的是它也会输出函数定义。
幸运的是 POSIX 模式只输出变量:
( set -o posix ; set ) | less
管道到less,或重定向到您想要选项的位置。
所以要获取仅在脚本中声明的变量:
( set -o posix ; set ) >/tmp/variables.before
source script
( set -o posix ; set ) >/tmp/variables.after
diff /tmp/variables.before /tmp/variables.after
rm /tmp/variables.before /tmp/variables.after
(或者至少是基于此的东西:-))
【讨论】:
VARS="`set -o posix ; set`"; source script; SCRIPT_VARS="`grep -vFe "$VARS" <<<"$(set -o posix ; set)" | grep -v ^VARS=`"; unset VARS;。这也将以可保存的格式输出变量。该列表将包括脚本更改的变量(这取决于是否需要)
source 重新创建环境,您应该能够使用declare -p 的输出来实现。
before=$(set -o posix; set); dosomestuff; diff <(echo "$before") <(set -o posix; set)
set: no such option: posix) 中不起作用 - 我们必须改为在 /bin/bash 中执行命令。
compgen -v
它列出了所有变量,包括本地变量。 是从Get list of variables whose name matches a certain pattern学来的,在my script使用过。
【讨论】:
compgen -v 还列出了本地未设置的全局变量。不确定这是长期存在的错误还是期望的行为。
for i in _ {a..z} {A..Z}; do eval "echo \${!$i@}" ; done | xargs printf "%s\n"
这必须打印所有 shell 变量名称。您可以在获取文件之前和之后获取一个列表,就像使用“set”来区分哪些变量是新的(如其他答案中所述)。但请记住,使用 diff 进行此类过滤可以过滤掉一些您需要但在获取文件之前就已存在的变量。
在您的情况下,如果您知道变量的名称以“VARIABLE”开头,那么您可以获取脚本并执行以下操作:
for var in ${!VARIABLE@}; do
printf "%s%q\n" "$var=" "${!var}"
done
更新:对于纯 BASH 解决方案(不使用外部命令):
for i in _ {a..z} {A..Z}; do
for var in `eval echo "\\${!$i@}"`; do
echo $var
# you can test if $var matches some criteria and put it in the file or ignore
done
done
【讨论】:
eval "printf '%q\n' $(printf ' "${!%s@}"' _ {a..z} {A..Z})"
"\\${!$i@}"?!我看到像echo "${!BASH_C@}" 这样简单的东西会转储所有以BASH_C 开头的变量,但是为什么呢? ! 是间接运算符,但间接超过什么?我们最终列出了变量,而不是它们的值。到底是怎么回事! :)
根据上面的一些答案,这对我有用:
before=$(set -o posix; set | sort);
源文件:
comm -13 <(printf %s "$before") <(set -o posix; set | sort | uniq)
【讨论】:
如果您可以进行后处理(如前所述),您可能只需在脚本的开头和结尾放置一个 set 调用(每个到不同的文件),然后对两个文件进行比较。意识到这仍然会包含一些噪音。
您也可以以编程方式执行此操作。要将输出限制在您当前的范围内,您必须实现一个包装器来创建变量。例如
store() {
export ${1}="${*:2}"
[[ ${STORED} =~ "(^| )${1}($| )" ]] || STORED="${STORED} ${1}"
}
store VAR1 abc
store VAR2 bcd
store VAR3 cde
for i in ${STORED}; do
echo "${i}=${!i}"
done
产量
VAR1=abc
VAR2=bcd
VAR3=cde
【讨论】:
这是类似于@GinkgoFr 的答案,但没有@Tino 或@DejayClayton 确定的问题,
并且比@DouglasLeeder 聪明的set -o posix bit 更健壮:
+ function SOLUTION() { (set +o posix; set) | sed -ne '/^\w\+=/!q; p;'; }
不同之处在于此解决方案在第一个非变量报告后停止,例如set报告的第一个函数
顺便说一句:“蒂诺”问题已解决。即使 POSIX 已关闭且函数由 set 报告,
解决方案的sed ... 部分仅允许通过变量报告(例如VAR=VALUE 行)。
特别是,A2 确实没有虚假地将其放入输出中。
+ function a() { echo $'\nA2=B'; }; A0=000; A9=999;
+ SOLUTION | grep '^A[0-9]='
A0=000
A9=999
并且:“DejayClayton”问题已解决(在变量值中嵌入换行符不会中断输出 - 每个 VAR=VALUE 得到一个输出行):
+ A1=$'111\nA2=222'; A0=000; A9=999;
+ SOLUTION | grep '^A[0-9]='
A0=000
A1=$'111\nA2=222'
A9=999
注意:@DouglasLeeder 提供的解决方案存在“DejayClayton”问题(带有嵌入换行符的值)。
下面,A1 是错误的,A2 根本不应该显示。
$ A1=$'111\nA2=222'; A0=000; A9=999; (set -o posix; set) | grep '^A[0-9]='
A0=000
A1='111
A2=222'
A9=999
最后:我不认为bash 的版本很重要,但它可能。我对此进行了测试/开发:
$ bash --version
GNU bash, version 4.4.12(1)-release (x86_64-pc-msys)
POST-SCRIPT:鉴于对 OP 的其他一些响应,我可以确定 set 总是 将值内的换行符转换为 \n,此解决方案依靠避免“DejayClayton”问题。也许这是一种现代行为?还是编译时变体?还是set -o 或shopt 选项设置?如果您知道此类变化,请添加评论...
【讨论】:
聚会有点晚了,但这里有另一个建议:
#!/bin/bash
set_before=$( set -o posix; set | sed -e '/^_=*/d' )
# create/set some variables
VARIABLE1=a
VARIABLE2=b
VARIABLE3=c
set_after=$( set -o posix; unset set_before; set | sed -e '/^_=/d' )
diff <(echo "$set_before") <(echo "$set_after") | sed -e 's/^> //' -e '/^[[:digit:]].*/d'
diff+sed 管道命令行以所需格式输出所有脚本定义的变量(在 OP 的帖子中指定):
VARIABLE1=a
VARIABLE2=b
VARIABLE3=c
【讨论】:
如果您只关心打印具有静态值的变量列表(即扩展在这种情况下不起作用),那么另一种选择是在文件中添加开始和结束标记,告诉您块的位置静态变量定义是,例如
#!/bin/bash
# some code
# region variables
VAR1=FOO
VAR2=BAR
# endregion
# more code
然后你就可以打印文件的那部分了。
这是我为此准备的:
function show_configuration() {
local START_LINE=$(( $(< "$0" grep -m 1 -n "region variables" | cut -d: -f1) + 1 ))
local END_LINE=$(( $(< "$0" grep -m 1 -n "endregion" | cut -d: -f1) - 1 ))
< "$0" awk "${START_LINE} <= NR && NR <= ${END_LINE}"
}
首先,请注意变量块驻留在此函数所在的同一文件中,因此我可以使用$0 访问文件的内容。
我使用“区域”标记来分隔不同的代码区域。所以我简单地将grep 用于“变量”区域标记(第一个匹配:grep -m 1)并让grep 为行号添加前缀(grep -n)。然后我必须从匹配输出中删除行号(拆分:)。最后,添加或减去 1,因为我不希望标记成为输出的一部分。
现在,要打印该文件范围,我使用 awk 和行号条件。
【讨论】:
尝试使用脚本(我们称之为“ls_vars”):
#!/bin/bash
set -a
env > /tmp/a
source $1
env > /tmp/b
diff /tmp/{a,b} | sed -ne 's/^> //p'
chmod +x 它,然后:
ls_vars your-script.sh > vars.files.save
【讨论】:
从安全角度来看,@akostadinov 的answer 或@JuvenXu 的answer 都比依赖set 命令的非结构化输出更可取,因为存在以下潜在的安全漏洞:
#!/bin/bash
function doLogic()
{
local COMMAND="${1}"
if ( set -o posix; set | grep -q '^PS1=' )
then
echo 'Script is interactive'
else
echo 'Script is NOT interactive'
fi
}
doLogic 'hello' # Script is NOT interactive
doLogic $'\nPS1=' # Script is interactive
上面的函数doLogic 使用set 来检查变量PS1 的存在以确定脚本是否是交互式的(不管这是否是实现该目标的最佳方式;这只是一个例子。)
但是,set 的输出是非结构化的,这意味着任何包含换行符的变量都可能完全污染结果。
这当然是潜在的安全风险。相反,请使用 Bash 对间接变量名扩展的支持,或 compgen -v。
【讨论】:
试试这个:set | egrep "^\w+="(有或没有| less 管道)
第一个建议的解决方案( set -o posix ; set ) | less 有效,但有一个缺点:它将控制代码传输到终端,因此无法正确显示。例如,如果有 (likely) 一个 IFS=$' \t\n' 变量,我们可以看到:
IFS='
'
…而不是。
我的egrep 解决方案正确地显示了这个(以及最终其他类似的)。
【讨论】:
bash -c $'a() { echo "\nA=B"; }; unset A; set | egrep "^\w+="' | grep ^A 显示 A=B" -> 失败!
我可能在不久前偷了the answer ...无论如何作为一个功能略有不同:
##
# usage source bin/nps-bash-util-funcs
# doEchoVars
doEchoVars(){
# if the tmp dir does not exist
test -z ${tmp_dir} && \
export tmp_dir="$(cd "$(dirname $0)/../../.."; pwd)""/dat/log/.tmp.$$" && \
mkdir -p "$tmp_dir" && \
( set -o posix ; set )| sort >"$tmp_dir/.vars.before"
( set -o posix ; set ) | sort >"$tmp_dir/.vars.after"
cmd="$(comm -3 $tmp_dir/.vars.before $tmp_dir/.vars.after | perl -ne 's#\s+##g;print "\n $_ "' )"
echo -e "$cmd"
}
【讨论】:
printenv 命令:printenv 打印所有 environment variables 及其值。
祝你好运……
【讨论】:
printenv 捕获它们,则必须导出脚本变量。
执行此操作的简单方法是在运行脚本之前通过设置系统环境变量来使用bash strict mode,并使用 diff 仅对脚本中的那些进行排序:
# Add this line at the top of your script :
set > /tmp/old_vars.log
# Add this line at the end of your script :
set > /tmp/new_vars.log
# Alternatively you can remove unwanted variables with grep (e.g., passwords) :
set | grep -v "PASSWORD1=\|PASSWORD2=\|PASSWORD3=" > /tmp/new_vars.log
# Now you can compare to sort variables of your script :
diff /tmp/old_vars.log /tmp/new_vars.log | grep "^>" > /tmp/script_vars.log
您现在可以在 /tmp/script_vars.log 中检索脚本的变量。 或者至少是基于此的东西!
【讨论】: