古いバージョンの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 ""
}
これは多かれ少なかれうまく機能しますが、いくつかの欠陥があります。
私のバージョンのbashは、この$ {var:x:y}のような部分文字列をサポートしていません。
[TL、DR:最後のコードブロックでurlencode_grouped_case
バージョンを使用します。]
Awkは、文字からその数字に変換する方法が煩わしく欠けていることを除いて、ほとんどの仕事を行うことができます。 od
がデバイスに存在する場合は、それを使用してすべての文字(より正確にはバイト)を対応する数値(awkが読み取れるように10進数で記述)に変換してから、awkを使用して有効な変換を行うことができます文字をリテラルに戻し、引用符で囲まれた文字を適切な形式に戻します。
urlencode_od_awk () {
echo "$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
を1回呼び出します。
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" "$@"
}
これは、外部呼び出しの観点から最適です(単一の呼び出しがあり、エスケープする必要のあるすべての文字を列挙する必要がない限り、純粋なShell構造でそれを行うことはできません)。引数のほとんどの文字を変更せずに渡す場合は、それらをバッチで処理できます。
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ベースのディストリビューション、ashを使用したBusyBox、外部printf
および[
)でテストしました。各バージョンは、前のバージョンに比べて速度が大幅に向上しています。シングルフォークへの移行は最も重要な改善です。これは、現実的な長いURLパラメーターの数秒後とは対照的に、関数をほぼ瞬時に(人間の観点から)応答させるものです。