如何从bash CGI脚本解析$QUERY\u字符串?

如何从bash CGI脚本解析$QUERY\u字符串?,bash,cgi,Bash,Cgi,我有一个正在CGI中使用的bash脚本。CGI通过读取URL中?后面的所有内容来设置$QUERY\u STRING环境变量。例如,设置QUERY\u STRING=a=123&b=456&c=ok 我在某处发现了以下丑陋之处: b=$(echo“$QUERY\u STRING”| sed-n's/^.*b=\([^&]*\).$/\1/p'| sed“s/%20//g”) 它将把$b设置为b的$QUERY\u字符串中的任何内容。但是,我的脚本已经增加到有十多个输入参数。是否有一种更简单的方法可以

我有一个正在CGI中使用的bash脚本。CGI通过读取URL中
后面的所有内容来设置
$QUERY\u STRING
环境变量。例如,设置
QUERY\u STRING=a=123&b=456&c=ok

我在某处发现了以下丑陋之处:

b=$(echo“$QUERY\u STRING”| sed-n's/^.*b=\([^&]*\).$/\1/p'| sed“s/%20//g”)

它将把$b设置为
b
的$QUERY\u字符串中的任何内容。但是,我的脚本已经增加到有十多个输入参数。是否有一种更简单的方法可以自动将$QUERY\u字符串中的参数转换为bash可用的环境变量

也许我会使用某种形式的for循环,但如果脚本足够智能,能够自动检测每个参数,并可能构建一个如下所示的数组,那就更好了:

${parm[a]}=123
${parm[b]}=456
${parm[c]}=ok

我如何编写代码来实现这一点呢?

您可以使用
IFS
来分解
$QUERY
。例如,将其设置为
&

$ QUERY="a=123&b=456&c=ok"
$ echo $QUERY
a=123&b=456&c=ok
$ IFS="&"
$ set -- $QUERY
$ echo $1
a=123
$ echo $2
b=456
$ echo $3
c=ok

$ array=($@)

$ for i in "${array[@]}"; do IFS="=" ; set -- $i; echo $1 $2; done
a 123
b 456
c ok
您可以在Bash4中保存到哈希/字典+

$ declare -A hash
$ for i in "${array[@]}"; do IFS="=" ; set -- $i; hash[$1]=$2; done
$ echo ${hash["b"]}
456
试试这个:

saveIFS=$IFS
IFS='=&'
parm=($QUERY_STRING)
IFS=$saveIFS
现在你有了这个:

parm[0]=a
parm[1]=123
parm[2]=b
parm[3]=456
parm[4]=c
parm[5]=ok
在具有关联数组的Bash 4中,可以这样做(使用上面创建的数组):

编辑:

要在Bash 2及更高版本中使用间接寻址(使用上面创建的
parm
数组):

您可以直接访问以下内容:

echo $var_a
或间接地:

echo $var_a
for p in a b c
do
    name="var$p"
    echo ${!name}
done

如果可能的话,最好使用它,因为它会使代码变得凌乱并成为bug的来源。

处理CGI查询字符串的一个好方法是使用它作为Bash CGI脚本的包装器,并提供方便和安全的查询字符串解析。

我将sed命令打包到另一个脚本中:

$cat getvar.sh

s='s/^.*'${1}'=\([^&]*\).*$/\1/p'
echo $QUERY_STRING | sed -n $s | sed "s/%20/ /g"
我在我的主要cgi中将其称为:

id=`./getvar.sh id`
ds=`./getvar.sh ds`
dt=`./getvar.sh dt`
…等等,等等-你知道了。


即使使用非常基本的busybox设备(本例中是我的PVR),我也能正常工作。

根据正确的答案,我自己做了一些更改,以支持中的数组变量。我还添加了一个解码功能,我找不到作者给予一些信任

代码看起来有些凌乱,但它可以工作。如有更改和其他建议,将不胜感激

function cgi_decodevar() {
    [ $# -ne 1 ] && return
    local v t h
    # replace all + with whitespace and append %%
    t="${1//+/ }%%"
    while [ ${#t} -gt 0 -a "${t}" != "%" ]; do
        v="${v}${t%%\%*}" # digest up to the first %
        t="${t#*%}"       # remove digested part
        # decode if there is anything to decode and if not at end of string
        if [ ${#t} -gt 0 -a "${t}" != "%" ]; then
            h=${t:0:2} # save first two chars
            t="${t:2}" # remove these
            v="${v}"`echo -e \\\\x${h}` # convert hex to special char
        fi
    done
    # return decoded string
    echo "${v}"
    return
}

saveIFS=$IFS
IFS='=&'
VARS=($QUERY_STRING)
IFS=$saveIFS

for ((i=0; i<${#VARS[@]}; i+=2))
do
  curr="$(cgi_decodevar ${VARS[i]})"
  next="$(cgi_decodevar ${VARS[i+2]})"
  prev="$(cgi_decodevar ${VARS[i-2]})"
  value="$(cgi_decodevar ${VARS[i+1]})"

  array=${curr%"[]"}

  if  [ "$curr" == "$next" ] && [ "$curr" != "$prev" ] ;then
      j=0
      declare var_${array}[$j]="$value"
  elif [ $i -gt 1 ] && [ "$curr" == "$prev" ]; then
    j=$((j + 1))
    declare var_${array}[$j]="$value"
  else
    declare var_$curr="$value"
  fi
done
函数cgi_decodevar(){ [$#-ne 1]&返回 局部电压互感器 #将所有+替换为空白并追加%% t=“${1/+/}%%” 而[${t}-gt 0-a“${t}”!=“%”,则 v=“${v}${t%%\%*}”消化到第一个% t=“${t#*%}”#移除消化部分 #若有任何东西要解码,若并没有,则在字符串的末尾进行解码 如果[${t}-gt 0-a“${t}”!=“%”,那么 h=${t:0:2}#保存前两个字符 t=“${t:2}”#删除这些 v=“${v}”`echo-e\\\\x${h}`#将十六进制转换为特殊字符 fi 完成 #返回解码字符串 回显“${v}” 返回 } saveIFS=$IFS IFS='=&' 变量=($QUERY\u字符串) IFS=$saveIFS
对于((i=0;i)来说,要使其更新,如果您有最新的Bash版本,则可以使用正则表达式实现这一点:

q="$QUERY_STRING"
re1='^(\w+=\w+)&?'
re2='^(\w+)=(\w+)$'
declare -A params
while [[ $q =~ $re1 ]]; do
  q=${q##*${BASH_REMATCH[0]}}       
  [[ ${BASH_REMATCH[1]} =~ $re2 ]] && params+=([${BASH_REMATCH[1]}]=${BASH_REMATCH[2]})
done
如果不想使用关联数组,只需更改倒数第二行即可。对于循环的每次迭代,参数位于
${BASH\u REMATCH[1]}
中,其值位于
${BASH\u REMATCH[2]}

这与短测试脚本中的函数相同,该脚本在数组上迭代输出查询字符串的参数及其值

#!/bin/bash
QUERY_STRING='foo=hello&bar=there&baz=freddy'

get_query_string() {
  local q="$QUERY_STRING"
  local re1='^(\w+=\w+)&?'
  local re2='^(\w+)=(\w+)$'
  while [[ $q =~ $re1 ]]; do
    q=${q##*${BASH_REMATCH[0]}}
    [[ ${BASH_REMATCH[1]} =~ $re2 ]] && eval "$1+=([${BASH_REMATCH[1]}]=${BASH_REMATCH[2]})"
  done
}

declare -A params
get_query_string params

for k in "${!params[@]}"
do
  v="${params[$k]}"
  echo "$k : $v"
done          
请注意,参数以相反的顺序在数组中结束(它是关联的,所以这不重要)。

为什么不这样做

    $ echo "${QUERY_STRING}"
    name=carlo&last=lanza&city=pfungen-CH
    $ saveIFS=$IFS
    $ IFS='&'
    $ eval $QUERY_STRING
    $ IFS=$saveIFS
现在你有了这个

    name = carlo
    last = lanza
    city = pfungen-CH

    $ echo "name is ${name}"
    name is carlo
    $ echo "last is ${last}"
    last is lanza
    $ echo "city is ${city}"
    city is pfungen-CH

我只是简单地替换&to;。它将变成如下内容:

a=123;b=456;c=ok
因此,现在您只需评估和阅读您的VAR:

eval `echo "${QUERY_STRING}"|tr '&' ';'`
echo $a
echo $b
echo $c

请不要使用邪恶的评估垃圾

以下是如何可靠地解析字符串并获得关联数组:

declare -A param   
while IFS='=' read -r -d '&' key value && [[ -n "$key" ]]; do
    param["$key"]=$value
done <<<"${QUERY_STRING}&"

要将查询字符串的内容转换为bash变量,请使用以下命令:

eval $(echo ${QUERY_STRING//&/;})
内部步骤
echo${QUERY_STRING//&/;}
用分号替换所有的符号,生成a=123;b=456;c=ok,然后
eval
将其计算到当前shell中

然后,可以将结果用作bash变量

echo $a
echo $b
echo $c
这些假设是:

  • 值将永远不包含“&”
  • 值永远不会包含“;”
  • 查询字符串永远不会包含恶意代码
@giacecco

要在正则表达式中包含一个hiphen,您可以更改@starfry中的两行

更改这两行:

  local re1='^(\w+=\w+)&?'
  local re2='^(\w+)=(\w+)$'
  local re1='^(\w+=(\w+|-|)+)&?'
  local re2='^(\w+)=((\w+|-|)+)$'
这两条线:

  local re1='^(\w+=\w+)&?'
  local re2='^(\w+)=(\w+)$'
  local re1='^(\w+=(\w+|-|)+)&?'
  local re2='^(\w+)=((\w+|-|)+)$'

对于所有那些无法使用张贴的答案的人(比如我), 我明白了

不幸的是,他无法提升自己的职位

让我快速地将代码重新发布到这里:

!/bin/sh
如果[“$REQUEST_METHOD”=“POST”];则
如果[“$CONTENT_LENGTH”-gt 0];则
读取-n$CONTENT\u LENGTH POST\u DATA.bin
IFS='=&'
设置--$POST_数据
#2-价值1
#4-价值2
#6-价值3
#8-价值4
echo$2$4$6$8
echo“内容类型:文本/html”
回声“”
回显“已保存”
echo“收到的数据:$POST_数据”

echo“”
虽然公认的答案可能是最漂亮的,但在某些情况下,安全性非常重要,而且还需要从脚本中清晰可见

在这种情况下,首先我不会使用bash来完成任务,但是如果出于某种原因需要使用bash,那么最好避免使用这些新的数组字典特性,因为您无法确定它们到底是如何转义的

在这种情况下,好的原始解决方案可能会起作用:

QS="${QUERY_STRING}"
while [ "${QS}" != "" ]
do
  nameval="${QS%%&*}"
  QS="${QS#$nameval}"
  QS="${QS#&}"
  name="${nameval%%=*}"
  val="${nameval#$name}"
  val="${nameval#=}"

  # and here we have $name and $val as names and values

  # ...

done
这将迭代
查询字符串的名称-值对
,没有办法用任何复杂的转义序列来绕过它,
在bash中是一个非常强大的东西,除了由我们完全控制的单变量名称替换之外,没有什么可以欺骗的

此外,您可以将自己的处理代码注入“
#…”
”中
  local re1='^(\w+=\w+)&?'
  local re2='^(\w+)=(\w+)$'
  local re1='^(\w+=(\w+|-|)+)&?'
  local re2='^(\w+)=((\w+|-|)+)$'
QS="${QUERY_STRING}"
while [ "${QS}" != "" ]
do
  nameval="${QS%%&*}"
  QS="${QS#$nameval}"
  QS="${QS#&}"
  name="${nameval%%=*}"
  val="${nameval#$name}"
  val="${nameval#=}"

  # and here we have $name and $val as names and values

  # ...

done
while IFS= read -r -d '&' KEYVAL && [[ -n "$KEYVAL" ]]; do
case ${KEYVAL%=*} in
        key1) KEY1=${KEYVAL#*=} ;;
        key2) KEY2=${KEYVAL#*=} ;;
    esac
done <<END
$(echo "${QUERY_STRING}&")
END