繁体   English   中英

如何从 bash CGI 脚本解析 $QUERY_STRING?

[英]How to parse $QUERY_STRING from a bash CGI script?

我有一个在 CGI 中使用的 bash 脚本。 CGI 通过读取?之后的所有内容来设置$QUERY_STRING环境变量? 在网址中。 例如, http://example.com?a=123&b=456&c=ok设置QUERY_STRING=a=123&b=456&c=ok

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

b=$(echo "$QUERY_STRING" | sed -n 's/^.*b=\\([^&]*\\).*$/\\1/p' | sed "s/%20/ /g")

这会将 $b 设置为在 $QUERY_STRING 中为b找到的任何内容。 但是,我的脚本已经增长到有十多个输入参数。 是否有更简单的方法将 $QUERY_STRING 中的参数自动转换为 bash 可用的环境变量?

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

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

我怎么能写代码来做到这一点?

试试这个:

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 中,您可以这样做(使用上面创建的数组):

declare -A array
for ((i=0; i<${#parm[@]}; i+=2))
do
    array[${parm[i]}]=${parm[i+1]}
done

这会给你这个:

array[a]=123
array[b]=456
array[c]=ok

编辑:

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

for ((i=0; i<${#parm[@]}; i+=2))
do
    declare var_${parm[i]}=${parm[i+1]}
done

那么你将拥有:

var_a=123
var_b=456
var_c=ok

您可以直接访问这些:

echo $var_a

或间接:

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

如果可能,最好避免间接,因为它会使代码变得混乱并成为错误的来源。

您可以使用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

您可以在 Bash 4+ 中保存到哈希/字典

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

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

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

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

如果你不喜欢密钥检查,你可以这样做:

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

列出数组中的所有键和值:

for key in "${!param[@]}"; do
    echo "$key: ${param[$key]}"
done

我将 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)也适用于我。

要将 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

假设是:

  • 值永远不会包含“&”
  • 值永远不会包含“;”
  • QUERY_STRING 永远不会包含恶意代码

虽然公认的答案可能是最漂亮的答案,但在某些情况下,安全性可能非常重要,并且还需要从您的脚本中清楚地看到。

在这种情况下,首先我不会使用 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

这在QUERY_STRING的名称-值对上进行迭代,并且没有办法用任何棘手的转义序列来规避它 - "在 bash 中是一个非常强大的东西,除了单个变量名称替换,它完全由我们控制,没有什么可以欺骗的。

此外,您可以将自己的处理代码注入“ # ... ”。 这使您能够只允许您自己的、明确定义的(理想情况下是简短的)允许变量名称列表。 不用说, LD_PRELOAD不应该是其中之一。 ;-)

此外,不会导出任何变量,仅使用QSnamevalnameval

按照正确答案,我对自己进行了一些更改以支持数组变量,例如在另一个问题中 我还添加了一个解码功能,我找不到作者来给予一些信任。

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

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 查询字符串的一个好方法是使用Haserl ,它充当 Bash cgi 脚本的包装器,并提供方便和安全的查询字符串解析。

我会简单地将 & 替换为 ;。 它会变成这样:

a=123;b=456;c=ok

所以现在你只需要评估和阅读你的变量:

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

为了更新这一点,如果您有最新的 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_REMATCH[1]} ,其值在${BASH_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

@贾塞科

要在正则表达式中包含一个 hihen,您可以在@starfry 的回答中更改这两行。

更改这两行:

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

到这两行:

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

对于所有无法使用已发布的答案(如我)使其工作的这个人想通了。

不幸的是,无法为他的帖子点赞...

让我快速在这里重新发布代码:

 #!/bin/sh if [ "$REQUEST_METHOD" = "POST" ]; then if [ "$CONTENT_LENGTH" -gt 0 ]; then read -n $CONTENT_LENGTH POST_DATA <&0 fi fi #echo "$POST_DATA" > data.bin IFS='=&' set -- $POST_DATA #2- Value1 #4- Value2 #6- Value3 #8- Value4 echo $2 $4 $6 $8 echo "Content-type: text/html" echo "" echo "<html><head><title>Saved</title></head><body>" echo "Data received: $POST_DATA" echo "</body></html>"

希望这对任何人都有帮助。

干杯

实际上我喜欢bolt的回答,所以我制作了一个也适用于 Busybox 的版本(Busybox 中的 ash 不支持此处的字符串)。 此代码将接受 key1 和 key2 参数,所有其他参数将被忽略。

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

可以使用bash-cgi.sh ,它处理:

  • 将查询字符串放入 $QUERY_STRING_GET 键值数组;

  • 将 post 请求数据 (x-www-form-urlencoded) 放入 $QUERY_STRING_POST 键值数组;

  • cookie 数据放入 $HTTP_COOKIES 键和值数组。

要求 bash 4.0 或更高版本(定义上面的键和值数组)。

所有处理仅由 bash 进行(即在一个进程中),没有任何外部依赖项和其他进程调用。

它有:

  • 检查最大数据长度,可以传输到它的输入,也可以作为查询字符串和 cookie 处理;

  • redirect() 过程产生重定向到它自己的扩展名更改为 .html(这对一个页面的站点很有用);

  • http_header_tail() 过程输出 HTTP(S) 响应头的最后两个字符串;

  • 可能注入的 $REMOTE_ADDR 值消毒剂;

  • 嵌入到传递给 $QUERY_STRING_GET、$QUERY_STRING_POST 和 $HTTP_COOKIES 的值中的转义 UTF-8 符号的解析器和评估器;

  • $QUERY_STRING_GET、$QUERY_STRING_POST 和 $HTTP_COOKIES 值的消毒剂以防止可能的 SQL 注入(像 mysql_real_escape_string php 函数那样进行转义,加上对 @ 和 $ 的转义)。

它可以在这里找到:

https://github.com/VladimirBelousov/fancy_scripts

这适用于使用 for in 循环的破折号

IFS='&'
for f in $query_string; do
   value=${f##*=}
   key=${f%%=*}
    # if you need environment variable -> eval "qs_$key=$value"
done

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM