在Bash中从配置文件解析变量

36

在一个文件中有以下内容:

VARIABLE1="Value1"
VARIABLE2="Value2"
VARIABLE3="Value3"

我需要一个脚本,输出以下内容:

Content of VARIABLE1 is Value1
Content of VARIABLE2 is Value2
Content of VARIABLE3 is Value3

有什么想法吗?

7个回答

67

由于您的配置文件是有效的shell脚本,因此您可以将其源代码化到当前的shell中:

. config_file
echo "Content of VARIABLE1 is $VARIABLE1"
echo "Content of VARIABLE2 is $VARIABLE2"
echo "Content of VARIABLE3 is $VARIABLE3"

稍微干一点,但更加棘手

. config_file
for var in VARIABLE1 VARIABLE2 VARIABLE3; do
    echo "Content of $var is ${!var}"
done

12
@samson,首先,它允许注入任何恶意代码。 - Big McLargeHuge
8
为什么你会担心从配置文件中注入恶意代码?我可以想象出这样的情况,但我的需求是从只存在于我的本地机器上的文件中读取数据,比如用户/密码组合的列表。请问您需要翻译成哪个语言呢? - samson
在这个答案中,你需要硬编码变量名。而在被接受的答案中,你不需要这样做,这就是它更好的原因所在。 - kara deniz
1
@samson,我会担心这个问题,因为小的安全漏洞变成大的安全漏洞的方式通常是攻击者找到可以利用的漏洞来增加他们(通常最初非常小的)访问权限。一个配置文件如果被粗心地分配了权限,往往就是这样的机会。 - Charles Duffy
我也更喜欢这里被接受的解决方案,因为它适用于更一般的情况,并且独立于变量名称和数量自动执行。 - derHugo
如果配置文件中省略了某个值,那么也存在风险。如果允许用户运行脚本但不允许修改配置,则他们可能通过设置环境变量来规避限制。您需要将环境保护起来,以防止它成为攻击向量。 - Drew Nutter

47

awk -F\= '{gsub(/"/,"",$2);print "文件名为" $1 "的内容是" $2}' <filename>

提供另一个纯 bash 方案,仅供参考。

IFS="="
while read -r name value
do
echo "Content of $name is ${value//\"/}"
done < filename

你也可以使用 IFS="=" read -r name value 来处理分割(-r 与分割无关,但建议使用除非你有不使用它的理由)。 - chepner
如果值包含等号,awk和bash的解决方案将给出不同的结果。 - glenn jackman

40

如果您需要这些...

特点

  • 单行和内联注释;
  • 修剪围绕=的空格(即var = value将不会失败);
  • 带引号的字符串值;
  • 理解DOS行结尾符;
  • 保持安全,避免从配置文件中获取源代码。

代码

shopt -s extglob
configfile="dos_or_unix" # set the actual path name of your (DOS or Unix) config file
tr -d '\r' < $configfile > $configfile.unix
while IFS='= ' read -r lhs rhs
do
    if [[ ! $lhs =~ ^\ *# && -n $lhs ]]; then
        rhs="${rhs%%\#*}"    # Del in line right comments
        rhs="${rhs%%*( )}"   # Del trailing spaces
        rhs="${rhs%\"*}"     # Del opening string quotes 
        rhs="${rhs#\"*}"     # Del closing string quotes 
        declare $lhs="$rhs"
    fi
done < $configfile.unix

评论

tr -d '\r' ... 去除 DOS 回车符。
! $lhs =~ ^\ *# 跳过单行注释,-n $lhs 跳过空行。
使用 ${rhs%%*( )} 去除尾部空格需要启用扩展通配符,可以通过 shopt -s extglob 实现。(除了使用 sed),你还可以通过以下复杂的方式来实现:

rhs="${rhs%"${rhs##*[^ ]}"}"  

测试配置文件

## This is a comment 
var1=value1             # Right side comment 
var2 = value2           # Assignment with spaces 

## You can use blank lines 
var3= Unquoted String   # Outer spaces trimmed
var4= "My name is "     # Quote to avoid trimming 
var5= "\"Bob\""         

测试代码

echo "Content of var1 is $var1"
echo "Content of var2 is $var2"
echo "Content of var3 is [$var3]"
echo "Content of var4 + var5 is: [$var4$var5]"

结果

Content of var1 is value1
Content of var2 is value2
Content of var3 is [Unquoted String]
Content of var4 + var5 is: [My name is "Bob"]

难道不是rhs="${rhs%"}"用于匹配右边的引号,而rhs="${rhs#"}"用于匹配左边的引号吗? - meso_2600
1
另一个问题是:当值为“aaaa#AAAAA”时,脚本会从#右侧剥离所有内容。 - meso_2600
我可以确认,如果值中有 #,则此脚本将失败。 - Arda

9

我是这样做的

. $PATH_TO_FILE

2
与戴夫·肯尼迪所说的类似,这允许注入任何和所有恶意代码。 - michaelmcandrew
5
只有当其他人更改了"$PATH_TO_FILE"中的原始文件时,才会发生此情况。如果有人能够这样做,那么系统管理员就很粗心。 - albert

4
awk '{print "Content of "$1" is "$3}' FS='[="]'

结果

变量1的内容为Value1
变量2的内容为Value2
变量3的内容为Value3

0

给定以下配置文件:

[a]
b=C
d=E;rm t1
[b]
g=h

以下一行代码将解析并保存值:

CFG=path-to-file; for ini in `awk '/^\[/' $CFG`;do unset ARHG;declare -A ARHG;while read A B;do ARHG[$A]=$B;echo "in section $ini, $A is equal to"  ${ARHG["$A"]};done < <(awk -F'=' '/\[/ {x=0} x==1 && $0~/=/ && NF==2 {print $1, $2} $0==INI {x=1}' INI="$ini" $CFG);declare -p ARHG;echo;done;printf "end loop\n\n";declare -p ARHG

现在,让我们来分解一下

CFG=path-to-file;
for ini in `awk '/^\[/' $CFG` # finds the SECTIONS (aka "ini")
do 
  unset ARHG # resets ARHG 
  declare -A ARHG # declares an associative array
  while read A B
  do
    ARHG[$A]=$B
    echo "in section $ini, $A is equal to"  ${ARHG["$A"]}
  done < <(awk -F'=' '/\[/ {x=0} x==1 && $0~/=/ && NF==2 {print $1, $2} $0==INI {x=1}' INI="$ini" $CFG)
  # the awk splits the file into sections, 
  # and returns pairs of values separated by "="
  declare -p ARHG # displays the current contents of ARHG
  echo
done
printf "end loop\n\n"
declare -p ARHG

这使我们能够保存值,而不使用 eval 或 backtick。 为了“真正干净”,我们可以在行首和行尾删除 [:space:],忽略“^#”行,并删除“等于”符号周围的空格。


1
当然,“declare -A”的使用需要一个较新版本的bash。例如,“GNU bash,version 3.2.57(1)-release(x86_64-apple-darwin14)”不包括“-A”参数... - Rich Armstrong

-1
    # 
    #------------------------------------------------------------------------------
    # parse the ini like $0.$host_name.cnf and set the variables
    # cleans the unneeded during after run-time stuff. Note the MainSection
    # courtesy of : http://mark.aufflick.com/blog/2007/11/08/parsing-ini-files-with-sed
    #------------------------------------------------------------------------------
    doParseConfFile(){
        # set a default cnfiguration file
        cnf_file="$run_unit_bash_dir/$run_unit.cnf"

        # however if there is a host dependant cnf file override it
        test -f "$run_unit_bash_dir/$run_unit.$host_name.cnf" \
            && cnf_file="$run_unit_bash_dir/$run_unit.$host_name.cnf"

        # yet finally override if passed as argument to this function
        # if the the ini file is not passed define the default host independant ini file
        test -z "$1" || cnf_file=$1;shift 1;


        test -z "$2" || ini_section=$2;shift 1;
        doLog "DEBUG read configuration file : $cnf_file"
        doLog "INFO read [$ini_section] section from config file"

        # debug echo "@doParseConfFile cnf_file:: $cnf_file"
        # coud be later on parametrized ...
        test -z "$ini_section" && ini_section='MAIN_SETTINGS'

        doLog "DEBUG reading: the following configuration file"
        doLog "DEBUG ""$cnf_file"
        ( set -o posix ; set ) | sort >"$tmp_dir/vars.before"

        eval `sed -e 's/[[:space:]]*\=[[:space:]]*/=/g' \
            -e 's/#.*$//' \
            -e 's/[[:space:]]*$//' \
            -e 's/^[[:space:]]*//' \
            -e "s/^\(.*\)=\([^\"']*\)$/\1=\"\2\"/" \
            < $cnf_file \
            | sed -n -e "/^\[$ini_section\]/,/^\s*\[/{/^[^#].*\=.*/p;}"`

        ( set -o posix ; set ) | sort >"$tmp_dir/vars.after"

        doLog "INFO added the following vars from section: [$ini_section]"
        cmd="$(comm -3 $tmp_dir/vars.before $tmp_dir/vars.after | perl -ne 's#\s+##g;print "\n $_ "' )"
        echo -e "$cmd"
        echo -e "$cmd" >> $log_file
        echo -e "\n\n"
        sleep 1; printf "\033[2J";printf "\033[0;0H" # and clear the screen
    }
    #eof func doParseConfFile

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接