返回文章列表

Shell指令碼實作貸款計算與事件追蹤

本文將探討如何使用 Shell 指令碼實作貸款計算器和事件追蹤工具,包含貸款計算公式的指令碼實作、事件追蹤指令碼 addagenda 和 agenda 的解析,以及如何利用 Shell 指令碼調整 Unix 系統以滿足特定需求,例如為檔案新增行號和只對長行進行換行處理等實用技巧。

Shell指令碼 金融工具

在金融領域,貸款計算是一個常見的需求,而事件追蹤則有助於管理個人日程。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

內容解密:

  1. 指令碼引數處理:指令碼需要三個引數:貸款本金、年利率和貸款年限。利用 $# 檢查引數數量,若不符合要求則輸出錯誤資訊並離開。
  2. 變數定義:將輸入引數指定給變數 PIL,分別代表本金、年利率和貸款年限。
  3. 月利率計算:利用 scriptbc 命令計算月利率 J,公式為 $I / (12 * 100),表示將年利率轉換為月利率。
  4. 總期數計算:透過 $L * 12 計算總還款期數 N
  5. 每月還款金額計算:使用核心公式計算每月還款金額 M
  6. 結果格式化:將計算出的 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 期。

進一步地,可以透過擴充套件指令碼功能,例如增加互動式輸入或求解其他未知變數(如根據月供金額反推可貸款金額),來提高其實用性。

事件追蹤工具簡介

除了貸款計算器,事件追蹤工具也是一個實用的指令碼應用。透過兩個指令碼(addagendaagenda)可以實作一個簡單的日曆程式,用於記錄和管理事件。

  • addagenda 指令碼用於新增新的事件,包括週期性事件或一次性事件,並將事件資訊儲存到 $HOME/.agenda 檔案中。
  • agenda 指令碼用於檢查當前日期是否有待處理的事件。

這種工具特別適用於記錄生日、週年紀念日等重要日期,幫助使用者避免遺忘。

UNIX提醒服務:Agenda與addagenda指令碼實作

UNIX系統中,透過Shell指令碼可以實作一個簡單而有效的提醒服務。這個服務由兩個主要指令碼組成:addagendaagendaaddagenda用於新增事件至個人行事曆,而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函式檢查當日的事件。

執行結果與改進方向

執行addagendaagenda指令碼,可以實作一個基本的個人行事曆功能。未來可改進的方向包括:

  • 增加日期運算功能,以便查詢未來幾天的事件。
  • 當無事件時,輸出「今日無待辦事項」而非空白。
  • 可擴充套件為系統級的提醒服務,檢查共用的.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(或 lesspg)分頁器具有指定輸出行號的旗標。然而,在某些 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

內容解密:

  1. width=72:設定每行的最大寬度為 72 個字元。
  2. for input in "$@":遍歷所有輸入的檔案名稱。
  3. fmt "$input":使用 fmt 指令格式化輸入檔案。
  4. 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

內容解密:

  1. lines=$(wc -l < "$input" | sed 's/ //g'):計算輸入檔案的行數,並去除輸出中的空白字元。
  2. chars=$(wc -c < "$input" | sed 's/ //g'):計算輸入檔案的字元數,同樣去除空白字元。
  3. owner=$(ls -ld "$input" | awk '{print $3}'):取得檔案的擁有者名稱。
  4. while read line; do ... done < "$input":逐行讀取輸入檔案,並根據行的長度決定是否需要格式化。
  5. echo "$line" | fmt | sed -e '1s/^/ /' -e '2,$s/^/+ /':對於超出寬度限制的行,使用 fmt 進行格式化,並透過 sed 在第一行前加上兩個空格,其餘行前加上加號和一個空格,以區分原始行和換行。
  6. 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 "$@"

內容解密:

  1. while [ $# -gt 0 ]; do ... done:遍歷所有輸入的引數。
  2. case $1 in ... esac:根據第一個引數的值進行不同的處理。
  3. --group) flags="$flags -g"; shift ;;:將 --group 長引數對映為 -g 短引數,並移除該引數。
  4. exec $realquota $flags "$@":執行實際的 quota 指令,並傳入處理後的引數。