urlencode 函式
我需要一種在執行舊版本busybox的OpenWRT設備上使用shell腳本對字元串進行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 "" }
這或多或少都很好,但有一些缺陷:
- 某些字元會被跳過,例如“\”。
- 結果是逐個字元返回,所以它非常慢。對批處理中的幾個字元串進行 url 編碼大約需要 20 秒。
我的 bash 版本不支持 ${var:x:y} 這樣的子字元串。
[TL,DR:使用
urlencode_grouped_case
最後一個程式碼塊中的版本。]awk 可以完成大部分工作,只是它令人討厭地缺少將字元轉換為數字的方法。如果
od
存在於您的設備上,您可以使用它將所有字元(更準確地說,字節)轉換為相應的數字(以十進制編寫,以便 awk 可以讀取它),然後使用 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
,你可以在 shell 內做任何事情;這將顯著提高性能(減少對外部程序的呼叫——如果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
不是內置工具而是外部工具,您將再次獲得性能,只需為整個函式呼叫一次,而不是每個字元呼叫一次。建立格式和參數,然後對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" "$@" }
這在外部呼叫方面是最佳的(只有一個,除非您願意列舉所有需要轉義的字元,否則您不能使用純 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" "$@" }
根據編譯選項,
[
(akatest
) 可能是一個外部實用程序。我們僅將它用於字元串匹配,這也可以在 shell 中使用case
構造完成。以下是為避免test
內置函式而重寫的最後兩種方法,首先是逐個字元: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 參數。請注意,上面的程式碼在花哨的語言環境中可能會失敗(不太可能在路由器上)。
export LC_ALL=C
如果您使用非預設語言環境,您可能需要。