在 UNIX 系統管理和程式開發中,Shell Script 是不可或缺的工具,它能自動化繁瑣的任務、提升效率。本文除了探討 Shell Script 的進階技巧,例如函式的引數傳遞與傳回、訊號捕捉、陣列操作等,也提供多個實務案例,包含判斷閏年、處理檔案內容、進行數值計算等,讓讀者能將 Shell Script 應用於日常工作。此外,本文也涵蓋了 UNIX 系統開發的關鍵技術,從系統核心結構、I/O 模型到記憶體管理、檔案系統操作,以及網路通訊、環境變數設定等,都有詳細的解說。同時也探討了使用者與群組管理、流程控制、常見語法錯誤和錯誤處理機制,並搭配流程圖和程式碼範例,讓讀者更輕鬆理解和運用。
UNIX Shell Script 進階
在 UNIX 系統中,Shell 指令碼是一種強大的工具,能夠讓使用者進行複雜的任務自動化。以下將探討 Shell 指令碼的各個重要主題,包括函式的引數傳遞、函式的傳回方式、訊號捕捉、陣列操作以及實用的 shell 指令碼範例。
函式的引數傳遞
在 Shell 指令碼中,函式可以透過位置引數來傳遞引數。這些位置引數可以透過 $1, $2, …, $n 來表示,並且在呼叫函式時可以使用空格分隔的字串來指定。
範例:傳遞引數給函式
Shell_fun () {
echo $1
echo $2
}
在這個例子中,Shell_fun 函式接受兩個引數。呼叫函式時,可以如下所示:
Shell_fun "hell" "World"
這樣,$1 和 $2 分別會被指定為 “hell” 和 “World”。
函式的傳回方式
Shell 函式有四種傳回控制方式:
- 改變區域性變數狀態:在函式內部修改區域性變數的值。
- 使用
exit命令終止 Shell 指令碼:這會立即終止整個 Shell 指令碼。 - 使用
return命令離開函式並傳回值:這會傳回一個整數值給呼叫函式的部分。 - 使用
echo輸出到終端:直接輸出結果到終端。
範例:傳回值
fun_ret.sh
fun1_ret() {
c=`expr $1 = $2`
return
}
Fun1_ret 10 20
ret=$?
echo sum is $ret
在這個例子中,fun1_ret 函式會傳回比較結果的離開狀態碼,並且在呼叫部分可以透過 $? 取得。
捕捉訊號
在 UNIX 中,程式可以接收並回應訊號。例如,當使用 kill -9 命令時,會傳送訊號 9 給程式以中止它。UNIX 允許透過 trap 命令來改變訊號的處理方式。
範例:捕捉訊號
#myfile.sh
file=$1
pat=$2
trap 'rm -f test.$$' 1 2 15
if grep $pat $file > test.$$; then
echo "Pattern found"
else
echo "Pattern not found"
fi
rm -f test.$$
在這個例子中,當接收到訊號 1, 2 或 15 時,會執行 rm -f test.$$ 命令來清除臨時檔案。
陣列操作
Bourne 和 C shell 不支援陣列,但 Bash 和 Korn shell 支援。Korn shell 提供了一維陣列,最多可以有 1,024 個元素。
陣列元素指定
有兩種方式可以指定給陣列元素:
標準變數指定方法:使用
[ ]運算元。my_arr[3]="arr3"set 命令變體:使用
set -A命令。set -A my_arr arr1 arr2 arr3
陣列元素參照
可以透過 $my_arr[i] 參照第 i 個元素。如果使用 * ,則表示參照所有元素。使用 #my_arr[*] 可以計算總元素數量。
範例:運算元組
myarr=(10 23 14 15 16)
echo ${myarr[*]}
echo ${#myarr[*]}
myarr[0]=15; myarr[1]=18
UNIX Shell Script 的應用
UNIX 提供了豐富的命令集合,使用者可以透過結合這些命令來構建複雜的任務。Shell 是一種功能齊全的程式語言,具有完整的程式設計工具和控制結構。除了基本的程式設計工具外,Shell 還提供了有限容量的陣列、函式和訊號捕捉。
常見問題及解決方案
以下是一些常見問題及其解決方案:
問題:如何顯示一個單詞重複多次?
#!/bin/bash
if [ $# -ne 2 ]; then
echo "Usage: $0 word number"
exit 1
fi
word=$1
number=$2
for ((i=0; i<number; i++)); do
echo $word
done
問題:如何檢查一個年份是否為閏年?
#!/bin/bash
year=$1
if (( (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)); then
echo "$year is a leap year"
else
echo "$year is not a leap year"
fi
問題:如何刪除檔案中的特定單詞行?
#!/bin/bash
filename=$1
sed '/UNIX/d' "$filename" > tempfile && mv tempfile "$filename"
問題:如何比較兩個檔案並刪除相同檔案?
#!/bin/bash
file1=$1
file2=$2
if cmp -s "$file1" "$file2"; then
rm "$file2"
fi
問題:如何計算一個整數每位之和?
#!/bin/bash
number=$1
sum=0
while [ -n "$number" ]; do
sum=$((sum + ${number: -1}))
number=${number%?}
done
echo "Sum of digits: $sum"
Conclusion
UNIX Shell 是一種強大且靈活的工具,能夠幫助使用者進行各種自動化任務。透過理解和應用 Shell 的各種特性,使用者可以構建出高效且靈活的指令碼來解決日常問題。
此圖示展示了 Shell Script 的基本流程:
Shell 指令碼的實用範例
Shell 指令碼是系統管理員和開發者日常工作中不可或缺的工具。透過 Shell 指令碼,我們可以自動化重複性工作、管理系統資源及進行各種系統操作。以下是一些實用的 Shell 指令碼範例,涵蓋了日常管理任務及技術問題解決。
判斷閏年
這個指令碼用來判斷輸入的年份是否為閏年。閏年的規則是每四年一次,但不是每一百年都是閏年,只有每四百年才是。
#!/bin/bash
# 判斷是否提供了正確的引數數量
if [ $# -ne 1 ]; then
echo "Usage: $0 year"
exit 1
fi
# 年份變數
year=$1
# 計算並判斷閏年
if [ $(($year % 4)) -eq 0 ] && ( [ $(($year % 100)) -ne 0 ] || [ $(($year % 400)) -eq 0 ] ); then
echo "$year 是一個閏年"
else
echo "$year 不是一個閏年"
fi
內容解密:
這段指令碼首先檢查輸入的引數數量,確保只有一個引數(即要判斷的年份)。然後,它使用算術運算來檢查該年份是否滿足閏年的條件:能被4整除,且不能被100整除,或者能被400整除。
清除包含特定字詞的行
這個指令碼用來刪除檔案中包含特定字詞(如「UNIX」)的行。
#!/bin/bash
# 判斷是否提供了正確的引數數量
if [ $# -ne 1 ]; then
echo "Usage: $0 filename"
exit 1
fi
# 檔案名稱變數
filename=$1
# 使用 grep 命令刪除包含「UNIX」的行
grep -v "UNIX" "$filename" > tmp && mv tmp "$filename"
內容解密:
這段指令碼首先檢查輸入的引數數量,確保只有一個引數(即要處理的檔名稱)。然後,它使用 grep 命令過濾掉包含「UNIX」的行,並將結果寫入臨時檔案 tmp。最後,它將臨時檔案重新命名為原始檔名。
比較兩個檔案並刪除相同的檔案
這個指令碼用來比較兩個檔案,如果兩個檔案內容相同,則刪除第二個檔案。
#!/bin/bash
# 判斷是否提供了正確的引數數量
if [ $# -ne 2 ]; then
echo "Usage: $0 file1 file2"
exit 1
fi
# 檔案名稱變數
file1=$1
file2=$2
# 比較兩個檔案內容並刪除相同的檔案
cmp -s "$file1" "$file2"
if [ $? -eq 0 ]; then
rm -f "$file2"
else
echo "Files $file1 and $file2 are not identical"
fi
內容解密:
這段指令碼首先檢查輸入的引數數量,確保只有兩個引數(即要比較的兩個檔名稱)。然後,它使用 cmp 命令比較兩個檔案的內容。如果兩個檔案內容相同,則刪除第二個檔案;否則,顯示提示資訊。
計算一個數字的各位數字之和
這個指令碼用來計算一個給定數字的各位數字之和。
#!/bin/bash
# 判斷是否提供了正確的引數數量
if [ $# -ne 1 ]; then
echo "Usage: $0 number"
exit 1
fi
# 初始化變數
sum=0
num=$1
# 處理每一位數字並計算總和
while [ $num -ne 0 ]; do
tmp=$(($num % 10))
sum=$(($sum + $tmp))
num=$(($num / 10))
done
echo "The sum of digits is $sum"
內容解密:
這段指令碼首先檢查輸入的引數數量,確保只有一個引數(即要計算的數字)。然後,它使用一個 while 輪迴來處理每一位數字,並將其加到總和中。最後,它顯示計算結果。
自動化系統管理任務:檢查目前登入使用者
以下這段指令碼會不斷地顯示目前登入在系統上的使用者資訊。每隔15秒更新一次。
#!/bin/bash
while true; do
who -H #顯示所有登入使用者資訊
sleep 15 #等待15秒後再次執行迴圈內指令。
done #無限迴圈繼續執行指令。
內容解密:
這段指令碼會進入無限迴圈,不斷執行 who -H 指令來顯示所有登入系統的人員清單。每次執行完 who -H 指令之後都會休眠(sleep)15秒才再重複執行。
以上範例展示瞭如何利用 Shell 指令碼來自動化日常管理任務及進行技術問題解決。透過這些實用範例,我們可以更高效地管理系統資源並提高工作效率。
進階UNIX系統開發技術解析
UNIX系統在現代電腦科學中擁有舉足輕重的地位,其強大的功能和靈活的設計使其成為許多高效能應用的首選平台。本文將探討UNIX系統的各個關鍵技術,從核心結構到I/O模型,再到程式控制與同步,並提供具體例項和詳細解說,幫助讀者全面理解UNIX系統的運作原理。
核心結構與管理
UNIX系統的內核是其核心,負責管理資源並提供基本服務。內核的主要功能包括:裝置管理、檔案系統管理、記憶體管理、程式管理及儲存管理。這些功能確保系統能夠高效地執行各種任務。
核心資料結構
在UNIX系統中,檔案I/O操作涉及多個資料結構:
- 每程式檔案描述子表:這個表格儲存每個程式所開啟的檔案描述子。
- 核心檔案表:這個表格包含每個開啟檔案的詳細資訊,例如檔案指標和讀寫許可權。
- 核心資料結構中的in-core表:這個表格用於快取已開啟檔案的資訊,以提高I/O操作效率。
這些資料結構共同作用,確保檔案操作的高效性和穩定性。
I/O模型
UNIX系統支援多種I/O模型,包括阻塞I/O、非阻塞I/O、訊號驅動I/O及多工I/O模型。每種模型都有其特定的應用場景和優勢。
非阻塞I/O模型
非阻塞I/O模型允許應用程式在等待I/O操作完成時繼續執行其他任務。這種模型適合於需要高效處理大量同時連線的情況,例如伺服器應用。
// 非阻塞I/O示例
int fd = open("example.txt", O_NONBLOCK);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
內容解密:
上述程式碼展示瞭如何使用非阻塞模式開啟一個檔案。O_NONBLOCK標誌表示該檔案將以非阻塞方式開啟,這意味著如果檔案不可立即讀取或寫入,操作會立即傳回而不會阻塞程式。
訊號驅動I/O模型
訊號驅動I/O模型透過訊號通知應用程式I/O操作完成。這種模型可以讓應用程式在等待I/O操作完成時執行其他任務,但在實作上較為複雜。
// 訊號驅動I/O示例
signal(SIGIO, sigio_handler);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETSIG, SIGIO);
內容解密:
上述程式碼展示瞭如何設定訊號驅動I/O。首先,使用signal()函式設定訊號處理函式sigio_handler來處理SIGIO訊號。接著,使用fcntl()函式將當前程式設定為檔案描述子fd的擁有者,並指定SIGIO訊號來通知I/O操作完成。
記憶體管理
記憶體管理是UNIX系統中另一個重要方面。記憶體管理涉及多個技術,包括需求分頁、交換以及作業系統所需的記憶體需求。
需求分頁
需求分頁是一種記憶體管理技術,只有在需要時才將頁面載入到記憶體中。這種技術可以有效地提高記憶體使用效率。
// 需求分頁示例
void* page = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
內容解密:
上述程式碼展示瞭如何使用mmap()函式進行需求分頁。mmap()函式將一塊虛擬記憶體對映到程式的地址空間中,只有在讀寫該區域時才會實際分配物理記憶體。
檔案系統與同步
UNIX系統中的檔案系統支援多種操作,包括建立、刪除、重新命名以及控制存取許可權。此外,程式同步是確保多個程式協同工作的關鍵技術。
檔案系統操作
// 建立目錄示例
if (mkdir("new_directory", 0755) == -1) {
perror("mkdir");
exit(EXIT_FAILURE);
}
內容解密:
上述程式碼展示瞭如何使用mkdir()函式建立一個新目錄。引數0755表示目錄的許可權設定為所有者可讀寫執行,組成員及其他使用者可讀執行。
檔案同步機制
程式同步機制包括訊號量、訊息佇列以及分享記憶體等技術。這些機制確保多個程式能夠協同工作而不會發生競爭條件或死鎖問題。
// 建立訊號量集合示例
sem_t sem;
if (sem_init(&sem, 0, 1) == -1) {
perror("sem_init");
exit(EXIT_FAILURE);
}
內容解密:
上述程式碼展示瞭如何使用POSIX訊號量進行程式同步。sem_init()函式初始化一個訊號量集合,引數表示初始值為1,表示資源可用狀態。
網路通訊與通訊端
網路通訊是UNIX系統的一大特色,通訊端提供了靈活且高效的網路通訊機制。通訊端可以分為流通訊端和資料包通訊端兩類別。
// 建立TCP流通訊端示例
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
內容解密:
上述程式碼展示瞭如何建立一個TCP流通訊端。socket()函式建立一個新的通訊端描述子,引數表示使用IPv4地址家族及流通訊端型別。
UNIX環境變數
UNIX環境變數控制了shell及應用程式的一些行為和組態。例如:
PATH:指定命令搜尋路徑。HOME:指定使用者主目錄。PS1:指定命令提示符字串。
# 設定環境變數示例
export PATH=$PATH:/new_directory
export HOME=/home/user
UNIX 檔案與目錄
UNIX 檔案系統概述
UNIX 檔案系統是一個強大且靈活的系統,能夠有效地管理各種檔案型別。瞭解 UNIX 檔案系統的基本概念和結構,對於有效地使用和管理系統資源至關重要。
檔案名稱與特殊字元
在 UNIX 系統中,檔案名稱可以包含多種字元,但有一些字元具有特殊意義,需要特別注意。例如,星號(*)表示所有檔案,問號(?)表示單一字元等。
檔案名稱慣例
UNIX 系統有其獨特的檔案名稱慣例,這些慣例幫助使用者和系統更好地識別和管理檔案。例如,檔案名稱通常以英文字母開頭,並且不建議使用空格或特殊字元。
檔案型別
UNIX 系統支援多種檔案型別,包括普通檔案、目錄、符號連結、裝置檔案等。每種檔案型別都有其特定的用途和屬性。
路徑名稱
路徑名稱是指從根目錄到特設定檔案或目錄的完整路徑。路徑名稱可以是絕對路徑(從根目錄開始),也可以是相對路徑(從當前目錄開始)。
UNIX 程式設計環境
UNIX 提供了一個強大的程式設計環境,支援多種程式語言和工具。這些工具幫助開發者高效地開發和測試程式。
客戶端-伺服器環境
UNIX 系統通常採用客戶端-伺服器架構,這種架構允許多個客戶端同時存取同一個伺服器上的資源。
個人化環境
UNIX 允許使用者自定義其工作環境,包括設定變數、自訂命令等。這使得使用者能夠根據自己的需求來最佳化工作流程。
時間分享環境
UNIX 支援時間分享功能,這意味著多個使用者可以同時使用同一台機器,而不會互相干擾。
UNIX Shell 函式
Shell 是 UNIX 系統中的命令列介面,提供了豐富的功能來管理系統和執行任務。
命令管線
命令管線允許使用者將多個命令連線在一起,形成一個Pipeline。這樣可以更高效地處理資料。
輸入/輸出重導向
輸入/輸出重導向允許使用者將命令的輸入和輸出導向到其他位置,例如檔案或其他命令。
程式執行
Shell 提供了多種方式來執行程式,包括直接執行、後台執行等。
Shell 程式語言
Shell 本身就是一種程式語言,使用者可以使用它來編寫指令碼來自動化任務。
UNIX Shell 模式
Shell 有多種執行模式,每種模式都有其特定的用途。
互動模式
互動模式允許使用者直接在命令列中輸入命令並立即看到結果。
程式設計模式
程式設計模式允許使用者編寫 Shell 指令碼來自動化任務。
工作階段自定義模式
工作階段自定義模式允許使用者自定義其工作環境,例如設定變數、別名等。
UNIX 標準 I/O 函式庫
UNIX 提供了一組標準 I/O 函式庫,用於處理檔案輸入和輸出操作。
檔案物件
檔案物件是指在記憶體中表示的一個檔案,可以透過標準 I/O 函式庫進行操作。
I/O 快取緩衝區
I/O 快取緩衝區用於提高 I/O 操作的效率,減少對磁碟的直接存取次數。
流物件
流物件是指一個連續的位元組序列,可以透過標準 I/O 函式庫進行讀取和寫入操作。
UNIX 系統架構
UNIX 系統由多個層次組成,每個層次都有其特定的功能和責任。
快取層次架構圖示
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title UNIX Shell Script 進階開發與實務應用
package "機器學習流程" {
package "資料處理" {
component [資料收集] as collect
component [資料清洗] as clean
component [特徵工程] as feature
}
package "模型訓練" {
component [模型選擇] as select
component [超參數調優] as tune
component [交叉驗證] as cv
}
package "評估部署" {
component [模型評估] as eval
component [模型部署] as deploy
component [監控維護] as monitor
}
}
collect --> clean : 原始資料
clean --> feature : 乾淨資料
feature --> select : 特徵向量
select --> tune : 基礎模型
tune --> cv : 最佳參數
cv --> eval : 訓練模型
eval --> deploy : 驗證模型
deploy --> monitor : 生產模型
note right of feature
特徵工程包含:
- 特徵選擇
- 特徵轉換
- 降維處理
end note
note right of eval
評估指標:
- 準確率/召回率
- F1 Score
- AUC-ROC
end note
@enduml此圖示展示了 UNIX 系統的基本架構。硬體層是最底層的部分,負責硬體資源的管理。核心層則負責系統的核心功能如程式管理、記憶體管理等。Shell 層提供了人機互動的介面。公用程式及應用程式層則包含各種工具和應用軟體。視窗介面層則提供圖形化介面給使用者使用。
UNIX 流程控制
until 命令
until 與 while 的對比
until [ condition ]; do commands; done
while [ condition ]; do commands; done
此圖示展示了 until 和 while 的基本語法結構。until 命令會在條件為 false 的時候執行指定的命令列表並持續重複執行直到條件為 true;而 with 命令則是在條件為 true 的時候執行指定的命令列表並持續重複執行直到條件為 false。
wait() 函式
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
內容解密:
wait() 函式是一個同步函式, 用於讓父程式等待子程式結束, 以便父程式獲得子程式的傳回狀態資訊. 該函式呼叫會暫停父程式, 直到有一個子程式終止. 若無子程式, wait() 函式會立即傳回 -1, 預設情況下, wait() 預設等待第一個已經改變狀態 (通常是已經終止) 的子程式. 當子程式終止時, wait() 函式會回傳該子程式的 PID (程式識別碼), 如果沒有任何子程式終止, 則回傳 -1. 在使用 wait() 函式時, 需要將 *status 指標傳遞給它, 以便捕捉子程式的離開狀態.
write() 函式
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
內容解密:
write() 函式被設計來將資料寫入到已開啟的檔案描述符 (fd) 中. write() 函式接受三個引數: fd 是檔案描述符, buf 是指向要寫入資料的緩衝區指標, count 是要寫入資料的位元組數. 若成功, write() 函式會回傳實際寫入到檔案中的位元組數; 若失敗則回傳 -1. write() 函式應該經常與錯誤處理機制配合使用, 以應對可能發生的異常情況.
UNIX 使用者與群組管理
使用者 ID (UID)
每個 UNIX 使用者都有一個唯一的 UID, 用於識別該使用者. UID 是一個整數值, 雖然通常不直接顯示給使用者.
使用者資訊
使用者資訊存取
id username
此圖示展示瞭如何存取某位使用者 (username) 的基本資訊. id 命令可顯示該使用者的 UID 和 GID (群組識別碼). id 命令也可以不附加引數直接顯示當前登入者的基本資訊.
使用者群組詳細資訊
groups username
cat /etc/group | grep groupname
此圖示展示瞭如何查詢某位使用者 (username) 的群組詳細資訊以及如何檢視某群組內部成員清單.
語法糾正及錯誤處理
在實務運作中常見錯誤處理方式包含:
- 未指定變數:
echo $undefined_variable # 應避免使用未指定變數;避免產生空白或不預期結果.內容解密:
在 Bash 指令碼中未設定變數時進行呼叫會產生空白或不預期結果。如上述範例將顯示空白輸出或錯誤提醒。因此應先檢查該變數是否已經被設定好再進行後續操作。 - 錯誤重導向:
command > output.txt 2>&1 #標準輸出重導向至 output.txt;標準錯誤也被重導向至 output.txt。內容解密:
在 bash 中我們可透過「2>&1」將標準錯誤(standard error)重導向至標準輸出(standard output),藉此確保所有錯誤資訊都能被記錄或捕捉。 - 訊號處理:
signal(SIGINT, handle_interrupt); //註冊 SIGINT 訊號處理函式為 handle_interrupt。內容解密:
在 Unix/Linux 中訊號(signal)可使不同程式之間溝通並進行互動。例如按下 Ctrl+C 或 Ctrl+Z 鍵會傳送 SIGINT 或 SIGTSTP 訊號給目前執行中的程式。因此我們可藉由註冊特定訊號處理函式(如上述範例中的 handle_interrupt)來捕捉並回應該型別訊號而達到錯誤處理或其他目的。