The Missing Semester of Your CS Education 學習筆記以及一些拓展知識(二)

文章目錄

  • The Missing Semester of Your CS Education 學習筆記以及一些拓展知識
    • Bash腳本
      • 筆記部分
        • 一些在Bash腳本中的常用命令補充
          • 常用標準輸入輸出命令
          • 常用環境變量(普通變量)控制命令
          • 常用系統時間信息獲取命令
          • 常用函數執行狀態控制命令
          • 常用腳本執行控制命令
        • Bash腳本的創建和運行
        • Bash腳本的SheBang
        • Bash腳本的變量
          • 變量的定義
          • 變量的使用
          • 變量的類型
            • 局部變量
            • 環境變量
            • 基本內置變量(位置參數)
            • 特殊內置變量
          • Bash變量的三個特殊機制
            • 數組
            • 命令替換 (Command Substitution)
            • 變量擴展(參數拓展)
          • 變量使用時的注意事項
        • Bash腳本的條件結構
          • test命令
            • test的基本功能
            • test指令的三種形式
            • test的常用測試表達式
            • 組合條件
            • test的使用基本注意事項
          • let命令
            • let的基本功能
            • let支持的運算符
            • let的改進寫法:雙括號算術運算 ((...))
          • if-then結構
            • if的三種結構
            • if的判斷條件類型
            • if的一些使用注意
          • case結構
            • case的基本結構
            • case使用的一些注意事項
          • &&和||
        • Bash腳本的循環結構
          • for循環
            • for循環的兩種基本結構
            • for循環的注意事項
          • while循環
            • while的基本語法:
            • while的常見用法
            • while的注意事項
          • until循環
          • 循環控制指令
            • break
            • continue
        • Bash腳本的函數
          • 函數的定義
          • 函數的調用
          • 函數的傳參
          • 函數變量的作用域
          • 函數的執行結果
          • 總結函數的注意事項
        • Bash腳本的Debug
          • 語法檢查
          • 腳本邏輯的debug
          • 其他
      • 習題部分

The Missing Semester of Your CS Education 學習筆記以及一些拓展知識

以下是使用The Missing Semester of Your CS Education的個人學習筆記,方便之后翻閱

Bash腳本

上一節我們學習了簡單的命令。假設有這么一種情況:我們希望每次開機后都執行一串相同的命令,難道說我們只能一個命令一個命令地在終端里面敲嗎?答案是我們可以把這些重復命令編寫成shell腳本,以下是二者的主要區別:

  • 交互性 vs. 自動化
    命令行是為交互而生的。你輸入一個命令,馬上就能看到結果,然后根據結果決定下一步做什么。這個即時反饋的循環對于文件管理、系統監控和問題排查至關重要。
    腳本是為自動化而生的。它的設計初衷就是“一次編寫,多次運行”。對于那些需要重復執行的、由多個步驟組成的任務(例如:每日備份、部署網站、批量轉換文件),寫成腳本可以極大地提高效率并減少人為錯誤。
  • 編程能力上,Bash腳本引入了非常多的語法結構,支持編寫復雜的邏輯功能。
  • 運行環境的差別(非常重要)
    命令行中的命令在當前 Shell 環境中執行。你在命令行里做的任何環境改變(如用 cd 切換目錄、用 export 設置環境變量),都會立即對你當前的 Shell 生效。
    腳本默認在一個新的子 Shell (subshell) 中執行。這意味著腳本內部對環境的修改(如 cd、變量賦值)不會影響到執行它的那個父 Shell。

舉個例子:你在命令行里現在位于 /home/user。你有一個腳本 test.sh,內容是 cd /tmp。你執行 ./test.sh。腳本執行時,它的工作目錄確實切換到了 /tmp。但當腳本執行完畢,你回到命令行時,你所在的目錄仍然是 /home/user,而不是 /tmp。因為 cd 命令只在那個短暫存在的子 Shell 里生效了。

例外:如果你使用 source 命令(或其簡寫 .)來執行腳本,那么腳本會在當前 Shell 中執行,其內部的環境變量和目錄切換會保留下來。例如:source test.sh。

筆記部分

一些在Bash腳本中的常用命令補充
常用標準輸入輸出命令
命令全稱基本功能常用參數
readread從標準輸入(stdin)中讀取一行文本,并將其賦值給一個或多個變量-p(prompt):在讀取輸入前,先顯示指定的提示信息。常用于交互式腳本。
-r (raw read):原始讀取模式。禁止反斜杠 \ 的轉義功能。
-s (silent):靜默模式。不將用戶輸入的內容顯示在屏幕上。非常適合用于輸入密碼或敏感信息。
-t <秒數> :設置一個超時時間。如果在指定秒數內用戶沒有輸入,read 命令會失敗并返回一個非零狀態碼。
-n <字符數> :讀取指定數量的字符后立即返回,而無需等待用戶按回車。
echoecho(回聲)在標準輸出(stdout)上打印文本或變量內容。-n :輸出內容后不自動添加換行符。
-e :啟用對反斜杠轉義字符的解釋。
crulSee URL默認將從URL獲取的響應打印到標準輸出-s (silent):靜默模式。不顯示進度條和錯誤信息。在腳本中強烈推薦使用。
-L (Location):自動跟隨重定向。如果請求的 URL 返回一個3xx重定向,curl 會自動請求新的 URL。
-o <文件名> (output):將下載內容寫入指定文件,而不是標準輸出。
-O:將下載內容以 URL 中的原始文件名保存在當前目錄。
-I (Information):只獲取 HTTP 響應頭(HEAD請求)。
-X <方法> (e.g., POST, PUT, DELETE):指定 HTTP 請求方法
-H “<頭部信息>” (Header):添加自定義的 HTTP 請求頭。
-d “<數據>” (data):發送 POST 請求的數據體。

crul有一個姊妹命令wget(web get),與 curl 不同,它的默認行為是將內容保存到文件而非stdio。

常用環境變量(普通變量)控制命令
命令全稱基本功能常用參數
envenvironment顯示所有環境變量或者進行一些環境變量臨時設置env: 直接執行,列出當前所有的環境變量,每行一個 變量名=值。
-i表示忽略當前的所有環境變量,在一個完全干凈的空環境中執行后續命令。
env <變量=值> <命令>: 臨時為 <命令> 設置一個環境變量,但不影響當前的 Shell 環境。
printenvprint environment打印環境變量的值printenv: 不帶參數時,行為與 env 幾乎完全相同,列出所有環境變量。
printenv <變量名>: 只打印指定單個環境變量的值。
exportexport將一個變量“輸出”到環境中,用于創建新的環境變量,或者將一個已經存在的局部變量提升(或稱“導出”)為環境變量。export 變量名=值: 定義一個變量并立即將其導出為環境變量(最常用)。
變量名=值; export 變量名: 先定義一個局部變量,再將其導出。
-p: 打印出當前 Shell 中所有被導出的環境變量,其輸出格式是可被 Shell 重新執行的 export 變量名=“值” 形式。單獨執行 export -p 的效果與 env 類似。
-n: 將一個已存在的環境變量降級為一個局部變量,它將不再被子進程繼承
unsetunset從當前 Shell 環境中徹底刪除一個變量或函數-v (variable):明確表示要刪除的是一個變量(這是默認行為,通常可省略)。
-f (function):明確表示要刪除的是一個函數。
常用系統時間信息獲取命令
命令全稱基本功能常用參數
datedate獲取系統時間或者設置系統時間+Format:自定義日期形式,如:date +‘%Y-%m-%d %H:%M:%S’
-s 設置系統時間
-d 計算相對時間

關于獲取系統的硬件信息和系統信息之后再說吧

常用函數執行狀態控制命令
命令全稱基本功能常用參數
returnreturn立即終止當前正在執行的函數并為該函數設置一個退出狀態碼 ($?)return [n],n 是一個可選的整數參數,范圍是0到255。如果省略n,函數的退出狀態碼將是函數中最后一條被執行的命令的退出狀態碼。
truetrue不做任何事,返回狀態碼0
falsefalse不做任何事,返回狀態碼1
常用腳本執行控制命令
命令全稱基本功能常用參數
exitexit立即終止整個腳本的執行,并可以為腳本設置一個最終的退出狀態碼 ($?)。exit [n]如果省略 n,則腳本的退出狀態碼是 exit 命令之前最后一條被執行的命令的退出狀態碼。
sleepsleep讓腳本的執行暫停指定的一段時間。sleep <數字>[后綴],后綴可以是s、m、h、d
shiftshift將參數列表向左“移動”一位。原來的$1會被丟棄(不可恢復),$2變成新的$1,$3變成新的 $2,以此類推。同時,參數總數 $# 的值也會減 1。shift [n],n 是一個可選的數字,表示要移動的位數。如果省略,默認為 1。
sourcesource當前的Shell環境中讀取并執行一個腳本文件中的命令,而不是為該腳本啟動一個新的子 Shell。
execexecute使用一個新的命令來替換當前的 Shell 進程。

關于更進一步的一些進程控制命令如kill、jobs、fg、bg之后再說吧

Bash腳本的創建和運行

還是HelloWorld程序起手:

touch helloworld.sh
nano helloworld.sh# 編寫如下內容,保存后退出
#!/bin/bash
echo "Hello World"chmod u+x helloworld.sh #更改權限
./helloworld.sh

從這個小小的例子可以看出:創建文件、編寫代碼、賦予權限、執行是所有 Bash 腳本開發的基礎流程。

Bash腳本的SheBang

Shebang,即#(sharp,但簡寫成she)+!(Bang)是位于腳本文件第一行開頭的一個特殊字符序列。它的作用是向操作系統(OS)的程序加載指明當用戶嘗試直接執行這個文件時,請不要把它當作普通的二進制文件,而是應該用我后面指定的這個解釋器來運行。

基本語法:

#!/path/to/interpreter [optional-argument]
  • #!: 這兩個字符是固定不變的“魔術字符”,內核通過它們識別這是一個需要解釋器執行的腳本。
  • /path/to/interpreter: 這是解釋器程序的絕對路徑。對于 Bash 腳本,它通常是 /bin/bash。
  • [optional-argument]: 這是一個可選的參數,會傳遞給解釋器,一般情況下省略。

例如:

#!/bin/bash
#!/bin/sh
#!/usr/bin/python3
#!/usr/bin/node

以上分別是bash、通用shell、python和Node.js腳本的解釋器聲明。

如果你不寫 Shebang,直接運行 ./myscript.sh,那么你當前的 Shell(比如你正在使用的 Bash 或 Zsh)會嘗試去逐行解釋執行這個腳本,就可能會因為語法不兼容而出錯。

Bash腳本的變量
變量的定義

基本語法和示例

var=value# 字符串變量
greeting="Hello, World!"# 整數變量(本質上仍是字符串)
cnt=10

定義時注意事項

  • 等號兩邊不能有空格! 這是初學者最常犯的錯誤。VAR = “value” 是錯誤的,Shell 會把 VAR 當成一個命令來執行。
  • 變量名規范:通常由字母、數字和下劃線組成,不能以數字開頭。習慣上,環境變量和全局變量使用全大寫,腳本內部的局部變量使用小寫,以作區分。
  • 無需聲明類型:Bash 中的變量默認都是字符串類型。即使你賦值一個數字 10,它本質上也是字符串 “10”,但在進行數學運算時,Shell 會自動進行轉換。
變量的使用

基本語法和示例:

$變量名 或者 ${變量名}
# 更推薦后者寫法name="Alex"
echo "My name is $name"
echo "My name is ${name}"

花括號 {} 是一種更嚴謹和安全的寫法,它可以明確變量名的邊界,避免歧義。看下面的例子:

fruit="apple"
echo "I have five ${fruit}s"  # 正確輸出: I have five apples
echo "I have five $fruits"    # 錯誤輸出: I have five  (因為 Shell 試圖尋找一個叫 fruits 的變量,但它不存在)
變量的類型

在Bash中變量本質上都被視作字符串,所以下面分類依據是作用域和來源。

局部變量

在腳本中直接定義的變量,其作用域默認為整個腳本。如果在函數內部定義,為了防止污染外部作用域,應使用local關鍵字聲明。

#!/bin/bash
name="global"my_func() {local name="local" # 使用 local 關鍵字,此變量只在函數內有效echo "Function sees name as: $name"
}echo "Before function call, name is: $name"
my_func
echo "After function call, name is: $name" # 外部變量不受函數內部影響# 輸出
Before function call, name is: global
Function sees name as: local
After function call, name is: global
環境變量

這些變量對當前 Shell 會話以及由它啟動的所有子進程(包括你運行的腳本)都可見。它們通常用于配置系統環境。環境變量可以自己利用report工具定義,也有如下常見的內置環境變量:

環境變量說明
$HOME當前用戶的夾目錄
$PATH可執行文件的搜索路徑
$USER用戶名
$PWD當前工作目錄
$HOSTNAME系統主機名稱
$LANG系統語言和編碼
基本內置變量(位置參數)

這些變量由 Shell 自動賦值,用于獲取傳遞給腳本的命令行參數。

  • $0: 腳本本身的文件名。
  • $1, $2, $3…: 分別代表第 1、第 2、第 3 個參數。
  • ${10}, ${11}… 當參數超過 9 個時,必須用花括號 {} 包裹。
  • $#: 傳遞給腳本的參數總個數。
  • $@: 代表所有參數的列表,每個參數都是獨立的字符串。在循環中,“$@” 是最常用和最安全的方式。
  • $*: 代表所有參數組成的單個字符串。
#!/bin/bash
# 用法: ./backup.sh /path/to/source /path/to/destinationSOURCE_DIR=$1
DEST_DIR=$2echo "將從 '$SOURCE_DIR' 備份到 '$DEST_DIR'..."
cp -r "$SOURCE_DIR" "$DEST_DIR"

這里特別注意一下$@與$*的區別:

當不使用雙引號時,$@ 和 $* 的表現是一樣的。但一旦用雙引號包裹,它們的行為就截然不同。

  • “$*”:會將所有參數視為一個整體的字符串。“param1 param2 param3”。
  • “$@”:會將每個參數視為獨立的字符串。“param1” “param2” “param3”

例如腳本loop_test.sh:

#!/bin/bash
echo "--- 循環遍歷 \"\$*\" (單個字符串) ---"
for arg in "$*"; doecho "參數: $arg"
doneecho ""
echo "--- 循環遍歷 \"\$@\" (獨立字符串列表) ---"
for arg in "$@"; doecho "參數: $arg"
done## 執行
chmod +x loop_test.sh
./loop_test.sh "first arg" "second" "third"## 結果
--- 循環遍歷 "$*" (單個字符串) ---
參數: first arg second third--- 循環遍歷 "$@" (獨立字符串列表) ---
參數: first arg
參數: second
參數: third

在需要遍歷或傳遞參數列表時,99% 的情況下你都應該使用 “$@”。它能保證參數(即使包含空格)被正確、獨立地處理。

特殊內置變量

這些變量由 Shell 預設,用于提供腳本運行狀態的信息。

  • $?: 上一個命令的退出狀態碼。這是腳本中進行錯誤檢查的最重要變量。0代表成功。非0代表失敗。
  • $$:當前腳本的進程ID。常用于創建唯一的臨時文件名。
  • $!: 上一個在后臺運行的命令的進程 ID。
cp source.txt dest.txt
if [ $? -eq 0 ]; thenecho "文件復制成功!"
elseecho "文件復制失敗!"
fi
Bash變量的三個特殊機制
數組

Bash 支持一維數組來存儲一組值。

  • 定義數組: array=(“apple” “banana” “cherry”)
  • 獲取元素: ${array[0]} (獲取第一個元素,索引從0開始)
  • 獲取所有元素: ${array[@]}
  • 獲取數組長度: ${#array[@]}
SERVERS=("server1.com" "server2.com" "server3.com")for server in "${SERVERS[@]}"; doecho "正在 ping 服務器: $server"ping -c 1 "$server"
done
命令替換 (Command Substitution)

這是一種非常強大的機制,允許你將一個命令的輸出結果賦值給一個變量。
基本語法和示例:

var=$(command) 或者 VAR=`command`# 獲取當前日期
TODAY=$(date +%Y-%m-%d)
echo "今天是: $TODAY"# 獲取 txt 文件數量
FILE_COUNT=$(find . -type f -name "*.txt" | wc -l)
echo "這里有 $FILE_COUNT 個 txt 文件。"

推薦使用()而非``,因為使用 $(…) 的好處是它可以輕松地嵌套,而反引號`嵌套起來非常麻煩和混亂。

變量擴展(參數拓展)

Bash 提供了強大的參數擴展功能,可以在不使用外部命令的情況下對變量進行處理。

  • 提供默認值(提升代碼健壯性)

    • ${variable:-default}: 如果 variable 未定義或為空,則返回 default,但不改變 variable 本身的值。
    • ${variable:=default}: 如果 variable 未定義或為空,則返回 default,并同時將 default 賦值給 variable。
  • 字符串操作

    • ${#variable}: 獲取變量值的長度。
    • ${variable#pattern}: 從開頭刪除最短匹配 pattern 的部分。
    • ${variable##pattern}: 從開頭刪除最長匹配 pattern 的部分。
    • ${variable%pattern}: 從結尾刪除最短匹配 pattern 的部分。
    • ${variable%%pattern}: 從結尾刪除最長匹配 pattern 的部分。
    • ${variable/pattern/string}: 替換第一個匹配的 pattern。
    • ${variable//pattern/string}: 替換所有匹配的 pattern。
# 默認值
echo "未定義的用戶: ${UNSET_USER:-guest}" # 輸出:未定義的用戶: guest# 字符串操作
FILEPATH="/home/user/document.txt"
echo "文件名: ${FILEPATH##*/}" # 輸出:document.txt (刪除最長的前綴 */)
echo "目錄名: ${FILEPATH%/*}"  # 輸出:/home/user (刪除最短的后綴 /*)
echo "擴展名: ${FILEPATH##*.}" # 輸出:txt
變量使用時的注意事項
  • 永遠用雙引號包裹變量:使用$variable 或 ${variable} 時,請始終用雙引號包裹,即 “$variable”。這可以防止當變量值包含空格或特殊字符時,發生意想不到的“分詞”和“文件名擴展”問題。這是編寫健壯腳本的黃金法則。
  • 賦值時等號兩邊無空格:再次強調,var=“value” 是正確的,其他形式都是錯誤的。
  • 優先使用 ${}:使用 ${variable} 的形式可以使代碼更清晰,并避免變量名邊界的歧義。
  • 檢查命令成功與否:在執行一個重要命令后,檢查 $? 的值,以確保腳本在出錯時能妥善處理,而不是繼續盲目執行。
  • 區分環境變量和局部變量:使用大寫命名環境變量,使用小寫命名腳本內部的變量,可以提高代碼的可讀性。在函數內部盡量使用 local 關鍵字。
Bash腳本的條件結構
test命令
test的基本功能

test 是一個標準的、內建于 Shell 的命令。它的核心用途只有一個:計算一個表達式的真偽。

強調一下是表達式的真偽而非命令執行的情況

  • 如果表達式為真 (true),test 命令執行成功,并返回退出狀態碼 0。
  • 如果表達式為假 (false),test 命令執行失敗,并返回一個非 0 的退出狀態碼(通常是 1)。

它的輸出不是文本"true"或"false",而是通過退出狀態碼 ($?) 來報告結果。這正是if語句所需要的判斷依據。

# 檢查文件 /etc/hosts 是否存在 (這個文件通常存在)
test -f /etc/hosts
echo $?  # 輸出: 0 (代表 true)# 檢查一個不存在的文件
test -f /nonexistent/file
echo $?  # 輸出: 1 (代表 false)
test指令的三種形式
  1. test expression (經典形式)
    這是 test 命令最原始的形態,現在已不常用,但了解它有助于理解本質。
if test -f "/etc/hosts"; thenecho "文件存在。"
fi
  1. [ expression ] (POSIX 標準形式)
    是test命令的等效寫法,一個方括號 [。它在功能上與 test 完全等價。
if [ -f "/etc/hosts" ]; thenecho "文件存在。"
fi

注意:

  • [ 實際上是一個命令的名稱,和 test 一樣。

  • [ 后面和 ] 前面必須有空格! 這是強制性的語法要求。可以理解為 [ 文件存在嗎 /etc/hosts ],每個部分都是獨立的參數。[ -f …] 是錯誤的。

  1. [[ expression ]] (Bash 擴展形式,推薦使用)
    在編寫 Bash 腳本時,強烈推薦使用它。它解決了 [ 的一些不足。
if [[ -f "/etc/hosts" ]]; thenecho "文件存在。"
fi

[[ … ]] 相比于 [ … ] 的優勢:

  • 更安全的變量處理:在 [[ … ]] 中,即使變量包含空格或為空,不加雙引號通常也不會導致錯誤。而在 [ … ] 中,不加雙引號的變量是導致腳本錯誤的常見原因。盡管如此,為變量加上雙引號永遠是最佳實踐。
  • 更直觀的邏輯運算:可以直接使用 && (與), || (或),而不是 -a, -o。
  • 支持模式匹配和正則:可以用 == 和 != 進行通配符匹配(globbing),用 =~ 進行正則表達式匹配。
test的常用測試表達式
  1. 文件測試
    這是腳本中最常見的測試類型,用于檢查文件或目錄的狀態。
表達式描述
-e <路徑>exists - 路徑是否存在(文件或目錄均可)。
-f <路徑>file - 路徑是否存在且為一個普通文件。
-d <路徑>directory - 路徑是否存在且為一個目錄。
-s <路徑>size - 文件是否存在且大小不為零。
-r <路徑>readable - 文件是否存在且當前用戶可讀。
-w <路徑>writable - 文件是否存在且當前用戶可寫。
-x <路徑>xecutable - 文件是否存在且當前用戶可執行。
CONFIG_FILE="/etc/myapp.conf"if [[ -f "$CONFIG_FILE" && -r "$CONFIG_FILE" ]]; thenecho "正在讀取配置文件..."# ... 讀取文件內容 ...
elseecho "錯誤:配置文件不存在或不可讀!" >&2exit 1
fi
  1. 字符串測試
表達式描述
$str1" = “$str2”字符串內容是否相等。(== 在 [ 和 [[ 中效果相同)
“$str1” != “$str2”字符串內容是否不相等。
-z “$str”zero length - 字符串長度是否為零(即是否為空)。
-n “$str”non-zero length - 字符串長度是否不為零(即是否非空)。
read -p "請輸入您的名字: " user_nameif [[ -z "$user_name" ]]; thenecho "錯誤:名字不能為空!"
elseecho "你好, $user_name!"
fi
  1. 整數測試

用于比較整數大小時,必須使用下面的專屬操作符。

表達式描述
$int1 -eq $int2equal - 是否相等。
$int1 -ne $int2not equal - 是否不相等。
$int1 -gt $int2greater than - 是否大于。
$int1 -ge $int2greater than or equal - 是否大于等于。
$int1 -lt $int2less than - 是否小于。
$int1 -le $int2less than or equal - 是否小于等于。
# 檢查傳入腳本的參數數量是否為 2
if [ "$#" -ne 2 ]; thenecho "用法: $0 <源文件> <目標文件>"exit 1
fi
組合條件
  1. 在 [ … ] 中(不推薦)
  • -a: and (與)
  • -o: or (或)
  • !: not (非)
# 判斷 num 是否在 0 到 100 之間
if [ "$num" -ge 0 -a "$num" -le 100 ]; thenecho "有效范圍"
fi
  1. 在 [[ … ]] 中(推薦)
  • &&: and (與)
  • ||: or (或)
  • !: not (非)
# 同樣是判斷 num 是否在 0 到 100 之間,可讀性更高
if [[ "$num" -ge 0 && "$num" -le 100 ]]; thenecho "有效范圍"
fi
test的使用基本注意事項
  • 優先使用 [[ … ]]:在編寫 Bash 腳本時,只要不需要考慮兼容古老的 sh,就應該優先使用 [[ … ]],它更健壯、功能更強、語法更友好。
  • 永遠用雙引號包裹變量:在進行字符串比較或文件測試時,務必將變量用雙引號 “” 包裹起來,即 [[ -f “file"]]和[["file" ]] 和 [[ "file"]][["str1” == “$str2” ]]。這可以防止當變量為空或包含空格時,test 命令解析出錯。
  • 區分字符串和整數比較:比較字符串用 == 或 =,比較整數用 -eq, -gt 等。混用會導致意想不到的結果。
  • test的結果是狀態碼:牢記 test 命令本身不產生任何可見輸出,它的價值在于其退出狀態碼 $?,if 和 while 語句正是依賴這個狀態碼來工作的。
let命令
let的基本功能

在 Bash Shell 中,變量默認是被當作字符串來處理的。這意味著你不能直接用常規的方式進行數學計算。let 命令的核心作用就是告訴Bash把接下來的表達式當作一個或多個整數算術運算來處理,而不是當作字符串。

let的基本語法結構和示例:

let <算術表達式1> [算術表達式2] ...a=10
b=5let result=a+b   # 加法
echo $result   # 輸出: 15
let result=a-b   # 減法
echo $result   # 輸出: 5
let result=a*b   # 乘法
echo $result   # 輸出: 50
let result=a/b   # 除法 (整數除法)
echo $result   # 輸出: 2
let result=a%b   # 取余
echo $result   # 輸出: 0

let表達式有一個突出的優點,那就是變臉引用可以不加上$:

a=10
b=5
let result=a+b     # 推薦,更簡潔
let result=$a+$b   # 也可以,效果相同

但let也有一個突出的缺點,如果你的表達式中包含空格,必須用引號將整個表達式引起來。

# 錯誤寫法,Shell 會把 =、5、+、3 當成多個獨立參數
let x = 5 + 3 # 正確寫法
let "x = 5 + 3"
echo $x # 輸出: 8
let支持的運算符
  • 賦值:=
  • 算術:+ (加), - (減), * (乘), / (除), % (取余), ** (冪運算)
  • 自增/自減:var++ (后增), var-- (后減), ++var (前增), --var (前減)
  • 復合賦值:+=, -=, *=, /=, %=
  • 位運算:<< (左移), >> (右移), & (按位與), | (按位或), ^ (按位異或), ~ (按位非)
  • 邏輯運算:! (邏輯非), && (邏輯與), || (邏輯或), , (逗號,用于分隔多個表達式)
  • 三元運算符:?: (例如 let “a = 1 > 0 ? 1 : 0” )
let的改進寫法:雙括號算術運算 ((…))

這是目前最推薦的進行整數運算的方式。它是一個命令,也可作為一個表達式

  • 作為命令,進行賦值:(( 算術表達式 ))
  • 作為表達式,獲取結果:$(( 算術表達式 ))
a=10
b=5# 1. 賦值
(( c = a + b * 2 ))
echo $c  # 輸出: 20# 2. 獲取結果
result=$(( a + b ))
echo $result # 輸出: 15# 3. 在 if 語句中直接使用
if (( a > b )); thenecho "a 大于 b"
fi

注意:

  • ((…))相比于let的巨大優勢:表達式內可以隨意使用空格,不需要引號;
  • 變量引用無需$:和let 一樣,內部的變量無需 $ 前綴。
  • 使用((…))處理整數判斷,進而替代[[…]]的一部分功能,使用起來更接近C語言,可以直接用于流程控制:if 和 while 語句可以直接使用雙括號進行判斷。
if-then結構

首先還是要強調if語句的核心本質:檢查命令的退出狀態碼。在很多編程語言(如 Python,Java)中,if 后面跟的是一個布爾值(true/false)。但在 Bash 中,if 的工作方式有本質不同:if 后面跟的是一個或多個 命令,它檢查的是這些命令執行后的 退出狀態碼 (Exit Status)。

# -q 選項讓 grep "安靜"執行,不輸出任何內容,只通過退出狀態碼報告結果
if grep -q "root" /etc/passwd; thenecho "文件中找到了 'root' 用戶。"
fi
if的三種結構
  1. 單分支
if <命令或條件>;then<要執行的代碼塊>
fi# 使用我們之前學過的 [[ ... ]] 作為判斷條件
if [[ -d "/var/log" ]]; thenecho "/var/log 是一個目錄。"
fi
  1. 雙分支
if <命令或條件>;then<條件為真時執行的代碼塊>
else<條件為假時執行的代碼塊>
fiif [[ "$USER" == "root" ]]; thenecho "當前用戶是 root,擁有最高權限。"
elseecho "當前用戶是 $USER,一個普通用戶。"
fi
  1. 多分支
if <條件1>;then<條件1為真時執行的代碼塊>
elif <條件2>;then<條件2為真時執行的代碼塊>
elif <條件3>;then<條件3為真時執行的代碼塊>
...
else<所有條件都為假時執行的代碼塊>
firead -p "請輸入你的分數 (0-100): " score
if (( score >= 90 )); thenecho "優秀 (A)"
elif (( score >= 80 )); thenecho "良好 (B)"
elif (( score >= 60 )); thenecho "及格 (C)"
elseecho "不及格 (F)"
fi
if的判斷條件類型

if后面可以跟任何“命令”,但最常用的是以下三種:

  1. 使用 test, [] 或 [[ … ]]
  • 文件測試: if [[ -f “$file” ]]
  • 字符串測試: if [[ “str1"=="str1" == "str1"=="str2” ]]
  • 整數測試: if [[ “num1"?gt"num1" -gt "num1"?gt"num2” ]]

強烈推薦在 Bash 腳本中使用 [[ … ]],因為它更健壯、功能更強。

  1. 使用算術運算 ((…))
    雙括號 ((…)) 用于整數算術運算。它同樣會返回一個退出狀態碼,這使得它可以非常直觀地用于 if 語句中的數字比較。
count=10
if (( count > 5 )); thenecho "count 大于 5。"
fi# 注意:(( 0 )) 會被認為是 false
if (( 0 )); thenecho "這句不會被打印"
elseecho "0 在 if 中被視為 false"
fi
  1. 使用任意普通命令
# 檢查網絡連通性
if ping -c 1 -W 1 "google.com" &> /dev/null; thenecho "網絡連接正常。"
elseecho "無法連接到外部網絡。"
fi
# &> /dev/null 的作用是將 ping 的所有輸出(標準和錯誤)都丟棄,我們只關心它的退出狀態碼。
if的一些使用注意
  • 優先使用 [[…]] 和 ((…)):在編寫 Bash 腳本時:進行文件和字符串判斷,優先使用 [[ … ]]。進行整數判斷,優先使用 (( … ))。
  • 變量引用要加雙引號 (“$var”):在 [[ … ]] 和特別是 [ … ] 中,對變量進行引用時,務必加上雙引號。這能防止當變量值包含空格或為空時,產生意想不到的錯誤。
case結構

和if的高度靈活不同,case語句是根據變量的不同取值來選擇分支的。

case的基本結構
case <變量> in<模式1>)<命令塊1>;;<模式2>)<命令塊2>;;<模式3> | <模式4>) # 模式3或模式4<命令塊3>;;*)<默認命令塊>;;
esac
  • case <變量> in: 語句的開始。<變量> 通常是一個變量的引用,如 $action。in 是關鍵字。
  • <模式n>): 每一個分支的匹配模式。模式后面的 ) 是必需的。
  • <命令塊n>: 如果變量的值匹配了該模式,則執行這里的命令。
  • ;; (雙分號): 這是 case 語句中至關重要的部分。它標志著一個命令塊的結束,其作用類似于其他語言中 switch 的 break。執行完命令塊后,遇到 ;; 就會直接跳到 esac,結束整個 case 語句。
  • *): 這是一個特殊的通配符模式,可以匹配任何沒有被前面模式捕獲到的值。它通常作為默認分支,放在最后。
  • esac: case 語句的結束標志 (case 的反寫)。

case的強大之處:它天然支持通配符匹配,靈活性很強:

  • *: 匹配任意長度的任意字符序列。
  • ?: 匹配任意單個字符。
  • […]: 匹配方括號中任意一個字符。例如 [0-9] 匹配任意數字,[a-z] 匹配任意小寫字母。
  • |: 或操作符,用于在一個分支中匹配多個模式

舉一些例子:

read -p "請輸入一個字符: " char
case "$char" in[a-z])echo "你輸入了一個小寫字母。";;[A-Z])echo "你輸入了一個大寫字母。";;[0-9])echo "你輸入了一個數字。";;?)echo "你輸入了一個特殊符號。";;*)echo "你輸入了多個字符。";;
esac
#!/bin/bash
# 用法: ./classify_file.sh document.pdffilename="$1"case "$filename" in*.jpg | *.jpeg | *.png | *.gif)echo "'$filename' 是一個圖片文件。";;*.tar.gz | *.zip | *.rar)echo "'$filename' 是一個壓縮包文件。";;*.sh)echo "'$filename' 是一個 Shell 腳本文件。";;*)echo "無法識別 '$filename' 的文件類型或它沒有擴展名。";;
esac
case使用的一些注意事項
  • 別忘了 ;;:這是初學者最容易犯的錯誤。忘記寫 ;; 會導致“穿透”(fall-through),即匹配成功后,腳本會繼續執行下一個分支的命令塊,直到遇到 ;; 或 esac 為止。這種行為在絕大多數情況下都不是我們想要的,所以請務必在每個命令塊的末尾加上 ;;。
  • 引用變量 (case “$var” in):在 case 語句中,總是用雙引號把要測試的變量包起來。這可以防止當變量為空或包含特殊字符時出現問題。
  • *) 默認分支的重要性:強烈建議總是提供一個 *) 默認分支。這能讓你的腳本更健壯,可以優雅地處理所有未預料到的輸入,而不是靜默失敗或產生錯誤。
  • 模式的順序:case 語句會從上到下依次匹配,一旦找到第一個匹配的模式,就會執行對應的代碼塊然后跳出。因此,應該將更具體的模式放在前面,更通用的模式(如 *)放在后面。
&&和||

我們知道&&和||可以在[[]]中用作邏輯判斷,但如果單獨使用它們也可以起到命令短路的作用。和if一樣,&&和||也是根據命令執行后的狀態碼來工作的:
基本語法:

command1 && command2 # 只有當command1成功執行(退出狀態碼為0)時,command2 才會被執行。
command1 || command2 # 只有當command1執行失敗(退出狀態碼為1)時,command2 才會被執行。

例如:

# 如果 mkdir 由于權限等問題失敗,cd 就不會執行,避免了進入錯誤目錄的風險
mkdir -p /var/data/new_project && cd /var/data/new_project# 嘗試 ping 谷歌,如果失敗(比如沒網),就打印錯誤信息
ping -c 1 google.com &> /dev/null || echo "錯誤:網絡連接中斷。"

二者還可以組合起來用,實現簡單的 if-then-else 邏輯:

command1 && command2 || command3# 執行邏輯:
# 執行 command1。
# 如果 command1 成功 (0),則執行 command2。 
# 如果 command1 失敗 (非 0),則執行 command3。

但是,這種組合不完全等價于if-else。因為它的行為依賴于 command2 的退出狀態。看這個例子: true && false || echo “This gets printed”

true 執行成功。于是 && 后面的 false 被執行。false 命令執行失敗(退出狀態碼為1)。于是 || 后面的 echo 被執行了!這通常不是我們想要的結果。在 if/else 中,如果 true 成立,else 部分是絕對不會執行的。

對于復雜的邏輯判斷,使用標準的 if/then/else/fi 結構更清晰、更安全。

Bash腳本的循環結構
for循環
for循環的兩種基本結構

Bash 中的 for 循環主要有兩種風格:一種是傳統的列表式循環,另一種是類似 C 語言的數值計算循環。

  1. 形式一:列表式for循環。
    這是Shell腳本中最經典、最常用、最靈活的for循環形式。
for <變量名> in <項目列表>;do<循環體代碼,使用 $變量名>
done

這樣形式的for循環使用起來靈活多變,以下是項目列表的多種形式

# 顯式列表(或數組)
for color in "red" "green" "blue"
doecho "當前顏色是: $color"
done# 范圍序列(花括號擴展)
for i in {1..10..2}
doecho "奇數: $i"打印 1 3 5 7 9
done# 文件名通配符 —> 這是極其常用的方式,用于處理當前目錄下的文件。
for file in *.txt
do# 引用變量時一定要加雙引號,以防文件名包含空格mv "$file" "${file}.bak"echo "已將 '$file' 重命名為 '${file}.bak'"
done# 命令替換
for user in $(cat user_list.txt)
doecho "正在為用戶 $user 創建家目錄..."
done
# 注意:這種方式對于包含空格或特殊字符的輸出處理不佳,容易出錯。對于逐行讀取文件內容,更健壯的方法是使用 while read 循環。# 腳本參數 ("$@")
echo "你傳入了 $# 個參數,它們是:"
for arg in "$@"
doecho "參數: $arg"
done
  1. 形式二:C語言風格for循環。
    這種形式對于有其他編程語言背景的開發者來說非常熟悉,主要用于數值計算和控制循環次數。
for (( 初始化; 條件; 迭代表達式 ));do<循環體代碼>
done

舉個例子:

# 計算 1 到 100 的和
sum=0
for (( i=1; i<=100; i++ ))
do(( sum += i ))
done
echo "1 到 100 的總和是: $sum"

這種寫法語法直觀,類似C。并且在雙括號 ((…)) 內,變量引用可以不加$。

for循環的注意事項
  • 引用循環變量時加雙引號:這是最重要的規則之一。在循環體中使用循環變量時,請務必用雙引號包裹,如 “$item”。這能防止因項目內容包含空格或特殊字符而導致命令執行失敗。
  • 避免 for file in $(ls) 的寫法:這是一個非常經典的反面教材。ls 的輸出格式不穩定,且當文件名包含空格時,$(ls) 的結果會被“分詞”,導致循環出錯。直接使用文件名通配符 for file in * 是更安全、更高效的做法。
while循環

while 循環用于只要某個條件持續為真(命令返回退出狀態碼 0),就重復執行一段代碼塊。

while的基本語法:
while <條件命令> ;do<循環體代碼>
done

while 的條件命令和if非常像,<條件命令> 最常見的形式是 [[ … ]](測試條件)和 (( … ))(算術條件),但也可以是任何能返回退出狀態碼的命令。

while的常見用法
  1. 逐行讀取文件(王牌用法)
while IFS= read -r line
doecho "正在處理行: $line"
done < "filename.txt"

說明:

  • read 是一個內建命令,用于從標準輸入讀取一行數據。當它成功讀到一行時,返回退出狀態碼 0;當它讀到文件末尾(EOF)時,返回非 0。這恰好完美契合 while 循環的機制。
  • -r: (raw read)選項,防止 read 命令對反斜杠 \進行轉義。在處理文件路徑或包含特殊字符的行時,強烈建議總是加上 -r。
  • line: 你定義的變量名,read 命令會將讀到的整行內容(除了行尾的換行符)賦值給這個變量。
  • IFS 是“內部字段分隔符”的縮寫,Shell 默認用它來分割單詞(默認為空格、制表符、換行符)。在read命令前加上 IFS=(將其臨時設置為空),可以防止 read 命令修剪掉行首行尾的空格和制表符,保證讀到的 line 變量內容與文件中的行內容完全一致。這也是一個最佳實踐。
  • < “filename.txt”:這是輸入重定向。它將 filename.txt 文件的內容作為整個 while 循環的標準輸入。這樣,read 命令就能從這個文件中逐行讀取數據。
# servers.list文件如下
google.com
github.com
# local.server
192.168.1.1#!/bin/bash
while IFS= read -r server
do# 跳過空行和注釋行if [[ -z "$server" || "$server" == \#* ]]; thencontinuefiecho "--- 正在檢查服務器: $server ---"ping -c 1 "$server"
done < "servers.list"

特別注意:不能使用管道代替重定向cat file.txt | while …; do …; done。使用管道時,| 右邊的 while 循環會在一個新的子 Shell中執行。這意味著在循環內部對變量做的任何修改,在循環結束后都會丟失。

  1. 無限循環 ->用于需要持續運行的服務、監控或主菜單程序。
# 使用 true 命令,它永遠返回 0
while true
doecho "系統正在運行... 按 [CTRL+C] 退出。"# ... 執行監控任務 ...sleep 5
done
while的注意事項
  • 避免死循環:確保你的循環體內部有邏輯能最終改變 while 的判斷條件,使其變為假,除非你有意編寫無限循環。
until循環

until循環就是while循環的反意詞:until 循環會只要某個條件持續為假(命令返回非 0 退出狀態碼),就重復執行一段代碼塊。當條件第一次變為真(命令返回退出狀態碼 0)時,循環就會停止。

until的基本語法:

until <條件命令>;do<循環體代碼>
done

其等效為:

while !<條件命令>;do<循環體代碼>
done

! 是一個邏輯非操作符,它會反轉后面命令的退出狀態碼(0 變成 1,非 0 變成 0)。

循環控制指令

和C語言一樣,Bash也使用continue和break控制循環,并且功能類似。

break

基本語法:

break [n]

[n] 是一個可選的整數參數,代表要跳出的循環層數。如果省略,n 默認為 1,即跳出當前最內層的循環。

示例:

#!/bin/bash
TARGET_FILE="sshd_config"echo "開始在 /etc/ 目錄中搜索文件 '$TARGET_FILE'..."for file in /etc/*
do# basename 命令用于提取路徑中的文件名部分if [[ "$(basename "$file")" == "$TARGET_FILE" ]]; thenecho ""echo "找到了!文件路徑是: $file"break # 任務完成,立即跳出 for 循環fi# -n 讓 echo 不換行,制造一個動態的搜索效果echo -n "."sleep 0.05
doneecho ""
echo "搜索過程結束。"
continue

基本語法:

continue [n]

[n] 同樣是可選參數,代表要“繼續”的是第幾層循環。如果省略,n 默認為 1,即作用于當前最內層的循環。

#!/bin/bash
sum=0for i in {1..10}
do# (( i % 2 != 0 )) 判斷是否為奇數if (( i % 2 != 0 )); then# 如果是奇數,就跳過本次循環的剩余部分(即下面的 sum+=i)# 直接開始下一次循環(i會變成下一個值)continuefiecho "將偶數 $i 加入總和。"(( sum += i ))
doneecho "1到10之間所有偶數的和是: $sum"

注意:

  • 對于 while true 或 until false 這樣的無限循環,break 是唯一的程序化退出方式(不包括 exit 或 kill)。必須在循環體內設計一個或多個 if 條件來觸發 break,否則循環將永不停止。
Bash腳本的函數
函數的定義

Bash 中,定義函數有兩種常見的方式,它們在功能上是等價的。

函數名() {<命令塊>
}# 或者function 函數名 {<命令塊>
}

在腳本中,函數必須先被定義,然后才能被調用。Shell 是自上而下解釋執行的,它需要先“學習”到函數的存在,之后才能執行它。

函數的調用

直接使用函數名即可:

#!/bin/bash
# 定義一個問候函數
greet() {echo "你好, 歡迎來到 Bash 函數的世界!"
}echo "準備調用函數..."
greet # 調用 greet 函數
echo "函數調用完畢。"
函數的傳參

函數內部處理參數的方式與腳本處理位置參數的方式完全相同。
例如:

  • $1, $2, $3…: 代表傳遞給函數的第 1、2、3 個參數。
  • $#: 代表傳遞給函數的參數總數。
  • $@: 代表傳遞給函數的所有參數列表。
# 定義一個可以向特定人問好的函數
greet_someone() {# $1 指的是傳遞給 greet_someone 的第一個參數echo "你好, $1! 祝你有美好的一天。"
}# 調用函數并傳遞參數
greet_someone "Alice"
greet_someone "Bob"
函數變量的作用域
  1. 局部變量
    在 Bash 中,默認情況下,你在任何地方(包括函數內部)定義的變量都是全局變量。想要使用局部變量需要local關鍵字,local 聲明的變量只在當前函數的作用域內有效,函數執行完畢后,該變量就會被銷毀。
#!/bin/bash
count=100 # 全局變量update_count() {local count=10 # 這是一個全新的、只屬于函數的局部變量(( count++ ))echo "函數內的 count: $count"
}echo "調用前, 全局 count: $count"
update_count
echo "調用后, 全局 count: $count" # 全局變量并未被修改# 輸出
調用前, 全局 count: 100
函數內的 count: 11
調用后, 全局 count: 100
  1. 引用變量
    默認情況下,我們輸入給函數的參數在執行后不會影響原來的參數的,想要實現C語言中參數引用的效果,可以使用local -n關鍵字。
#/bin/bash
modify_var() {local -n ref=$1  # 引用變量名ref="新值"
}
value="舊值"
modify_var value
echo "$value"  # 輸出: 新值# 輸出
新值 # 不加上-n輸出是"舊值"
函數的執行結果
  1. 返回狀態
    return 命令用于設置函數的退出狀態碼 ($?),它和普通命令的退出碼作用一樣,用于表示函數執行的成功或失敗。
is_file_exist() {if [[ -f "$1" ]]; thenreturn 0 # 文件存在,返回成功elsereturn 1 # 文件不存在,返回失敗fi
}# 使用 if 來捕獲函數的退出狀態
if is_file_exist "/etc/hosts"; thenecho "狀態檢查成功:文件 /etc/hosts 存在。"
elseecho "狀態檢查失敗:文件 /etc/hosts 不存在。"
fi
  1. 返回數據:使用 echo 和命令替換
    Shell 函數不能像其他語言那樣直接返回一個字符串、數組或其他復雜數據。
    要從函數中獲取數據(比如一個計算結果或處理過的字符串),標準做法是:
    在函數中,使用 echo 將結果打印到標準輸出;在調用函數的地方,使用命令替換 $(…) 來捕獲這個輸出,并賦值給一個變量。
get_timestamp() {# 將 date 命令的結果打印到標準輸出echo "$(date +%Y-%m-%d_%H:%M:%S)"
}# 使用命令替換來“接收”函數的返回值
log_prefix=$(get_timestamp)
echo "[$log_prefix] 應用程序已啟動。"## 輸出
[2025-07-16_17:11:05] 應用程序已啟動。
總結函數的注意事項
  • 先定義后調用:始終確保在調用函數之前,它已經被 Shell 讀取和定義。通常的做法是將所有函數定義放在腳本的開頭。
  • 函數內部多用 local:這是編寫高質量函數的黃金法則。除非你刻意要修改一個全局狀態,否則函數內的所有變量都應該用 local 聲明。
  • return 用于狀態,echo 用于數據:清晰地區分這兩種“返回”方式。不要試圖用 return 來返回一個字符串(例如 return “some string” 是無效的)。
  • 保持函數單一職責:一個好的函數應該只做一件事,并把它做好。這使得函數更容易理解、測試和復用。
  • 傳遞參數使用 “@":如果需要將腳本收到的所有參數原封不動地傳遞給一個函數,請使用myfunction"@":如果需要將腳本收到的所有參數原封不動地傳遞給一個函數,請使用 my_function "@":如果需要將腳本收到的所有參數原封不動地傳遞給一個函數,請使用myf?unction"@”。
Bash腳本的Debug
語法檢查
  • 使用shellcheck工具對Bash腳本進行語法檢測
腳本邏輯的debug
  • 注釋大法,把可疑的問題代碼注釋一下確定問題來源
  • 打印大法,對關鍵變量echo一下
  • 把輸出重定向到log文件中處理
  • 借助調試命令set、trap、strace。
  1. set命令
    set 主要用來開啟“嚴格模式”和“追蹤模式”,讓腳本在出錯時立即失敗,或者清晰地展示出每一行代碼的執行情況。可以在腳本的任何地方使用 set -<選項> 來開啟,用 set +<選項> 來關閉。
  • set -e :錯誤退出
    作用:當腳本中的任何命令執行后返回非 0 的退出狀態碼時(即執行失敗),立即退出整個腳本。這能防止錯誤像滾雪球一樣越滾越大。例如,如果 cd到一個不存在的目錄失敗了,后續的 rm -rf * 就不會在錯誤的目錄下執行,避免了災難。
#!/bin/bash
set -eecho "準備創建一個不存在的目錄..."
ls /nonexistent_directory # 這個命令會失敗# 因為 set -e,下面這行代碼將永遠不會被執行
echo "腳本執行完畢。" 

在某些情況下,set -e 不會立即退出,例如在 if 或 while 的條件判斷中,或者命令后面跟著 &&, || 時,因為 Shell 認為這些錯誤已經被“處理”了。

  • set -u:未定義變量視為錯誤
    當腳本嘗試使用一個未被定義的變量時,視為錯誤并立即退出。這能幫你捕捉到變量名的拼寫錯誤。
#!/bin/bash
set -uMY_NAME="Alex"
# 假設手誤,將 MY_NAME 寫成了 MY_NAM
echo "你好, $MY_NAM" # 這行會觸發錯誤,腳本退出
  • set -x:執行追蹤
    這是最常用的調試工具。在執行每一行命令之前,Shell 會先把它打印到標準錯誤輸出,并且是變量和通配符都已展開后的最終樣子。你可以清晰地看到每個變量在執行時的實際值,以及命令最終是以何種形態被執行的。
#!/bin/bash
set -x # 開啟追蹤USER_COUNT=$(who | wc -l)
echo "當前有 $USER_COUNT 個用戶登錄。"set +x # 關閉追蹤
echo "追蹤已關閉。"
  • set -o pipefail:管道失敗
    在管道命令中(如 cmd1 | cmd2),只要有任何一個命令失敗,整個管道的退出狀態碼就是失敗(非 0)。默認情況下,只有最后一個命令的退出碼才算數。保證了管道中任何一步的失敗都能被捕獲到。

這些選項可以組合在一起使用,set -euxo pipefail 是一個非常流行的“非官方嚴格模式”的開頭。

  1. trap 命令
    介紹trap前需要先講一下Linux的信號。信號是操作系統與進程之間通信的一種方式。對于 trap,最關心的幾個信號是:
  • EXIT: 腳本退出時觸發(無論正常退出、出錯退出還是被中斷)。這是最適合用于清理工作的信號。
  • ERR: 當任何命令執行失敗(返回非 0 狀態碼)時觸發(set -e 可能會影響其行為)。
  • INT: 當用戶按下 Ctrl+C 中斷腳本時觸發。
  • TERM: 當腳本被 kill 命令(默認信號)終止時觸發。

trap的語法和示例

trap '<要執行的命令或函數>' <信號1> [<信號2> ...]#!/bin/bash
# 創建一個臨時文件,文件名包含進程ID以保證唯一性
TEMP_FILE="/tmp/my_app_temp_$$"
touch "$TEMP_FILE"# 定義一個清理函數
cleanup() {echo "捕獲到退出信號,正在執行清理..."rm -f "$TEMP_FILE"echo "臨時文件 '$TEMP_FILE' 已刪除。"
}# 設置 trap:無論腳本如何退出(正常、出錯、被中斷),都調用 cleanup 函數
trap cleanup EXITecho "腳本正在執行,臨時文件是 $TEMP_FILE"
echo "你可以嘗試用 Ctrl+C 中斷此腳本來測試 trap。"
sleep 20
echo "腳本正常結束。"# 當你運行這個腳本,無論你是等它自然結束,還是中途按 Ctrl+C,最后的清理函數總會被調用。

如果你想讓腳本在運行時不被 Ctrl+C 中斷,可以設置一個空的 trap。:

trap '' INT
echo "你無法用 Ctrl+C 中斷我..."
sleep 10

注意:trap 的位置:trap 命令應該放在腳本的開頭部分,以確保它能盡早生效。

  1. strace命令(初學者不推薦)
    strace 用于追蹤一個程序在運行期間發生的所有系統調用和接收到的信號。
其他

其實還有一些交互式的debug工具,由于我們寫的腳本一般比較簡單,這里先略過。

習題部分

題目一:

ls -l -a -h -t --color=yes

題目二:
首先說明一下我的工作目錄:/home/michael/Documents/YSYX/Yuxuexi/StudyProj/2/homework

以下是我的腳本:

## macro.sh#/bin/bash
echo "$PWD" > ~/Documents/YSYX/Yuxuexi/StudyProj/2/homework/macro_dst.txt # 保存當前路徑## polo.sh#!/bin/bash
cat ~/Documents/YSYX/Yuxuexi/StudyProj/2/homework/macro_dst.txt # 先顯示一下保存的路徑
cd "$(cat ~/Documents/YSYX/Yuxuexi/StudyProj/2/homework/macro_dst.txt)" # 載入保存的路徑

在運行腳本的時候也需要注意:

export PATH="$PATH:$PWD" # 在工作目錄下運行
macro.sh # 在任意目錄下運行,保存路徑
source polo.sh # 在任意目錄下運行,并且使用source在當前shell中執行

值得說明的是答案的思路,它直接使用了source,并在marco下編寫這樣的腳本:

!/bin/bashmarco(){echo "$(pwd)" > $HOME/Documents/YSYX/Yuxuexi/StudyProj/2/homework/answer/marco_history.logecho "save  pwd $(pwd)"
}polo(){cd "$(cat "$HOME/Documents/YSYX/Yuxuexi/StudyProj/2/homework/answer/marco_history.log")"
}

這樣使用時也很方便。我的方法麻煩地多,主要是因為我之前不知道source的機制,以為source只會讓腳本在當前shell執行,其實source還有其他功能: ?讓腳本中的變量、函數或環境變更直接影響當前 Shell 會話??。

題目三
我先將題干的代碼放在一個debug.sh的腳本中,然后在同一個目錄下寫下如下dedebug.sh的腳本:

#!/bin/bashi=0
echo "" > debug.txt #清空文件
while true; do(( i++ ))echo "***第${i}次執行***" >>  debug.txt./debug.sh >> debug.txt 2>> debug.txtif [[ $? -eq 0 ]]; then #執行成功echo "現在共運行${i}次"elseecho "在第${i}次運行出現錯誤,現在打印報告"cat "./debug.txt"breakfi
done

題目四

find -name "*.html" -print0 | xargs -0 tar -cf html.tar
  • find自帶遞歸
  • print0用來防止文件中的空格
  • xargs用來講輸出的文件轉成路徑參數給tar
  • -0是配合-print0使用的
  • -cf是用來產生.tar的常用命令

上面實現的效果其實有一些不足,就是它會把包好html文件的文件夾也一起壓縮,不是純粹的提取html文件

題目五
這一題沒太讀懂,想著不是使用ls -R -l -t就可以按時間列出文件了嗎,后來明白這一題只能輸出一行,并且只能是文件(不包含文件夾),看了答案如下:

find -type f -print0 | xargs -0 ls -l -t |head -1

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/89855.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/89855.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/89855.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

教育科技內容平臺的用戶定位與產品方案:從需求到解決方案的精準匹配

教育科技內容平臺的用戶定位與產品方案&#xff1a;從需求到解決方案的精準匹配打造一款成功的內容平臺&#xff0c;核心在于 “懂用戶”—— 明確不同用戶的需求場景、使用目的&#xff0c;才能設計出真正有價值的產品功能。本文以面向互聯網從業者的教育科技內容平臺為例&…

網絡之路16:認識虛擬化環境H3C CAS

正文共&#xff1a;3888 字 54 圖&#xff0c;預估閱讀時間&#xff1a;6 分鐘目錄網絡之路第一章&#xff1a;Windows系統中的網絡0、序言1、Windows系統中的網絡 1.1、桌面中的網卡 1.2、命令行中的網卡 1.3、路由表 1.4、家用路由器網絡之路第二章&#xff1a;認識企業設備2…

Sklearn 機器學習 IRIS數據 理解分類報告

??親愛的技術愛好者們,熱烈歡迎來到 Kant2048 的博客!我是 Thomas Kant,很開心能在CSDN上與你們相遇~?? 本博客的精華專欄: 【自動化測試】 【測試經驗】 【人工智能】 【Python】 Sklearn 機器學習 IRIS 數據分類報告解讀 鳶尾花(Iris)數據集是機器學習入門中最經典…

ni-app 對鴻蒙的支持現狀

自 HBuilderX 4.27 版本開始&#xff0c;uni-app 支持 Harmony Next 平臺的 App 開發&#xff0c;目前僅支持 Vue3 項目編譯到鴻蒙平臺。uni-app x 從 4.61 版本起支持純血鴻蒙&#xff0c;即 Harmony NEXT&#xff0c;其組件、API、CSS 與 Android 和 iOS 基本拉齊。 開發與配…

docker 容器學習

筆者來介紹一下docker 容器的學習1、docker容器背景 docker 里面有兩個概念&#xff0c;鏡像可看成一個類&#xff0c;而容器則是鏡像的一個實例&#xff0c;從這個來看&#xff0c;那么一般鏡像是一個&#xff0c;而容器可以有很多個。 鏡像&#xff1a;帶一堆工具鏈的操作系統…

MongoDB社區版安裝(windows)

下載地址 官網&#xff1a; MongoDB: The World’s Leading Modern Database | MongoDB 8.0.11版本下載地址&#xff1a; https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-8.0.11.zip 安裝 解壓安裝包 解壓結果目錄如下&#xff1a; bin目錄介紹 文件名稱作用…

Git上傳與下載GitHub倉庫

新建GitHub倉庫 … 本地上傳GitHub 第一步&#xff1a;git add .第二步&#xff1a;git commit -m your content第三步&#xff1a;git push xxx main或者git push xxx master 對于第三步&#xff0c;首先看自己建立的倉庫是master分支&#xff0c;還是main分支。以前都是默認m…

OpenCV 官翻 3 - 特征檢測 Feature Detection

文章目錄理解特征目標解釋Harris角點檢測目標理論OpenCV 中的 Harris 角點檢測器亞像素級精度角點檢測練習Shi-Tomasi角點檢測器與優質跟蹤特征目標理論基礎代碼SIFT&#xff08;尺度不變特征變換&#xff09;簡介目標理論1、尺度空間極值檢測2、關鍵點定位3、方向分配4、關鍵點…

2??處理文本數據

本章介紹 為大語言模型的訓練準備文本數據集將文本分割成詞和子詞token字節對編碼(Byte Pair Encoding,BPE):一種更為高級的文本分詞技術使用滑動窗口方法采樣訓練示例將tokens轉換為向量&#xff0c;輸入到大語言模型中 文章目錄本章介紹2.1 理解詞嵌入2.2 文本分詞2.3 將tok…

TestCase Studio - 自動生成測試用例詳解

你是否也曾為編寫測試用例而頭疼&#xff1f;點擊按鈕、填寫表單、截圖說明——這些重復操作讓人心生倦意。 而現在&#xff0c;只需動動鼠標&#xff0c;TestCase?Studio 就能自動錄制你的 Web 操作&#xff0c;生成清晰的“Plain English”步驟、截圖和定位器&#xff0c;徹…

Rust+ChatBoxAI:實戰

Chatbox AI Chatbox AI 是一款基于人工智能技術的智能助手工具,旨在通過自然語言交互幫助用戶完成多種任務。以下是其核心功能與特點: 功能概述 多模型支持:可連接 OpenAI、Claude、Gemini 等主流大語言模型,用戶能自由切換不同 AI 服務。 本地運行:支持離線使用,數據隱…

服務器與工控機的區別解析

服務器和工控機雖然都是計算機&#xff0c;但它們的設計目標、使用環境和核心特性有本質的區別&#xff0c;就像轎車和越野車雖然都是車&#xff0c;但用途和構造截然不同。以下是它們的主要區別&#xff1a;核心設計目標&#xff1a;服務器&#xff1a; 數據處理、存儲、網絡服…

【大模型】深入解析大模型推理架構之 Prefill-Decode Disaggregation (PD分離)

深入解析大模型推理架構之 Prefill-Decode Disaggregation (PD分離) 文章目錄深入解析大模型推理架構之 Prefill-Decode Disaggregation (PD分離)1 從統一到分離&#xff0c;推理架構為何演進&#xff1f;2 什么是Prefill-Decode分離&#xff1f;3 PD分離系統的工作流程4 PD分離…

D3動畫--動態繪制文本下劃線,支持自定義曲線

前言&#xff1a;在現實生活中&#xff0c;看書的時候&#xff0c;在文本的下面畫個波浪線&#xff0c;畫個橫線&#xff0c;是很常見的行為。本篇文章使用D3動畫來實現一個給文本繪制下劃線的效果&#xff0c;可以暫停繪制&#xff0c;繼續繪制&#xff0c;重新繪制&#xff0…

單表查詢-分頁提前獲取數據

1、 問題 以下的例子如何優化呢&#xff1f; SELECT * FROM(SELECT INNER_TABLE.*, ROWNUM OUTER_TABLE_ROWNUM FROM (SELECT t1.* FROM ( SELECT * FROM T1 ) t1 WHERE 1 1 ORDER BY T1.TTIME DESC)INNER_TABLE ) OUTER_TABLE WHERE OUTER_TABLE_ROWNUM<25AND OUTER_TA…

Oracle觸發器:數據世界的“隱形守護者“

今天&#xff0c;我想和大家聊一個在Oracle數據庫領域既強大又神秘的話題——觸發器&#xff08;Trigger&#xff09;?。在座的各位可能都寫過SQL語句&#xff0c;做過表結構設計&#xff0c;甚至用過存儲過程&#xff0c;但有很多人對觸發器的態度可能是"既愛又怕"…

Python桌面版數獨游戲(三版)-增加難易度模式

數獨游戲難度模式解析 在數獨游戲中&#xff0c;難度通常由已知數字&#xff08;提示數&#xff09;的數量決定。難度越高&#xff0c;已知數字越少&#xff0c;玩家需要推理的步驟越多。以下是不同模式下的算法區別和核心代碼解析。 文章目錄數獨游戲難度模式解析1. **難度模…

k8s查看某個pod的svc

在 Kubernetes 中&#xff0c;要查看與特定 Pod 相關的 Service&#xff0c;可以通過以下方法&#xff1a;#### 方法一&#xff1a;通過標簽匹配1. **獲取 Pod 的標簽**bashkubectl get pod <pod-name> --show-labels輸出示例&#xff1a;NAME READY STATUS RESTARTS AGE…

通俗易懂卷積神經網絡(CNN)指南

本文用直觀類比和可視化方法&#xff0c;幫你徹底理解CNN的工作原理&#xff0c;無需深厚數學基礎也能掌握計算機視覺的核心技術。卷積神經網絡&#xff08;CNN&#xff09;是深度學習中革命性的架構&#xff0c;它徹底改變了計算機"看世界"的方式。本文將用最直觀的…

AV1平滑緩沖區

對于解碼的每一幀視頻數據&#xff0c;解碼器都必須從緩沖池中找到一個尚未被使用的幀緩沖區插槽來存儲解碼后的數據。分配的幀緩沖區插槽用于臨時保存解碼過程中生成的幀數據&#xff0c;直到它們被用于顯示或進一步的處理。函數get_free_buffer的作用是在緩沖池中搜索尚未被分…