在系統管理中,處理時區和溫度轉換是常見的需求。本文介紹的 timein 指令碼提供了一個便捷的時區查詢方案,並探討瞭如何擴充套件其功能以比較時區差異。此外,我們還將探討如何構建命令列計算器和溫度轉換工具,提升日常工作效率。這些工具都根據 Shell 指令碼,展現了其在處理日常任務時的靈活性和便捷性。

最佳化時區查詢指令:timein 指令碼詳解

在管理全球網路的系統管理員日常工作中,查詢不同時區的時間是一項常見需求。timein 是一個用於查詢特定時區當前時間的 Shell 指令碼。本篇文章將深入分析該指令碼的實作細節,並探討如何擴充套件其功能。

指令碼原始碼解析

#!/bin/bash
# timein:顯示指定時區或地理區域的當前時間。若無引數,則顯示 UTC/GMT 時間。
# 使用 "list" 引數可列出已知地理區域。
# 時區資料函式庫參考:http://www.twinsun.com/tz/tz-link.htm

zonedir="/usr/share/zoneinfo"
if [ ! -d $zonedir ] ; then
    echo "在 $zonedir 找不到時區資料函式庫。" >&2
    exit 1
fi

if [ -d "$zonedir/posix" ] ; then
    zonedir=$zonedir/posix # 現代 Linux 系統的處理
fi

if [ $# -eq 0 ] ; then
    timezone="UTC"
    mixedzone="UTC"
elif [ "$1" = "list" ] ; then
    ( echo "列出本系統上定義的所有已知時區和區域:"
      cd $zonedir
      find -L * -type f -print | xargs -n 2 | \
      awk '{ printf " %-38s %-38s\n", $1, $2 }'
    ) | more
    exit 0
else
    region="$(dirname $1)"
    zone="$(basename $1)"
    # 檢查給定的時區是否直接匹配,若無則進一步查詢。
    matchcnt="$(find -L $zonedir -name $zone -type f -print | wc -l | sed 's/[^[:digit:]]//g' )"
    
    # #### 內容解密:
    # 這段程式碼檢查使用者輸入的時區名稱是否在時區資料函式庫中有直接匹配的專案。
    # `find` 命令用於在 `$zonedir` 目錄下遞迴查詢與 `$zone` 匹配的檔案,並統計匹配數量。
    # 若匹配數量大於 1,表示有多個時區與輸入名稱匹配,指令碼將輸出錯誤訊息並離開。

    if [ "$matchcnt" -gt 0 ] ; then
        if [ $matchcnt -gt 1 ] ; then
            echo "\"$zone\" 匹配多個可能的時區記錄。" >&2
            echo "請使用 'list' 檢視所有已知區域和時區。" >&2
            exit 1
        fi
        match="$(find -L $zonedir -name $zone -type f -print)"
        mixedzone="$zone"
    else 
        # 若無直接匹配,則嘗試在指定區域中查詢時區。
        mixedregion="$(echo ${region%${region#?}} | tr '[[:lower:]]' '[[:upper:]]')$(echo ${region#?} | tr '[[:upper:]]' '[[:lower:]]')"
        mixedzone="$(echo ${zone%${zone#?}} | tr '[[:lower:]]' '[[:upper:]]')$(echo ${zone#?} | tr '[[:upper:]]' '[[:lower:]]')"
        
        # #### 內容解密:
        # 這裡進行大小寫轉換,以確保輸入的區域和時區名稱符合時區資料函式庫的命名慣例。
        # 例如,將 "asia/taipei" 轉換為 "Asia/Taipei"。

        if [ "$mixedregion" != "." ] ; then
            match="$(find -L $zonedir/$mixedregion -type f -name $mixedzone -print)"
        else
            match="$(find -L $zonedir -name $mixedzone -type f -print)"
        fi
        
        # #### 內容解密:
        # 若指定了區域,則在該區域下查詢匹配的時區檔案;否則,在整個時區資料函式庫中查詢。

        if [ -z "$match" ] ; then
            if [ ! -z $(find -L $zonedir -name $mixedzone -type d -print) ] ; then
                echo "區域 \"$1\" 有多個時區。" >&2
            else 
                echo "無法找到與 \"$1\" 精確匹配的時區。" >&2
            fi
            echo "請使用 'list' 檢視所有已知區域和時區。" >&2
            exit 1
        fi
    fi
    timezone="$match"
fi

nicetz=$(echo $timezone | sed "s|$zonedir/||g") 
echo "現在是 $(TZ=$timezone date '+%A, %B %e, %Y, at %l:%M %p')$nicetz"

# #### 內容解密:
# 最後,利用 `date` 命令顯示指定時區的當前時間,並格式化輸出。

exit 0

功能說明與改進方向

  • 核心功能:該指令碼利用 date 命令查詢指定時區的當前時間。大部分複雜度來自於對使用者輸入的時區名稱進行模糊匹配,以提高使用者經驗。
  • 錯誤處理:對於不明確或錯誤的輸入,指令碼提供了友善的錯誤訊息,幫助使用者修正輸入。
  • 擴充套件方向:可根據此指令碼開發 tzdiff,用於比較兩個時區的時間差。這需要接受兩個引數,分別查詢對應時區的時間,並計算時間差。

執行範例與輸出結果

$ timein
現在是 Wednesday, April 5, 2017, at 4:00 PM 在 UTC

$ timein London
現在是 Wednesday, April 5, 2017, at 5:00 PM 在 Europe/London

$ timein Brazil
區域 "Brazil" 有多個時區。請使用 'list' 檢視所有已知區域和時區。

$ timein Pacific/Honolulu
現在是 Wednesday, April 5, 2017, at 6:00 AM 在 Pacific/Honolulu

$ timein WET
現在是 Wednesday, April 5, 2017, at 5:00 PM 在 WET

$ timein mycloset
無法找到與 "mycloset" 精確匹配的時區。請使用 'list' 檢視所有已知區域和時區。

建立實用工具

建立 Shell 指令碼的主要目的之一是將複雜的命令列序列放入檔案中,使其可重複使用且易於調整。使用者命令散佈在整本文中,這不足為奇。令人驚訝的是,我們尚未為 Linux、Solaris 和 OS X 系統上的每個命令編寫包裝器。

Linux/Unix 是唯一一個可以決定不喜歡命令的預設旗標並透過幾次按鍵永久修復它的主要作業系統,或者透過使用別名或十幾行指令碼來模擬其他作業系統上最喜歡的實用工具的行為。這就是使 Unix 如此有趣的原因,也是我們撰寫這本文的主要原因!

#22 提醒實用工具

Windows 和 Mac 使用者多年來一直使用像 Stickies 這樣的簡單實用工具,這些簡化的應用程式讓您可以在螢幕上保留小筆記和提醒。它們非常適合記下電話號碼或其他提醒。不幸的是,如果您想在 Unix 命令列上記筆記,就沒有類別似的工具,但這個問題可以透過這兩個指令碼輕易解決。

第一個指令碼 remember (如列表 3-1 所示)讓您可以輕鬆地將資訊片段儲存到家目錄中的單一 rememberfile 中。如果在沒有任何引數的情況下呼叫,它會讀取標準輸入直到按下 CTRL-D 給出檔案結束序列。如果帶有引數呼叫,它會直接將這些引數儲存到資料檔案中。

這對指令碼的另一半是 remindme ,如列表 3-2 所示,當沒有給出引數時,它會顯示整個 rememberfile 的內容,或者使用引數作為模式搜尋其中的內容並顯示結果。

程式碼

#!/bin/bash
# remember--An easy command line-based reminder pad
rememberfile="$HOME/.remember"
if [ $# -eq 0 ] ; then
  # Prompt the user for input and append whatever they write to
  # the rememberfile.
  echo "Enter note, end with ^D: "
  cat - >> $rememberfile
else
  # Append any arguments passed to the script on to the .remember file.
  echo "$@" >> $rememberfile
fi
exit 0
#!/bin/bash
# remindme--Searches a data file for matching lines or, if no
# argument is specified, shows the entire contents of the data file
rememberfile="$HOME/.remember"
if [ ! -f $rememberfile ] ; then
  echo "$0: You don't seem to have a .remember file. " >&2
  echo "To remedy this, please use 'remember' to add reminders" >&2
  exit 1
fi
if [ $# -eq 0 ] ; then
  # Display the whole rememberfile when not given any search criteria.
  more $rememberfile
else
  # Otherwise, search through the file for the given terms, and display
  # the results neatly.
  grep -i -- "$@" $rememberfile | ${PAGER:-more}
fi
exit 0

#### 內容解密:

  • remember 指令碼可以用作互動式程式,請求使用者輸入要記住的詳細資訊,也可以接受命令列引數來儲存。
  • 如果使用者沒有傳遞任何引數給指令碼,則使用 cat 從標準輸入讀取資料,直到使用者按下 CTRL-D。
  • 如果指定了引數,則將所有引數直接附加到 rememberfile 中。
  • remindme 指令碼首先檢查 rememberfile 是否存在,如果不存在則離開並顯示錯誤訊息。
  • 如果沒有給出引數,則顯示整個 rememberfile 的內容;否則,使用 grep 搜尋匹配的內容並顯示結果。

執行指令碼

要使用 remindme 工具,首先使用 remember 指令碼新增筆記、電話號碼或其他內容到 rememberfile 中,如列表 3-3 所示。然後使用 remindme 搜尋這個自由形式的資料函式庫,指定任意長或短的模式。

結果範例

$ remember Southwest Airlines: 800-IFLYSWA
$ remember
Enter note, end with ^D:
Find Dave's film reviews at http://www.DaveOnFilm.com/
^D
$ remindme film reviews
Find Dave's film reviews at http://www.DaveOnFilm.com/
$ remindme 800
Southwest Airlines: 800-IFLYSWA

改進指令碼的方法

這些指令碼可以透過多種方式改進,例如引入記錄的概念:每個 remember 條目都有時間戳記,多行輸入可以儲存為單一記錄,可以使用正規表示式進行搜尋。這種方法讓您可以儲存一組人的電話號碼,並透過記住其中一個人的名字來檢索它們。如果您真的很熱衷於寫指令碼,您可能還需要包含編輯和刪除功能。

#23 互動式計算器

如果您記得的話,scriptbc (第 34 頁的指令碼 #9)允許我們將浮點數 bc 計算作為內聯命令引數呼叫。下一步是編寫一個包裝器指令碼,將這個指令碼轉換為完全互動式的命令列計算器。這個指令碼(如列表 3-6 所示)最終變得非常簡短!請確保 scriptbc 指令碼在 PATH 中,否則這個指令碼將無法執行。

程式碼(待補充)

# 待補充的程式碼內容,將根據上下文進行合適的原創撰寫與解說。

#### 內容解密:

  • (待補充程式碼的詳細解說,涵蓋程式邏輯、設計考量、技術原理及潛在改進點。)

命令列計算器與溫度轉換實用工具

本章介紹了兩個實用的命令列工具:計算器(calc)與溫度轉換工具(convertatemp)。這兩個工具展示瞭如何利用shell腳原本簡化常見的數學運算任務。

命令列計算器(calc)

程式碼解析

#!/bin/bash
# calc--命令列計算器,前端介面至bc
scale=2
show_help()
{
cat << EOF
除了標準數學函式外,calc 還支援:
a % b a除以b的餘數
a ^ b 指數運算:a 的 b 次方
s(x) x 的正弦,x 以弧度表示
c(x) x 的餘弦,x 以弧度表示
a(x) x 的反正切,結果以弧度表示
l(x) x 的自然對數
e(x) e 的 x 次方
j(n,x) x 的 n 階貝塞爾函式
scale N 顯示 N 位小數(預設 = 2)
EOF
}
if [ $# -gt 0 ] ; then
exec scriptbc "$@"
fi
echo "Calc--簡單的計算器。輸入 'help' 取得幫助,輸入 'quit' 離開。"
/bin/echo -n "calc> "
while read command args
do
case $command
in
quit|exit) exit 0 ;;
help|\?) show_help ;;
scale) scale=$args ;;
*) scriptbc -p $scale "$command" "$args" ;;
esac
/bin/echo -n "calc> "
done
echo ""
exit 0

內容解密:

  1. scale=2 設定預設的小數位數為2。
  2. show_help() 函式顯示計算器支援的數學運算和函式。
  3. 當命令列引數存在時,直接呼叫 scriptbc 執行運算。
  4. 否則,進入互動模式,接受使用者輸入的命令和引數。
  5. 使用 case 陳述句處理不同的命令,例如 quithelpscale
  6. 對未知的命令,直接呼叫 scriptbc 進行運算。

執行與測試

該指令碼提供了一個互動式的計算環境。使用者可以輸入數學運算式進行計算,或輸入 help 檢視支援的函式。

溫度轉換工具(convertatemp)

程式碼解析

#!/bin/bash
# convertatemp--溫度轉換指令碼,可在華氏、攝氏和克氏溫度間轉換
if [ $# -eq 0 ] ; then
cat << EOF >&2
用法:$0 溫度[F|C|K]
字尾表示溫度單位:
F 華氏(預設)
C 攝氏
K 克氏
EOF
exit 1
fi

unit="$(echo $1|sed -e 's/[-[:digit:]]*//g' | tr '[:lower:]' '[:upper:]' )"
temp="$(echo $1|sed -e 's/[^-[:digit:]]*//g')"
case ${unit:=F}
in
F ) farn="$temp"; cels="$(echo "scale=2;($farn - 32) / 1.8" | bc)"; kelv="$(echo "scale=2;$cels + 273.15" | bc)" ;;
C ) cels=$temp; kelv="$(echo "scale=2;$cels + 273.15" | bc)"; farn="$(echo "scale=2;(1.8 * $cels) + 32" | bc)" ;;
K ) kelv=$temp; cels="$(echo "scale=2; $kelv - 273.15" | bc)"; farn="$(echo "scale=2; (1.8 * $cels) + 32" | bc)" ;;
*) echo "不支援的溫度單位"; exit 1 ;;
esac

echo "華氏 = $farn"
echo "攝氏 = $cels"
echo "克氏 = $kelv"
exit 0

內容解密:

  1. 使用正規表示式從輸入中提取溫度和單位。
  2. 使用 case 陳述句根據不同的單位進行溫度轉換。
  3. 對每種單位,使用 bc 命令進行數學運算,以實作精確的溫度轉換。
  4. 輸出轉換後的溫度值,包括華氏、攝氏和克氏三種單位。

執行與測試

使用者可以透過指定溫度值和單位來進行轉換,例如 100C212F。該指令碼會輸出對應的其他兩個溫度單位的值。