Как разобрать $ QUERY_STRING из CGI-скрипта bash?
У меня есть скрипт bash, который используется в CGI. CGI устанавливает переменную среды $QUERY_STRING
, читая все, что находится после URL-адреса ?
. Например, 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
Как я мог написать код для этого?
Ответы
Ответ 1
Попробуйте следующее:
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
Edit:
Чтобы использовать косвенное использование в 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
Если возможно, лучше избегать косвенности, поскольку он может сделать код грязным и быть источником ошибок.
Ответ 2
вы можете сломать $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=([email protected])
$ 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
Ответ 3
Пожалуйста, не используйте злой утиль eval.
Здесь вы можете достоверно разобрать строку и получить ассоциативный массив:
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
Ответ 4
Я упаковал команду sed в другой script:
$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
Чтобы преобразовать содержимое QUERY_STRING в переменные bash, используйте следующую команду:
eval $(echo ${QUERY_STRING//&/;})
Внутренний шаг echo ${QUERY_STRING//&/;}
заменяет все амперсанды точками с запятой, производящими a = 123; b = 456; c = ok, который затем eval
оценивается в текущей оболочке.
Результат можно затем использовать как переменные bash.
echo $a
echo $b
echo $c
Предположения:
-
Значения
- никогда не будут содержать '&'
Значения
- никогда не будут содержать ';'
- QUERY_STRING никогда не будет содержать вредоносный код
Ответ 6
Хорошим способом обработки строк запроса CGI является использование Haserl, который действует как обертка вокруг вашего Bash cgi script, и предлагает удобный и безопасный синтаксический анализ строк запроса.
Ответ 7
Следуя правильному ответу, я сделал некоторые изменения для поддержки переменных массива, таких как этот другой вопрос. Я добавил также функцию декодирования, которой я не могу найти автора, чтобы дать некоторый кредит.
Код выглядит несколько грязным, но он работает. Изменения и другие рекомендации были бы весьма признательны.
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
Я просто заменил бы и. Это станет чем-то вроде:
a=123;b=456;c=ok
Итак, теперь вам нужно просто оценить и прочитать ваши вары:
eval `echo "${QUERY_STRING}"|tr '&' ';'`
echo $a
echo $b
echo $c
Ответ 9
Хотя принятый ответ, пожалуй, самый красивый, могут быть случаи, когда безопасность очень важна, и она также должна быть хорошо видна из вашего сценария.
В таком случае, во-первых, я бы не стал использовать 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
, и нет способа обойти его любой хитрой escape-последовательностью - "
- очень сильная вещь в bash, за исключением подстановки имени одной переменной, который полностью контролируется нами, ничего нельзя обмануть.
Кроме того, вы можете добавить свой собственный код обработки в "# ...
". Это позволяет вам разрешить только свой собственный, четко определенный (и, в идеале, короткий) список разрешенных имен переменных. Само собой разумеется, LD_PRELOAD
не должен быть одним из них. ;-)
Кроме того, никакая переменная не будет экспортирована, и будут использоваться исключительно QS
, nameval
, name
и val
.
Ответ 10
Чтобы обновить эту версию, если у вас есть последняя версия 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]}
.
Вот то же самое, что и функция в коротком тесте script, который выполняет итерацию по массиву, выводит параметры строки запроса и их значения
#!/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
Обратите внимание, что параметры заканчиваются в массиве в обратном порядке (это ассоциативно, так что это не имеет значения).
Ответ 11
почему не этот
$ 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
Ответ 12
@giacecco
Чтобы включить в регулярное выражение дефис, вы можете изменить две строки как таковые в ответе @starfry.
Измените эти две строки:
local re1='^(\w+=\w+)&?'
local re2='^(\w+)=(\w+)$'
На эти две строки:
local re1='^(\w+=(\w+|-|)+)&?'
local re2='^(\w+)=((\w+|-|)+)$'
Ответ 13
Для всех тех, кто не мог заставить его работать с опубликованными ответами (как я),
этот парень понял это.
К сожалению, не могу проголосовать за его пост...
Позвольте мне очень быстро опубликовать код здесь:
#!/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>"