定義 Shell 函數
Shell 也可以創建函數(像 C 語言),讓同段代碼被重複利用
函數是一個腳本代碼區塊,可以放置在腳本任意位置; Shell 函數有兩種格式(都可以)如下
# 1. 第一種
function name {
# other commands
}
# ------------------------------------
# 2. 第二種
name() {
# other commands
}
其中 name 是每個函數的名稱,呼叫函數時需使用 name 呼叫,它也是必須為 腳本內唯一名稱(不可重複)
創建、呼叫函數:範例
● 使用兩種不同格式創建函數
#! /bin/bash
# 格式 1
function func1 {
echo "Hello, I am function 1."
}
# 格式 2
func2() {
echo "Function 2 here."
}
echo "Before call the function"
func1
func2
echo "After call the function"
● Shell 呼叫函數還有幾個特性如下
A. 順序性:同命令式設計的 C 語言,強調順序性,如果呼叫函數時,該函數尚未定義,則會出錯(command not found
錯誤)
在尚未定義函數時,呼叫函數的(順序)錯誤範例…
#! /bin/bash
echo "Before call the function"
func2
echo "After call the function"
func2() {
echo "Function 2 here."
}
B. 可覆蓋性:如果出現相同命名的函數,那會出現 後蓋前 的問題(因為分析的順序是由上至下…)
#! /bin/bash
func2() {
echo "Function 2 here."
}
echo "Before redefined func."
func2
function func2 {
echo "What up man~"
}
echo "After redefined func."
func2
函數遞迴
● 遞迴就是函數呼叫自身多次,概念程式如下
function MyFuncion {
MyFunction
}
● 遞迴是邏輯複雜、又危險的?
遞迴的優點在於程式碼的複用,但相對起來確實是邏輯較為複雜、當操作不慎時確實危險(容易造成無窮迴圈)
當每次操作時我們都必須判斷何時該停止遞迴,並請再遞迴前也須改變傳入的參數
● 能夠體現遞迴最經典的就是 階乘(factorial
),階乘的概念如下
N! = N * (N-1)!
# 舉例
5! = 5 * (4)!
5 = 5 * 4 * 3 * 2 * 1
Shell 函數可以使用遞迴,接下來我們就使用 Shell Function 來達成遞迴計算
# !/bin/bash
function factorial {
local curValue=$1
if [ $curValue -eq 1 ] ; then
#透過 echo 方式回傳
echo 1
else
# 記得修改傳入變數!
local tmp=$[ $curValue - 1 ]
# 遞迴呼叫
local res=$(factorial $tmp)
echo $[ $res * $1 ]
fi
}
read -p "Enter value: " value
finalRes=$(factorial $value)
echo "The factorial of $value is $finalRes"
Shell 函數:返回值
bash shell 會把函數當作一個小型腳本,函數運行完後會 返回一個 退出狀態碼(返回給父進程);函數以下有種方式產生退出狀態碼
取得默認退出狀態碼:「$?
」
● Shell 腳本中的函數,退出狀態碼是函數最後一條命令返回的退出碼
● 函數執行結束後可以使用
$?
來取得最後的狀態碼
#! /bin/bash
func() {
echo "Hello function"
}
echo "Start..."
func
echo "Finish... exit code: $?"
● 以默認最後一行命令的作為退出碼的方案有些危險,有可能在函數中,其他指令出錯,它也會以最後一行退出碼為主(沒有好好處理,會不容易找到 Bug 的根源)
#! /bin/bash func() { # The file 123 is not exist. ls -laF 123 echo "Hello function" } echo "Start..." func echo "Finish... exit code: $?"
● 當然 並非所有返回非
0
都代表命令錯誤!像是
grep
如果匹配到數據返回 0,沒匹配到則返回 1;但指令仍算是正確執行(因為語法沒有錯誤,只是沒有批配到)# 有該字面 grep 'Hello' tempFile # 返回 0 echo $? # 無該字面 grep 'World' tempFile # 返回 1 echo $?
最準確的還是使用
man
查看所需指令的返回意義
使用 return 命令
● Bash shell 允許我們使用 return 命令指定一個「整數」來作為退出碼,這時使用 $?
符號,就可以取得指定的退出碼;其格式如下
return [整數]
使用 return
命令的範例如下
#!/bin/bash
func() {
# 讀取輸入
read -p "Enter a value: " value
echo "The double value..."
return $(( value * 2 ))
# 這行不會被執行到
echo "After return."
}
# 呼叫函數
func
echo "$?"
● 返回的整數範圍是
0
~255
之間,如果超過則是返回值除以 256 取餘數(MOD)
echo 函數輸出:使用「$()
」
● 如同把命令的輸出存到一個變量一樣,我們也可以 透過 $()
符號 來呼叫函數,並把函數最終結果賦予到 Shell 變量中(使用 $()
接收到返回值)
#!/bin/bash
function myFunc {
read -p "Enter a value: " value
echo $[ $value * 2 ]
}
# 使用 $() 符號
result=$(myFunc)
echo "The new value is $result"
用這個方法就不會限定於一定要數字,可以返回字串、浮點數
Shell 函數的參數、變量
定義 Shell 變量是腳本中容易出錯的部分
傳遞參數給函數
● Shell 函數無法直接接收參數,它是透過幾個特殊的環境變量來取得傳入的參數(完整請參考 建構腳本 - 特殊參數、互動輸入)
特殊變量 | 說明 |
---|---|
$# | 取得參數總數 |
$* | 取得全部傳入的參數(整體) |
$@ | 取得全部傳入的參數(個別分開) |
$0 | 腳本自身名稱 |
$1 ~ $n | 取得參數 1 ~ n |
#! /bin/bash
# 定義 add 函數
function add {
# 判斷參數數量
case $# in
1)
echo $[ $1 + $1 ] ;;
2)
echo $[ $1 + $2 ];;
*)
echo "Invalid input";;
esac
}
# 對 add 函數傳入變數 1
res=$(add 1)
echo "1 + 1: $res"
# 對 add 函數傳入變數 4、7
res=$(add 4 7)
echo "4 + 7: $res"
res=$(add)
echo "--: $res"
● 另外 Shell 函數有個特點,它會隔離在自己的小世界中,無法接收到腳本外部傳入的參數;範例如下
● 錯誤範例:由於函數無法接收到外部腳本的參數($1
、$2
),所以在函數中取外部變數,就會判斷錯誤
#! /bin/bash
function add {
case $# in
1)
echo $[ $1 + $1 ] ;;
2)
echo $[ $1 + $2 ];;
*)
echo "Invalid input";;
esac
}
if [ $# -eq 2 ] ; then
# 錯誤點
value=$(add)
echo "result: $value"
fi
● 修正後的範例:透過手動將參數傳入($1
、$2
),函數就可以正常運作
#! /bin/bash
function add {
case $# in
1)
echo $[ $1 + $1 ] ;;
2)
echo $[ $1 + $2 ];;
*)
echo "Invalid input";;
esac
}
if [ $# -eq 2 ] ; then
# 手動傳入參數
value=$(add $1 $2)
echo "result: $value"
fi
Shell 函數中的變量:區域變數 local
● 如同我們在使用其他程式語言(C, C++, Java...)開發一樣,變量基本有分為兩種
● 全局變量:函數內、函數外都可以使用
這種方案你要清楚知道該全局變量在哪裡被調用,哪裡可能有做值得修改、讀取,才能安全使用
#! /bin/bash
globalVar=100
function myFunc {
(( globalVar -= 89 ))
echo "func var is $globalVar"
}
echo "Before call func, external var is $globalVar"
myFunc
echo "Before call func, external var is $globalVar"
可以看到,函數內操控全域變數會影響外部
● 局部變量關鍵字 local
:只有函數內可用,在 Shell 函數中要加上 local
關鍵字
#! /bin/bash
globalVar=100
function myFunc {
# 差異在這,宣告 local 參數
# global 沒有定義,預設為 0
local globalVar=$[ $global - 89 ]
echo "func var is $globalVar"
}
echo "Before call func, external var is $globalVar"
myFunc
echo "Before call func, external var is $globalVar"
Shell 函數:傳入、返回數組
函數傳入數組
● 如果你傳遞的變數(Array
)是數組變量,那要 做特別處理,否則函數內部只能收到第一個變量(更多數組操作,請看 Shell 全局及區域變數、特殊變數及環境變數 | Shell 啟動順序 | Array 變數)
● 對函數傳入數組的問題點,範例如下
#! /bin/bash
function myFunc {
echo "Function get array: $@"
getArray=$1
echo "Receive array is: ${getArray[*]}"
}
myArray=(1 2 3 4 5 6)
echo "External array is: ${myArray[*]}"
myFunc $myArray
可以看到函數內部只接收到一個元素,而並非整個 Array 的元素!
● 解決方式:分批傳入
、重組
A. 分批傳入:傳入參數的方式不對,我們必須使用 $Array[*]
的方式將參數傳入
#! /bin/bash
function myFunc {
echo "Function get array: $@"
getArray=$1
echo "Receive array is: ${getArray[*]}"
}
myArray=(1 2 3 4 5 6)
echo "External array is: ${myArray[*]}"
# 修正點
myFunc ${myArray[*]}
我們可以看到分批傳入成功,但函數接收全部 Array 仍錯誤
B. 重組:透過 ("$@")
方式重組 Array 的元素
#! /bin/bash
function myFunc {
echo "Function get array: $@"
getArray=("$@")
echo "Receive array is: ${getArray[*]}"
}
myArray=(1 2 3 4 5 6)
echo "External array is: ${myArray[*]}"
myFunc ${myArray[*]}
函數返回數組
● 如果 Shell 函數要返回數組,那可以將 1.數組分批傳出(${Array[*]}
)再 2.透過 echo 命令作為函數的返回
#! /bin/bash
function getArray {
local array
array=(1 2 3 4 5)
echo "${array[*]}"
}
res=($(getArray))
echo "external get array: $res"
echo "external get array: ${res[*]}"
命令行中使用函數
Shell 函數不一定要在腳本內創建,也可以在 CLI 命令行創建並使用
命令行中創建
● Shell 會解釋用戶輸入的命令,所以可以直接在命令行中定義;有兩個方案可用(重點是花括號 { }
)
A. 單行定義:操作如同在 Shell 中定義參數,定義出來後在該 Shell 進程內都可以使用(如果需要給 sub shell 使用記得 export
)
# 每個命令後後記得 `;`
function add { echo $[ $1 + $2]; }
# 呼叫 Function
add 100 200
B. 多行定義:多行定義使用 {
開頭,並使用 }
結尾;在這 花括號{ }
中間我們可以定義多個命令
function double {
read -p "Enter value: " value
echo $[ $value * 2]
}
● 在命令行創建函數有幾個注意點
A. 如果在同一個 Shell 中定義同名函數,仍會後蓋前
B. Shell 結束,則命令行函數也會消失
.bashrc
中創建函數
● 在 .bashrc
中創建函數之後,在每個命令行中都可以使用;以下我們在 .bashrc
之後創建函數(不要刪除 .bashrc
中任何資料)
A. 在 .bashrc
中創建函數
# 添加在最後一行
function showTime {
echo -n "Hello $USER, current time is "
date
}
執行測試(以下在同個 Shell 中執行函數)
# 重新加載檔案
source ~/.bashrc
showTime
B. 在 .bashrc
中加載腳本庫:使用 source 命令做函數加載
可以先看下面
創建庫 Library
小節
A. 首先創建一個腳本庫:以下創建名為 showTimeLib.sh
的腳本庫
function showTime2 {
echo -n "Hello $USER, current time is "
date
}
B. 設定腳本庫加載進 .bashrc
:這邊請記得使用全路徑!
● 編輯 .bashrc
# 使用 source + 使用全路徑
source /home/alien/Desktop/shell/chapter_17/showTimeLib.sh
● 測試使用 .bashrc
加載的庫
source ~/.bashrc
showTime2
腳本庫創建、使用
庫(Library
)可擁有一系列可以重用方法的函數,可以加速開發,而腳本也可以製作腳本庫
創建、加載腳本庫 Library:source
● 當同一段函數有許多檔案都用的到相同邏輯時,就可以創建一個腳本庫(shell library
) 將同邏輯的函數放置於其中,在需要時再引入即可;方式如下
A. 創建腳本庫:以下會在腳本庫裡面存放共同邏輯的 function
# MyLib.sh. library
function add {
echo $[ $1 + $2 ]
}
function minus {
echo $[ $1 - $2 ]
}
function mul {
echo $[ $1 * $2 ]
}
function div {
if [ $2 -ne 0 ] ; then
echo $[ $1 / $2]
else
echo -1
fi
}
B. 使用 source
命令引用庫:引入後就可以使用庫中的函數,而不需重複創建
●
source
的使用方式有兩種,如下# 使用方式一 source <檔案> # 使用方式二:點操作符(dot operator) . <檔案>
其中 檔案 可以是 相對路徑、絕對路徑
#!/bin/bash
source ./MyLib.sh
read -p "Enter 2 value: " val1 val2
echo "add res: $(add $val1 $val2)"
echo "minus res: $(minus $val1 $val2)"
echo "mul res: $(mul $val1 $val2)"
echo "div res: $(div $val1 $val2)"
下載 & 建構 shtool 包
● GNU shtool
庫:GNU shtool 提供了一些簡單的 shell 腳本函數給我們復用
A. 下載 shtool 包
# 下載
wget ftp://ftp.gnu.org/gnu/shtool/shtool-2.0.8.tar.gz
# 解壓
gunzip shtool-2.0.8.tar.gz
# 解歸檔
tar -xvf shtool-2.0.8.tar
下載網址 ftp://ftp.gnu.org/gnu/shtool/shtool-2.0.8.tar.gz
B. 建構 shtool 庫
shtool 文件必須針對特定的 Linux 環境進行配置,配置需使用到 configure
、make
命令
命令 | 功能 |
---|---|
configure | 檢查 shtool 庫文件所必須的應用,當發現所需的工具,它會使用工具路徑修改配置文件 |
make | 負責建構 shtool 庫,最終將它打包為一個完整的軟體包 |
make test 用於測試軟體包 | |
make install 用於安裝包道系統中 |
cd shtool-2.0.8.tar/
./configure
make
sudo make install
現在請移動到你解壓後的資料夾中操作
使用 shtool 庫
● 以下列出 shtool 庫中可用的幾個函數;使用 shtool 指令格式如下
shtool [options] [function [options] [args]]
函數 | 說明 |
---|---|
Arx | 創建歸檔文件(包含一些擴展功能) |
Echo | 顯示字符串,並提供了一些擴展構件 |
fixperm | 改變目錄樹中的文件權限 |
install | 安裝腳本或文件 |
mdate | 顯示文件或目錄的修改時間 |
mkdir | 創建一個或更多目錄 |
Mkln | 使用相對路徑創建鏈接 |
mkshadow | 創建一棵陰影樹 |
move | 帶有替換功能的文件移動 |
Path | 處理程序路徑 |
platform | 顯示平台標識 |
Prop | 顯示一個帶有動畫效果的進度條 |
rotate | 轉置日誌文件 |
Scpp | 共享的 C 預處理器 |
Slo | 根據庫的類別,分離鏈接器選項 |
Subst | 使用sed的替換操作 |
Table | 以表格的形式顯示由字段分隔(field-separated)的數據 |
tarball | 從文件和目錄中創建 tar 文件 |
version | 創建版本信息文件 |
● shtool 使用範例:platform
顯示版本訊息
#! /bin/bash
shtool platform
更多 Linux Shell 知識
Linux 環境、服務:用戶、權限管理
● Linux 環境、服務、管理:
在這個主題中,你可以探索各種 Linux 環境、服務和管理相關的重要概念…
● 了解 Linux 組成:內核責任、GNU 工具、桌面環境 | 基礎版到發行版
在這裡,你將了解 Linux 系統的基本組成部分,包括內核、GNU 工具和桌面環境,並了解不同 Linux 發行版之間的差異
● 探索 X Window 服務與桌面環境:Linux 視窗 XWindow 服務、D-Bus 機制
這裡探討了 Linux 系統中 X Window 服務和 D-Bus 機制的工作原理,讓你深入了解桌面環境的背後運作方式
● Linux 系統管理入門:安全性、用戶管理與權限設定指南
在這個指南中,你將學習如何管理 Linux 系統,包括安全性、用戶管理和權限設定,以確保系統的安全和有效運作
● 認識身份驗證與權限管理:UNIX UID、使用者驗證與 PAM | PAM 設定
這裡介紹了 UNIX 系統中的身份驗證機制和權限管理方式,包括 UNIX UID、使用者驗證和 PAM(
Pluggable Authentication Modules
)設定
Linux Shell 相關知識
● Linux Shell 相關知識:
這個主題涵蓋了各種與 Linux Shell 相關的知識,從基礎到進階都有…
● Shell 基礎知識:包括 Shell 的基礎使用以及差異,還有變數以及計算
● 認識命令行:Shell 類型、命令差異 | Sub Shell 關係 | Builtin 命令
這裡介紹了命令行中不同類型的 Shell,以及各種 Shell 命令之間的區別和內建命令的用法
● Shell 全局及區域變數、特殊變數及環境變數 | Shell 啟動順序 | Array 變數
學習如何在 Shell 中使用全局變數、特殊變數和環境變數,以及如何設置和管理這些變數
● 探索腳本與命令:Shell 腳本的必備相關知識 | 腳本的數學運算
這裡介紹了 Shell 腳本的基本知識和常用技巧,包括如何執行腳本和進行數學運算
● Shell 結構化
● Shell 腳本程式中的條件語句和高級特性 | Shell 結構化 & 判斷
該篇文章是介紹結構化腳本的基礎以及特殊的高級技巧,轉著於腳本中的邏輯判斷
● 掌握 Bash 腳本中的迴圈與循環控制技巧 | Shell 結構化 & 循環
接著是腳本中的循環與控制的技巧,它可以讓我們在 Shell 腳本中有更多的判斷與技巧
● 探索 Shell 函數與腳本庫 | Shell 結構化 & 函數
這裡你將會學在到結構化腳本的重要技巧「函數」,它可以讓你建立可重複利用的腳本,以節省我們之後開發的時間
● Shell 掌握參數處理與用戶輸入技巧 | Shell 結構化 &輸入互動
不可互動的腳本有時候相對無趣,這篇文章有分享如何讓腳本與使用者產生交互互動,並依照使用者輸入的參數做讀取
● Shell script 進階
● Shell 命令輸入與輸出指南:掌握標準文件描述符與重定向 | 臨時文件
掌控 Shell、指令的輸出輸入是成為進階使用 CLI 之人必經的一課,透過這篇文章可以了解到輸出輸入、臨時文件等等資訊
● 探索 Linux 訊號與後台進程管理:安排定期啟動腳本 | 運行時啟動腳本
信號可以算是 Shell 腳本與 Linux 系統之間的通訊方式,透過這個文章,我們可以了解到信號與 Shell 腳本的安排時間
Linux 硬體規劃:檔案系統、分區
● Linux 硬體規劃:檔案系統、分區:
最後,這個主題涵蓋了 Linux 系統中硬體結構相關的重要概念…
● Shell 文件和目錄操作的常用命令和技巧 | 尋找檔案
這裡介紹了 Shell 中常用的文件和目錄操作命令,包括如何尋找文件和管理文件系統
● 深入探索 Linux 文件系統與硬體管理 | 分區、檔案系統、邏輯卷 | inode
學習如何管理 Linux 文件系統,包括分區、文件系統和邏輯卷等相關概念,以及文件系統中的 inode 機制
● 理解 Unix/Linux 設備偵測、建立 | 認識與使用 udevd | SCSI 與 Linux 核心
這裡介紹了 Unix/Linux 系統中的設備偵測和管理,包括如何使用 udevd 和 SCSI 相關的 Linux 核心知識