如何从bash CGI脚本解析$ QUERY_STRING

时间:2010-10-12 23:11:57

标签: bash cgi

我有一个在CGI中使用的bash脚本。 CGI通过读取URL中?之后的所有内容来设置$ 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设置为b的$ QUERY_STRING中找到的任何内容。但是,我的脚本已经增长到超过十个输入参数。有没有更简单的方法将$ QUERY_STRING中的参数自动转换为bash可用的环境变量?

也许我只会使用某种类型的for循环,但如果脚本足够聪明以自动检测每个参数并且可能构建一个看起来像这样的数组,那就更好了:

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

我怎么能写代码呢?

15 个答案:

答案 0 :(得分:40)

试试这个:

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

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

答案 1 :(得分:14)

您可以使用$QUERY打破IFS。例如,将其设置为&

$ 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

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:3)

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

假设是:

  • 值永远不会包含'&amp;'
  • 值永远不会包含';'
  • QUERY_STRING永远不会包含恶意代码

答案 4 :(得分:2)

我将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),

也适用于我。

答案 5 :(得分:1)

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

答案 6 :(得分:1)

我只需更换&amp;至 ;。它将成为类似的东西:

a=123;b=456;c=ok

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

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

答案 7 :(得分:0)

在正确答案之后,我已经完成了一些更改,以支持this other question中的数组变量。我还添加了一个解码功能,我找不到作者给予一些信任。

代码看起来有些混乱,但它确实有效。非常感谢变更和其他建议。

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

答案 8 :(得分:0)

要更新这一点,如果您有最新的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          

请注意,参数以相反的顺序结束在数组中(它是关联的,因此不应该重要)。

答案 9 :(得分:0)

为什么不呢

    $ 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

答案 10 :(得分:0)

@giacecco

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

更改这两行:

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

这两行:

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

答案 11 :(得分:0)

对于所有那些无法使用已发布的答案(像我一样)的人, this guy弄清楚了。

不幸不能赞成他的帖子......

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

#!/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>"

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

干杯

答案 12 :(得分:0)

虽然公认的答案可能是最美丽的答案,但是在某些情况下,安全性是非常重要的,因此在脚本中也必须使其清晰可见。

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

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

此外,您可以将自己的处理代码注入“ # ...”中。这使您仅允许自己定义的(最好是简短的)允许变量名列表。不用说,LD_PRELOAD不应该是其中之一。 ;-)

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

答案 13 :(得分:0)

实际上,我喜欢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

答案 14 :(得分:0)

一个人可以使用 bash-cgi.sh 来处理:

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

  • 将请求数据(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