星期六, 4月 07, 2012

使用 Shell Script 進行 URL 編/解碼

一段時間之前,我寫了個利用 GNU coreutils 中的 "printf" 程式來進行 URL 解碼的 script 叫作 "urldecode"。今天本來想寫個使用 tinyurl.com 來產生短網址的 script,不過這樣的 script 必須要用到 URL 編碼來把傳給 tinyurl 的網址資料先編碼才行。在查了維基百科的 "Percent-encoding" 頁面之後,我寫了個用來做 URL 編碼的 script 叫 "urlencode


首先來看解碼的部份。解碼是相對簡單的事情,因為 "printf" 接受 "\xHH" 這樣的格式字串("HH" 是一位或兩位數的16位元數值),所以只要先把編碼字串中的 '%' 換成 "\x" 字串後,再整個丟給 "printf" 即可。以下是我的實作:


#!/bin/bash
#
# urldecode - decoding the URL-encoded string
#
# (C)2010 Shang-Feng Yang <storm_DOT_sfyang_AT_gmail_DOT_com>
#
# License: GPLv3

ENC_STR=$@
[ "${ENC_STR}x" == "x" ] && {
TMP_STR="$(cat - | sed -e 's/%/\\x/g')"
} || {
TMP_STR="$(echo ${ENC_STR} | sed -e 's/%/\\x/g')"
}
PRINTF=/usr/bin/printf
exec ${PRINTF} "${TMP_STR}\n"

"urldecode" 程式接受由標準輸入串流中或從命令參數中傳入目標字串。不過這個程式有個明顯的問題,那就是因為是把前處理過的字串整個丟給 "printf" 當作格式字串,所以當輸入字串太長時,程式會發生錯誤。

至於編碼的部份就相對比較麻煩了。我最初的想法是掃描輸入字串找出保留字元,把這些字元轉換後,再代換進輸入字串中。為了做字元轉換,我寫了個叫作 "char2hex" 的 script 來把目標字元轉換成對應的 ASCII 碼的 16 進位數值:


#!/bin/bash
#
# char2hex - returning the hexadecimal value of the given characters
#
# (C)2012 Shang-Feng Yang <storm_DOT_sfyang_AT_gmail_DOT_com>
#
# License: GPLv3

function usage() {
echo -e "Usage:\n"
echo -e "\t$(basename $0) CHARACTER(S)_TO_CONVERT\n"
}

CHAR=$1

[ "x${CHAR}" == "x" ] && { usage; exit 1; }

echo -n "${CHAR}" | od -A n -t x1 | tr -d ' '

這個 script 的運作方式相當直觀,唯一需要稍微提一下的是 "echo" 需要加上 '-n' 選項的原因。"echo" 預設會在輸出的最後加上一個換行字元,所以如果不加 '-n' 選項的話,輸出的結果在結尾會多個 "0a" 在後面。加上 '-n' 選項會關閉這個預設行為。

這種方法似乎相當簡單,但是實際上實作可能就不是那麼容易了。也許是我太笨想不到解法,不過像是光要從輸入字串中挑出保留字元,我就想不到有什麼簡單又不會因為大幅度增加 I/O 導致程式跑得很沒效率的方法。對像我這種懶惰的人來說,為了這個不是主要目的的問題想破頭,顯然不是個可行的辦法。

在看過維基百科 "Percent-encoding" 頁面的 "Percent-encoding reserved characters" 和 "Percent-encoding the percent character" 兩個段落後,發現必須進行編碼的保留字元其實並不多,所以最直接簡單的辦法是使用相對無腦的『列表法』來實作:


#!/bin/bash
#
# urlencode - escaping the reserved characters using URL-encoding
#
# (C)2012 Shang-Feng Yang <storm_DOT_sfyang_AT_gmail_DOT_com>
#
# License: GPLv3

STR=$@
[ "${STR}x" == "x" ] && { STR="$(cat -)"; }

echo ${STR} | sed -e 's| |%20|g' \
-e 's|!|%21|g' \
-e 's|#|%23|g' \
-e 's|\$|%24|g' \
-e 's|%|%25|g' \
-e 's|&|%26|g' \
-e "s|'|%27|g" \
-e 's|(|%28|g' \
-e 's|)|%29|g' \
-e 's|*|%2A|g' \
-e 's|+|%2B|g' \
-e 's|,|%2C|g' \
-e 's|/|%2F|g' \
-e 's|:|%3A|g' \
-e 's|;|%3B|g' \
-e 's|=|%3D|g' \
-e 's|?|%3F|g' \
-e 's|@|%40|g' \
-e 's|\[|%5B|g' \
-e 's|]|%5D|g'

"urlencode" 的內容相當簡單不需要多做解釋。這個 script 同樣可以使用 STDIN 資料流和命令列參數的方式來傳入輸入字串。下面是使用這兩個 script 的一些範例:


$ urlencode http://en.wikipedia.org/wiki/Percent-encoding
http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FPercent-encoding
$ echo 'http://en.wikipedia.org/wiki/Percent-encoding' |urlencode
http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FPercent-encoding
$ urldecode $(urlencode http://en.wikipedia.org/wiki/Percent-encoding)
http://en.wikipedia.org/wiki/Percent-encoding
$ urlencode http://en.wikipedia.org/wiki/Percent-encoding |urldecode
http://en.wikipedia.org/wiki/Percent-encoding

註:由於應 blogger 要求更新了 blogger 底層的 template,所以上面的程式碼和終端機區塊的排版有點問題...我未來如果有足夠動力的話也許會去改 CSS 把這個問題修好...

繼續閱讀全文