古いバージョンの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文字ずつ返されるため、非常に遅くなります。バッチ処理では、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
)は外部ユーティリティです。文字列の一致にのみ使用され、構文を使用してシェルでも実行できますcase
。test
組み込みメソッドを避けるためにオーバーライドされた最後の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ベースのディストリビューション、printf
Ashを含むBusyBox、外部、および各バージョン[
は、以前のバージョンと比較して速度が大幅に向上しました。単一のフォークに移動することが最も重要な改善です。実際の長いURLパラメータに応答するのではなく、関数がほぼ瞬時に(人の観点から)応答するようにします。
上記のコードはエキゾチックな地域では失敗する可能性があります(ルーターではほとんど発生しません)。export LC_ALL=C
デフォルト以外のロケールを使用している場合は、これが必要な場合があります。