Cómo analizar $ QUERY_STRING desde un script bash CGI

Tengo un script bash que se está usando en un CGI. El CGI establece la variable de entorno $ QUERY_STRING leyendo todo después de ? en la URL Por ejemplo, http://example.com?a=123&b=456&c=ok establece QUERY_STRING=a=123&b=456&c=ok .

En algún lugar encontré la siguiente fealdad:

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

que establecerá $ b a lo que se encontró en $ QUERY_STRING para b . Sin embargo, mi script ha crecido para tener más de diez parámetros de entrada. ¿Hay alguna manera más fácil de convertir automáticamente los parámetros en $ QUERY_STRING en variables de entorno utilizables por bash?

Tal vez use un bucle for de algún tipo, pero sería aún mejor si el script fuera lo suficientemente inteligente como para detectar automáticamente cada parámetro y quizás construir una matriz que se parezca a esto:

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

¿Cómo podría escribir código para hacer eso?

    Prueba esto:

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

    Ahora tienes esto:

     parm[0]=a parm[1]=123 parm[2]=b parm[3]=456 parm[4]=c parm[5]=ok 

    En Bash 4, que tiene matrices asociativas, puedes hacer esto (usando la matriz creada anteriormente):

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

    que te dará esto:

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

    Editar:

    Para usar direccionamiento indirecto en Bash 2 y posteriores (utilizando la matriz de parm creada anteriormente):

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

    Entonces tendrás:

     var_a=123 var_b=456 var_c=ok 

    Puede acceder a estos directamente:

     echo $var_a 

    o indirectamente:

     for p in abc do name="var$p" echo ${!name} done 

    Si es posible, es mejor evitar la indirección, ya que puede hacer que el código sea desordenado y ser una fuente de errores.

    puedes romper $QUERY usando IFS . Por ejemplo, configurándolo en &

     $ 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 

    Y puedes guardarlo en un hash / diccionario en Bash 4+

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

    Por favor, no use el malvado junk eva.

    A continuación, le mostramos cómo puede analizar de forma confiable la cadena y obtener una matriz asociativa:

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

    Si no le gusta el control de llave, puede hacer esto en su lugar:

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

    Listado de todas las claves y valores de la matriz:

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

    Para convertir los contenidos de QUERY_STRING en variables bash, use el siguiente comando:

     eval $(echo ${QUERY_STRING//&/;}) 

    El paso interno, echo ${QUERY_STRING//&/;} , sustituye todos los ampersands con punto y coma que producen a = 123; b = 456; c = ok, que el eval luego evalúa en el shell actual.

    El resultado se puede usar como variables de bash.

     echo $a echo $b echo $c 

    Las suposiciones son:

    • los valores nunca contendrán ‘&’
    • los valores nunca contendrán ‘;’
    • QUERY_STRING nunca contendrá código malicioso

    Empaqueté el comando sed en otro script:

    $ cat getvar.sh

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

    y lo llamo desde mi cgi principal como:

     id=`./getvar.sh id` ds=`./getvar.sh ds` dt=`./getvar.sh dt` 

    … etc, etc. – tienes una idea.

    funciona para mí incluso con un dispositivo busybox muy básico (mi PVR en este caso).

    Una buena manera de manejar las cadenas de consulta CGI es usar Haserl, que actúa como un envoltorio alrededor de su script Bash cgi, y ofrece un análisis conveniente y seguro de cadenas de consultas.

    Simplemente reemplazaría el & a;. Se convertirá en algo así como:

     a=123;b=456;c=ok 

    Entonces, ahora solo necesitas evaluar y leer tus vars:

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

    Siguiendo la respuesta correcta, me he hecho algunos cambios para apoyar variables de matriz como en esta otra pregunta . Agregué también una función de deencoding de la cual no puedo encontrar al autor para darle algún crédito.

    El código parece algo desordenado, pero funciona. Los cambios y otras recomendaciones serían muy apreciados.

     function cgi_decodevar() { [ $# -ne 1 ] && return local vth # 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 

    Para actualizar esto, si tienes una versión Bash reciente, puedes lograr esto con expresiones regulares:

     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 

    Si no desea utilizar matrices asociativas, simplemente cambie la penúltima línea para hacer lo que desee. Para cada iteración del ciclo, el parámetro está en ${BASH_REMATCH[1]} y su valor está en ${BASH_REMATCH[2]} .

    Esto es lo mismo que una función en un script de prueba corto que itera sobre la matriz genera los parámetros de la cadena de consulta y sus valores

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

    Tenga en cuenta que los parámetros terminan en la matriz en orden inverso (es asociativo, por lo que no debería importar).

    por qué no esto

      $ echo "${QUERY_STRING}" name=carlo&last=lanza&city=pfungen-CH $ saveIFS=$IFS $ IFS='&' $ eval $QUERY_STRING $ IFS=$saveIFS 

    ahora tienes esto

      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 

    @giacecco

    Para incluir un hiphen en la expresión regular, puedes cambiar las dos líneas como respuesta de @starfry.

    Cambia estas dos líneas:

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

    A estas dos líneas:

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

    Para todos aquellos que no pudieron hacerlo funcionar con las respuestas publicadas (como yo), este hombre lo descubrió.

    No puedo desacreditar su publicación desafortunadamente …

    Permítanme volver a publicar el código aquí muy rápido:

     #!/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 "Saved" echo "Data received: $POST_DATA" echo ""