web-dev-qa-db-ja.com

bash CGIスクリプトから$ QUERY_STRINGを解析する方法は?

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で見つかったものに設定されます。ただし、私のスクリプトは10個を超える入力パラメーターを持つようになりました。 $ QUERY_STRINGのパラメーターをbashで使用できる環境変数に自動的に変換する簡単な方法はありますか?

たぶん、ある種のforループを使用するかもしれませんが、スクリプトが各パラメーターを自動的に検出し、多分次のような配列を構築するのに十分なほど賢いスクリプトであるとなお良いでしょう。

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

それを行うコードをどのように書くことができますか?

22
User1

これを試して:

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
15
ghostdog74

邪悪な評価のジャンクは使用しないでください。

以下は、文字列を確実に解析して連想配列を取得する方法です。

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
bolt

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に悪意のあるコードが含まれることはありません
2
Tai Paul

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)でも動作します。

2
Simon

受け入れられた答えはおそらく最も美しいものですが、セキュリティが非常に重要な場合があり、スクリプトからもよく見えるようにする必要があります。

そのような場合、最初はタスクに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はそれらの1つであってはなりません。 ;-)

さらに、変数はエクスポートされず、QSnamevalnameおよびvalのみが使用されます。

&を;に置き換えるだけです。それは次のようなものになります:

a=123;b=456;c=ok

だから今あなたはあなたの変数を評価して読む必要があるだけです:

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

CGIクエリ文字列を処理する良い方法は、Bash cgiスクリプトのラッパーとして機能する Haserl を使用することであり、便利で安全なクエリ文字列解析を提供します。

1
user3292713

正しい答えに従って、配列変数をサポートするために この他の質問 のようないくつかの変更を加えました。また、作者が信用できないデコード機能も追加しました。

コードはやや乱雑に見えますが、機能します。変更やその他の推奨事項をいただければ幸いです。

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
1
badc0de

@giacecco

正規表現にハイフンを含めるには、@ starfryからの回答で2行を変更します。

次の2行を変更します。

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

これらの2行に:

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

なぜこれではない

    $ 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
0
Carlisto

投稿された回答(私のように)を処理できなかったすべての人にとって、 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>"

これが誰にとっても役立つことを願っています。

乾杯

0
kindaleek

これを最新にするには、最新の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

連想配列を使用したくない場合は、最後から2番目の行を変更して、必要なことを行います。ループの反復ごとに、パラメーターは${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          

パラメータは配列の逆順で終わることに注意してください(それは連想的であるため、重要ではありません)。

0
starfry

実際、私は bolt の答えが好きだったので、Busyboxでも動作するバージョンを作成しました(Busyboxの灰はここの文字列をサポートしていません)。このコードは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
0
ZsZs