在金融領域,貸款計算是一個常見的需求,而事件追蹤則有助於管理個人日程。Shell 指令碼提供了一個簡潔有效的途徑來實作這些功能。本文將會探討如何利用 Shell 指令碼編寫貸款計算器和事件追蹤工具,並進一步探討如何利用指令碼調校 Unix 系統,使其更符合個人使用習慣。核心內容包含貸款計算公式的指令碼轉換、日期處理技巧、以及如何利用迴圈和條件判斷來實作特定的功能。此外,文章也將探討如何透過指令碼增強現有 Unix 工具的功能,例如調整 fmt 指令使其保留原始換行、擴充套件 cat 指令以顯示更多檔案資訊,以及統一處理 quota 指令的引數等。
貸款計算器指令碼解析與實作
貸款計算是金融領域中常見的任務,透過 shell 指令碼實作貸款計算器可以簡化這一過程。本文將探討如何利用 shell 指令碼,根據貸款本金、利率和貸款期限計算每月的還款金額。
計算公式與實作
貸款計算的核心公式為:$M = P * (J / (1 - (1 + J)^{-N}))$,其中:
- $P$ 代表貸款本金
- $J$ 代表月利率
- $N$ 代表還款總期數(月份)
透過 shell 指令碼實作這一公式,需要進行適當的變數定義和計算。
#!/bin/bash
# loancalc--根據貸款本金、利率和貸款期限(年)計算每期還款金額
# 公式:M = P * (J / (1 - (1 + J) ^ -N)),
# 其中 P = 本金, J = 月利率, N = 貸款期限(月)
. library.sh # 參照指令碼函式庫
if [ $# -ne 3 ]; then
echo "用法:$0 本金 利率 貸款年限" >&2
exit 1
fi
P=$1 I=$2 L=$3
J=$(scriptbc -p 8 $I / \( 12 \* 100 \) )
N=$(( $L * 12 ))
M=$(scriptbc -p 8 $P \* \( $J / \(1 - \(1 + $J\) \^ -$N\) \) )
# 格式化輸出結果
dollars=$(echo $M | cut -d. -f1)
cents=$(echo $M | cut -d. -f2 | cut -c1-2)
cat << EOF
$L 年期貸款,利率 $I%,本金 $(nicenumber $P 1),
每月還款 \$${dollars}.${cents},共計 $N 期。
EOF
exit 0
內容解密:
- 指令碼引數處理:指令碼需要三個引數:貸款本金、年利率和貸款年限。利用
$#檢查引數數量,若不符合要求則輸出錯誤資訊並離開。 - 變數定義:將輸入引數指定給變數
P、I和L,分別代表本金、年利率和貸款年限。 - 月利率計算:利用
scriptbc命令計算月利率J,公式為$I / (12 * 100),表示將年利率轉換為月利率。 - 總期數計算:透過
$L * 12計算總還款期數N。 - 每月還款金額計算:使用核心公式計算每月還款金額
M。 - 結果格式化:將計算出的
M分離為整數部分(美元)和小數部分(美分),並格式化輸出。
指令碼應用與擴充套件
該指令碼可用於計算不同貸款條件下的每月還款金額。例如,透過比較不同貸款年限下的還款金額,可以幫助使用者選擇合適的貸款方案。
$ loancalc 44900 4.75 4
4 年期貸款,利率 4.75%,本金 44,900,
每月還款 \$1028.93,共計 48 期。
$ loancalc 44900 4.75 5
5 年期貸款,利率 4.75%,本金 44,900,
每月還款 \$842.18,共計 60 期。
進一步地,可以透過擴充套件指令碼功能,例如增加互動式輸入或求解其他未知變數(如根據月供金額反推可貸款金額),來提高其實用性。
事件追蹤工具簡介
除了貸款計算器,事件追蹤工具也是一個實用的指令碼應用。透過兩個指令碼(addagenda 和 agenda)可以實作一個簡單的日曆程式,用於記錄和管理事件。
addagenda指令碼用於新增新的事件,包括週期性事件或一次性事件,並將事件資訊儲存到$HOME/.agenda檔案中。agenda指令碼用於檢查當前日期是否有待處理的事件。
這種工具特別適用於記錄生日、週年紀念日等重要日期,幫助使用者避免遺忘。
UNIX提醒服務:Agenda與addagenda指令碼實作
UNIX系統中,透過Shell指令碼可以實作一個簡單而有效的提醒服務。這個服務由兩個主要指令碼組成:addagenda和agenda。addagenda用於新增事件至個人行事曆,而agenda則用於查詢當日或特定日期的事件。
addagenda指令碼解析
功能概述
addagenda指令碼負責接收使用者輸入的事件日期和描述,並將其格式化後儲存至使用者的.agenda檔案中。
程式碼重點
isDayName()
{
case $(echo $1 | tr '[[:upper:]]' '[[:lower:]]') in
sun*|mon*|tue*|wed*|thu*|fri*|sat*) retval=0 ;;
* ) retval=1 ;;
esac
return $retval
}
內容解密:
此函式檢查輸入是否為有效的星期名稱。它將輸入轉換為小寫並與已知的星期縮寫進行匹配。如果匹配成功,則傳回0(真);否則傳回1(假)。這確保了日期輸入的正確性。
normalize()
{
# Return string with first char uppercase, next two lowercase.
/bin/echo -n $1 | cut -c1 | tr '[[:lower:]]' '[[:upper:]]'
echo $1 | cut -c2-3| tr '[[:upper:]]' '[[:lower:]]'
}
內容解密:
此函式將輸入字串格式化為首字母大寫,其餘兩個字母小寫。這對於統一日期格式(如星期和月份的縮寫)至關重要,以確保與date命令輸出的格式一致。
date="$word1$word2$year"
echo "$(echo $date|sed 's/ //g')|$description" >> $agendafile
內容解密:
這段程式碼將格式化後的日期和事件描述寫入.agenda檔案。日期被進一步處理以去除空格,並與描述一起以「日期|描述」的格式儲存。
agenda指令碼解析
功能概述
agenda指令碼檢查.agenda檔案中是否有與當日日期相匹配的事件,並將其顯示給使用者。
程式碼重點
checkDate()
{
weekday=$1 day=$2 month=$3 year=$4
format1="$weekday" format2="$day$month" format3="$day$month$year"
while read date description ; do
if [ "$date" = "$format1" -o "$date" = "$format2" -o \
"$date" = "$format3" ]
then
echo " $description"
fi
done < $agendafile
}
內容解密:
此函式根據傳入的日期引數(星期、日期、月份、年份)生成三種不同的日期格式,並與.agenda檔案中的每一行進行比較。如果找到匹配的日期,則輸出對應的事件描述。
eval $(date '+weekday="%a" month="%b" day="%e" year="%G"')
day="$(echo $day|sed 's/ //g')"
checkDate $weekday $day $month $year
內容解密:
這裡使用eval來一次性取得日期命令的多個輸出值,避免了因多次呼叫date命令而導致的潛在錯誤。接著,去除日期中的前導空格,並呼叫checkDate函式檢查當日的事件。
執行結果與改進方向
執行addagenda和agenda指令碼,可以實作一個基本的個人行事曆功能。未來可改進的方向包括:
- 增加日期運算功能,以便查詢未來幾天的事件。
- 當無事件時,輸出「今日無待辦事項」而非空白。
- 可擴充套件為系統級的提醒服務,檢查共用的
.agenda檔案。
這些改進可以透過擴充現有的指令碼功能或結合其他UNIX工具(如GNU date命令)來實作,從而增強其實用性和靈活性。
調整 Unix 系統以滿足特定需求
Unix 系統在不同版本之間可能存在著顯著的差異,儘管它們都遵循 POSIX 標準。對於經常使用多個 Unix 系統的使用者來說,這種差異性可能會帶來一些挑戰。例如,大多數 Unix 或 Linux 系統都包含 ls 命令,但並非所有版本都支援 --color 旗標。同樣地,不同版本的 Bourne shell 對某些語法特性的支援也可能有所不同,例如變數切片(${var:0:2})。
利用 Shell 指令碼進行系統調整
Shell 指令碼的一個非常有價值的用途是調整特定的 Unix 版本,使其更接近其他系統。雖然大多數現代 GNU 工具在非 Linux 的 Unix 系統上運作良好(例如,用新的 GNU tar 取代舊的 tar),但系統更新往往不需要如此大規模的變動。使用 shell 指令碼可以將流行的旗標對映到本地等效的旗標,利用 Unix 的核心功能來建立更智慧的命令版本,甚至解決某些功能長期缺失的問題。
為檔案新增行號
有多種方法可以為顯示的檔案新增行號,其中一些非常簡短。例如,可以使用 awk 命令實作:
awk '{ print NR": "$0 }' < inputfile
在某些 Unix 版本中,cat 命令具有 -n 旗標,而在其他版本中,more(或 less、pg)分頁器具有指定輸出行號的旗標。然而,在某些 Unix 版本中,這些方法可能無法正常運作,此時可以使用以下指令碼(清單 4-1)來完成任務。
numberlines 指令碼
#!/bin/bash
# numberlines--一個簡單的 cat -n 等替代方案
for filename in "$@"
do
linecount="1"
while IFS="\n" read line
do
echo "${linecount}: $line"
linecount="$(( $linecount + 1 ))"
done < $filename
done
exit 0
numberlines 指令碼解析
這個指令碼的主迴圈有一個技巧:它看起來像一個普通的 while 迴圈,但關鍵部分實際上是 done < $filename。事實證明,每個主要區塊結構都充當其自己的虛擬子 shell,因此這種檔案重新導向不僅有效,而且是一種簡單的方法,可以讓迴圈逐行迭代 $filename 的內容。結合 read 陳述式(將每一行載入 line 變數),就可以輕鬆輸出帶有行號的行,並遞增 linecount 變數。
只對長行進行換行處理
fmt 命令及其等效的 shell 指令碼(第 53 頁的指令碼 #14)的一個限制是,它們會對遇到的每一行進行換行和填充,無論是否合理。這可能會破壞電子郵件(例如,換行 .signature 就不好)以及任何需要保留換行符的輸入檔案格式。
toolong 指令碼
#!/bin/bash
# toolong--只將輸入串流中長度超過指定長度的行饋送給 fmt 命令
width=72
if [ ! -r "$1" ] ; then
echo "無法讀取檔案 $1" >&2
echo "用法:$0 檔名" >&2
exit 1
fi
while read input
do
if [ ${#input} -gt $width ] ; then
echo "$input" | fmt
else
echo "$input"
fi
done < $1
exit 0
toolong 指令碼解析
檔案透過簡單的 < $1 與迴圈末端相關聯,每一行都可以透過 read input 陳述式進行分析,該陳述式將檔案的每一行逐一指定給 input 變數。如果 shell 不支援 ${#var} 表示法,可以使用非常有用的「字數統計」命令 wc 來模擬其行為:
varlength="$(echo "$var" | wc -c | sed 's/[^[:digit:]]//g')"
這種方法可以計算變數的長度,同時避免 wc 輸出中的空格問題。透過這種方式,可以實作只對長行進行換行處理的功能,使輸出更加符合預期。
調校 Unix 工具的實用技巧
在 Unix 及 Linux 系統中,有許多強大的命令列工具可供使用,但有時這些工具的輸出結果或使用方式可能不完全符合我們的預期。本篇文章將介紹如何透過撰寫 shell 指令碼,來增強或修改現有的 Unix 工具,使其更符合我們的實際需求。
增強 fmt 指令的 toolong 指令碼
首先,我們來看看 toolong 指令碼,它是一個對 fmt 指令的增強。fmt 指令用於格式化文字,使其符合特定的寬度限制,但預設情況下,它不會保留原始的換行符號。toolong 指令碼則改進了這一點,使其在格式化長行的同時,盡量保留原始的換行。
程式碼解析
#!/bin/bash
# toolong - 增強 fmt 指令,使其保留原始換行
width=72
for input in "$@"; do
fmt "$input" | sed -e '1s/^/ /' -e '2,$s/^/ /'
done
內容解密:
width=72:設定每行的最大寬度為 72 個字元。for input in "$@":遍歷所有輸入的檔案名稱。fmt "$input":使用fmt指令格式化輸入檔案。sed -e '1s/^/ /' -e '2,$s/^/ /':在輸出的每一行前加上四個空格,以保持縮排的一致性。
新增檔案資訊顯示的 showfile 指令碼
接下來,我們來介紹 showfile 指令碼,它是一個替代 cat 指令的工具,不但顯示檔案內容,還會顯示額外的檔案資訊,如行數、字元數及檔案擁有者。
程式碼解析
#!/bin/bash
# showfile - 顯示檔案內容及額外資訊
width=72
for input; do
lines=$(wc -l < "$input" | sed 's/ //g')
chars=$(wc -c < "$input" | sed 's/ //g')
owner=$(ls -ld "$input" | awk '{print $3}')
echo "
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
--"
echo "File $input ($lines lines, $chars characters, owned by $owner):"
echo "
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
--"
while read line; do
if [ ${#line} -gt $width ]; then
echo "$line" | fmt | sed -e '1s/^/ /' -e '2,$s/^/+ /'
else
echo " $line"
fi
done < "$input"
echo "
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
--"
done | ${PAGER:-more}
exit 0
內容解密:
lines=$(wc -l < "$input" | sed 's/ //g'):計算輸入檔案的行數,並去除輸出中的空白字元。chars=$(wc -c < "$input" | sed 's/ //g'):計算輸入檔案的字元數,同樣去除空白字元。owner=$(ls -ld "$input" | awk '{print $3}'):取得檔案的擁有者名稱。while read line; do ... done < "$input":逐行讀取輸入檔案,並根據行的長度決定是否需要格式化。echo "$line" | fmt | sed -e '1s/^/ /' -e '2,$s/^/+ /':對於超出寬度限制的行,使用fmt進行格式化,並透過sed在第一行前加上兩個空格,其餘行前加上加號和一個空格,以區分原始行和換行。done | ${PAGER:-more}:將輸出透過分頁程式顯示,預設為more。
使用 quota 指令時的引數統一處理
最後,我們來看看如何統一處理 quota 指令的引數。由於不同 Unix 及 Linux 系統對 quota 指令的引數支援不同,本指令碼提供了一個前端,允許使用者使用長引數(如 --group、--verbose 等),並將其對映為對應的單字母引數。
程式碼解析
#!/bin/bash
# newquota - 統一處理 quota 指令的引數
flags=""
realquota=$(which quota)
while [ $# -gt 0 ]; do
case $1 in
--help) echo "Usage: $0 [--group --verbose --quiet -gvq]" >&2; exit 1 ;;
--group) flags="$flags -g"; shift ;;
--verbose) flags="$flags -v"; shift ;;
--quiet) flags="$flags -q"; shift ;;
--) shift; break ;;
*) break ;;
esac
done
exec $realquota $flags "$@"
內容解密:
while [ $# -gt 0 ]; do ... done:遍歷所有輸入的引數。case $1 in ... esac:根據第一個引數的值進行不同的處理。--group) flags="$flags -g"; shift ;;:將--group長引數對映為-g短引數,並移除該引數。exec $realquota $flags "$@":執行實際的quota指令,並傳入處理後的引數。