可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a bash script that is being used in a CGI. The CGI sets the $QUERY_STRING environment variable by reading everything after the ?
in the URL. For example, http://example.com?a=123&b=456&c=ok sets QUERY_STRING=a=123&b=456&c=ok
.
Somewhere I found the following ugliness:
b=$(echo "$QUERY_STRING" | sed -n 's/^.*b=\([^&]*\).*$/\1/p' | sed "s/%20/ /g")
which will set $b to whatever was found in $QUERY_STRING for b
. However, my script has grown to have over ten input parameters. Is there an easier way to automatically convert the parameters in $QUERY_STRING into environment variables usable by bash?
Maybe I'll just use a for loop of some sort, but it'd be even better if the script was smart enough to automatically detect each parameter and maybe build an array that looks something like this:
${parm[a]}=123 ${parm[b]}=456 ${parm[c]}=ok
How could I write code to do that?
回答1:
Try this:
saveIFS=$IFS IFS='=&' parm=($QUERY_STRING) IFS=$saveIFS
Now you have this:
parm[0]=a parm[1]=123 parm[2]=b parm[3]=456 parm[4]=c parm[5]=ok
In Bash 4, which has associative arrays, you can do this (using the array created above):
declare -A array for ((i=0; i
which will give you this:
array[a]=123 array[b]=456 array[c]=ok
Edit:
To use indirection in Bash 2 and later (using the parm
array created above):
for ((i=0; i
Then you will have:
var_a=123 var_b=456 var_c=ok
You can access these directly:
echo $var_a
or indirectly:
for p in a b c do name="var$p" echo ${!name} done
If possible, it's better to avoid indirection since it can make code messy and be a source of bugs.
回答2:
you can break $QUERY
down using IFS
. For example, setting it to &
$ 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
And you can save to a hash/dictionary in Bash 4+
$ declare -A hash $ for i in "${array[@]}"; do IFS="=" ; set -- $i; hash[$1]=$2; done $ echo ${hash["b"]} 456
回答3:
Please don't use the evil eval junk.
Here's how you can reliably parse the string and get an associative array:
declare -A param while IFS='=' read -r -d '&' key value && [[ -n "$key" ]]; do param["$key"]=$value done
If you don't like the key check, you could do this instead:
declare -A param while IFS='=' read -r -d '&' key value; do param["$key"]=$value done
Listing all the keys and values from the array:
for key in "${!param[@]}"; do echo "$key: ${param[$key]}" done
回答4:
I packaged the sed command up into another script:
$cat getvar.sh
s='s/^.*'${1}'=\([^&]*\).*$/\1/p' echo $QUERY_STRING | sed -n $s | sed "s/%20/ /g"
and I call it from my main cgi as:
id=`./getvar.sh id` ds=`./getvar.sh ds` dt=`./getvar.sh dt`
...etc, etc - you get idea.
works for me even with a very basic busybox appliance (my PVR in this case).
回答5:
A nice way to handle CGI query strings is to use Haserl which acts as a wrapper around your Bash cgi script, and offers convenient and secure query string parsing.
回答6:
I would simply replace the & to ;. It will become to something like:
a=123;b=456;c=ok
So now you need just evaluate and read your vars:
eval `echo "${QUERY_STRING}"|tr '&' ';'` echo $a echo $b echo $c
回答7:
To converts the contents of QUERY_STRING into bash variables use the following command:
eval $(echo ${QUERY_STRING//&/;})
The inner step, echo ${QUERY_STRING//&/;}
, substitutes all ampersands with semicolons producing a=123;b=456;c=ok which the eval
then evaluates into the current shell.
The result can then be used as bash variables.
echo $a echo $b echo $c
The assumptions are:
- values will never contain '&'
- values will never contain ';'
- QUERY_STRING will never contain malicious code
回答8:
Following the correct answer, I've done myself some changes to support array variables like in this other question. I added also a decode function of which I can not find the author to give some credit.
Code appears somewhat messy, but it works. Changes and other recommendations would be greatly appreciated.
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
回答9:
To bring this up to date, if you have a recent Bash version then you can achieve this with regular expressions:
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
If you don't want to use associative arrays then just change the penultimate line to do what you want. For each iteration of the loop the parameter is in ${BASH_REMATCH[1]}
and its value is in ${BASH_REMATCH[2]}
.
Here is the same thing as a function in a short test script that iterates over the array outputs the query string's parameters and their values
#!/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
Note the parameters end up in the array in reverse order (it's associative so that shouldn't matter).
回答10:
why not this
$ echo "${QUERY_STRING}" name=carlo&last=lanza&city=pfungen-CH $ saveIFS=$IFS $ IFS='&' $ eval $QUERY_STRING $ IFS=$saveIFS
now you have this
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