Urlencodeの機能

Urlencodeの機能

古いバージョンのbusyboxを実行しているOpenWRTデバイスでは、シェルスクリプトを使用して文字列をURLエンコードする方法が必要です。今、私は次のコードを得ました。

urlencode() {
echo "$@" | awk -v ORS="" '{ gsub(/./,"&\n") ; print }' | while read l
do
  c="`echo "$l" | grep '[^-._~0-9a-zA-Z]'`"
  if [ "$l" == "" ]
  then
    echo -n "%20"
  else
    if [ -z "$c" ]
    then
      echo -n "$l"
    else
      printf %%%02X \'"$c"
    fi
  fi
done
echo ""
}

これはややうまく機能しますが、いくつかの欠陥があります。

  1. 「\」などの特定の文字はスキップされます。
  2. 結果は1文字ずつ返されるため、非常に遅くなります。バッチ処理では、URLエンコードには数文字列だけで約20秒かかります。

私のbashバージョンは$ {var:x:y}のような部分文字列をサポートしていません。

ベストアンサー1

[TL、DR:urlencode_grouped_case最後のコードブロックのバージョンを使用してください。 ]

awkはほとんどのことを行うことができますが、迷惑なことに、文字を数字に変換する方法が不足しています。odデバイスに存在する場合は、それを使用してすべての文字(より正確にはバイト)を対応する数字(awkが読みやすいように10進数で書かれている)に変換し、次にawkを使用して有効な文字をリテラルに変換し、引用符付き文字を正しい形式に変換します。

urlencode_od_awk () {
  echo -n "$1" | od -t d1 | awk '{
      for (i = 2; i <= NF; i++) {
        printf(($i>=48 && $i<=57) || ($i>=65 && $i<=90) || ($i>=97 && $i<=122) ||
                $i==45 || $i==46 || $i==95 || $i==126 ?
               "%c" : "%%%02x", $i)
      }
    }'
}

デバイスが存在しない場合は、odシェル内ですべての操作を実行できます。これによりパフォーマンスが大幅に向上し(外部プログラムへの呼び出し数が減り、組み込みprintfプログラムの場合はありません)、正しく作成するのが簡単になります。私はすべてのBusyboxシェルが文字列${VAR#PREFIX}からプレフィックスを切り取る構文をサポートしていると思います。これを使用して、文字列の最初の文字を繰り返し削除します。

urlencode_many_printf () {
  string=$1
  while [ -n "$string" ]; do
    tail=${string#?}
    head=${string%$tail}
    case $head in
      [-._~0-9A-Za-z]) printf %c "$head";;
      *) printf %%%02x "'$head"
    esac
    string=$tail
  done
  echo
}

printf組み込みユーティリティではなく外部ユーティリティの場合は、文字ごとに1回ではなくフル機能を1回だけ呼び出すと、再びパフォーマンスが得られます。形式とパラメータを作成してからprintf

urlencode_single_printf () {
  string=$1; format=; set --
  while [ -n "$string" ]; do
    tail=${string#?}
    head=${string%$tail}
    case $head in
      [-._~0-9A-Za-z]) format=$format%c; set -- "$@" "$head";;
      *) format=$format%%%02x; set -- "$@" "'$head";;
    esac
    string=$tail
  done
  printf "$format\\n" "$@"
}

外部呼び出しに関する限り、これは最適です(呼び出しは1つだけであり、エスケープする必要があるすべての文字を喜んで列挙しない限り、純粋なシェル構成ではこれを行うことはできません)。パラメーターのほとんどの文字を変更せずに渡す必要がある場合は、これをバッチ処理できます。

urlencode_grouped_literals () {
  string=$1; format=; set --
  while
    literal=${string%%[!-._~0-9A-Za-z]*}
    if [ -n "$literal" ]; then
      format=$format%s
      set -- "$@" "$literal"
      string=${string#$literal}
    fi
    [ -n "$string" ]
  do
    tail=${string#?}
    head=${string%$tail}
    format=$format%%%02x
    set -- "$@" "'$head"
    string=$tail
  done
  printf "$format\\n" "$@"
}

コンパイルオプションによっては、[(別名test)は外部ユーティリティです。文字列の一致にのみ使用され、構文を使用してシェルでも実行できますcasetest組み込みメソッドを避けるためにオーバーライドされた最後の2つのメソッドは、文字ごとに最初です。

urlencode_single_fork () {
  string=$1; format=; set --
  while case "$string" in "") false;; esac do
    tail=${string#?}
    head=${string%$tail}
    case $head in
      [-._~0-9A-Za-z]) format=$format%c; set -- "$@" "$head";;
      *) format=$format%%%02x; set -- "$@" "'$head";;
    esac
    string=$tail
  done
  printf "$format\\n" "$@"
}

そして、各テキストフィールドを一括コピーします。

urlencode_grouped_case () {
  string=$1; format=; set --
  while
    literal=${string%%[!-._~0-9A-Za-z]*}
    case "$literal" in
      ?*)
        format=$format%s
        set -- "$@" "$literal"
        string=${string#$literal};;
    esac
    case "$string" in
      "") false;;
    esac
  do
    tail=${string#?}
    head=${string%$tail}
    format=$format%%%02x
    set -- "$@" "'$head"
    string=$tail
  done
  printf "$format\\n" "$@"
}

私のルーター(MIPSプロセッサ、DD-WRTベースのディストリビューション、printfAshを含むBusyBox、外部、および各バージョン[は、以前のバージョンと比較して速度が大幅に向上しました。単一のフォークに移動することが最も重要な改善です。実際の長いURLパラメータに応答するのではなく、関数がほぼ瞬時に(人の観点から)応答するようにします。

上記のコードはエキゾチックな地域では失敗する可能性があります(ルーターではほとんど発生しません)。export LC_ALL=Cデフォルト以外のロケールを使用している場合は、これが必要な場合があります。

おすすめ記事