### 以日期為檔名命名
```bash
#!/bin/bash
# 獲取當前日期
current_date=$(date +%Y-%m-%d)
# 檔名
filename="file_${current_date}.txt"
# 使用檔名進行相應操作
echo "This is a sample file" > "$filename"
date +%Y-%m-%d # 2023-07-07
date +%Y%m%d # 20230707
```
---
# 函式
```bash
greet() {
local name=$1
echo "Hello, $name!"
}
greet "Alice" # 傳遞參數 "Alice" 給函式 greet
greet "Bob" # 傳遞參數 "Bob" 給函式 greet
```
帶有預設值的參數
```bash
#!/bin/bash
# 定義一個函式,帶有一個帶有預設值的參數
greet() {
name="${1:-Guest}" # 如果沒有提供參數,使用 "Guest" 作為預設值
echo "Hello, $name!"
}
# 呼叫函式,不提供參數
greet
# 呼叫函式,提供參數
greet "Alice"
```
---
# If
在bash中,if判斷式通常使用以下語法:
## 字串判斷
```
str="hello"
# 1. 判斷是否相等
if [[ "$str" == "hello" ]]; then
echo "字串完全相同"
fi
# 2. 判斷是否不相等
if [[ "$str" != "world" ]]; then
echo "字串不相同"
fi
filename="report_2026_draft.pdf"
# 1. 判斷是否以 "report_" 開頭 (右側 * 不加引號)
if [[ $filename == report_* ]]; then
echo "這是 report 開頭的檔案"
fi
# 2. 判斷是否以 ".pdf" 結尾 (右側 * 不加引號)
if [[ $filename == *.pdf ]]; then
echo "這是 PDF 檔案"
fi
# 3. 判斷是否包含某字串 (前後加 *)
if [[ $filename == *2026* ]]; then
echo "檔名包含 2026"
fi
```
## 數值判斷
### 方法一:雙括號語法 `(( ... ))`(最推薦)
`(( ... ))` 是 `bash` 專門用來處理數學運算與比較的語法。最大的優點是可以**直接使用標準的數學符號** (`>`, `<`, `>=`, `<=`, `==`, `!=`),而且**變數前面不需要加 `$` 符號**。
```bash
age=20
# 1. 判斷是否大於等於 (>=)
if (( age >= 18 )); then
echo "已成年"
fi
# 2. 判斷是否等於 (==)
if (( age == 20 )); then
echo "剛好 20 歲"
fi
# 3. 判斷是否不等於 (!=)
if (( age != 0 )); then
echo "年齡不為零"
fi
# 4. 複合判斷:可以直接用 && 或 ||,且支援括號
if (( age >= 18 && age < 65 )); then
echo "屬於勞動人口"
fi
```
### 方法二:條件判斷式 `[[ ... ]]`(傳統字母運算子)
如果習慣在 `[[ ... ]]` 裡面進行判斷,**不能**直接使用 `>` 或 `<`(因為在 `[[ ]]` 中 `>` 會被當作字串字典順序比較,而非數字比較)。必須使用英文縮寫的運算子:
| 英文縮寫 | 全稱 | 數學符號 | 範例 |
| --- | --- | --- | --- |
| **`-eq`** | **Eq**ual | `==` | `[[ $a -eq $b ]]` |
| **`-ne`** | **N**ot **E**qual | `!=` | `[[ $a -ne $b ]]` |
| **`-gt`** | **G**reater **T**han | `>` | `[[ $a -gt $b ]]` |
| **`-ge`** | **G**reater or **E**qual | `>=` | `[[ $a -ge $b ]]` |
| **`-lt`** | **L**ess **T**han | `<` | `[[ $a -lt $b ]]` |
| **`-le`** | **L**ess or **E**qual | `<=` | `[[ $a -le $b ]]` |
**範例:**
```bash
score=85
if [[ $score -ge 60 ]]; then
echo "及格"
fi
if [[ $score -gt 80 && $score -le 100 ]]; then
echo "優秀"
fi
```
### ⚠️ 重要陷阱:浮點數(小數點)問題
`bash` 原生**只支援整數運算**,不支援包含小數點的浮點數(例如 `3.14` 或 `85.5`)。若直接將小數傳給 `(( ... ))` 或 `-ge` 會跳出 `integer expression expected` 錯誤。
若要處理浮點數比較,需要呼叫外部命令 `bc`:
```bash
num1=3.14
num2=2.71
# 利用 bc 進行判斷,bc 成立會回傳 1,不成立回傳 0
if (( $(echo "$num1 > $num2" | bc -l) )); then
echo "$num1 大於 $num2"
fi
```
---
# for
Bash 的 `for` 迴圈主要有兩種常見寫法:**列表迭代(走訪項目)** 與 **C 語言風格(計數器)**。
## 📌 核心知識
* **列表迭代**:依序讀取每一個項目(字串、檔名、陣列內容等)。
* **C 語言風格**:使用數字計數器,指定起始值、終止條件與遞增量。
## 🔸 類似概念
* 列表迭代類似 Python 的 `for item in sequence:`
* C 語言風格類似 C/C++ 或 Java 的 `for (int i = 0; i < n; i++)`
## 📝 範例說明
**範例 1:走訪固定清單(列表迭代)**
語法:`for 變數 in 項目1 項目2 ...; do 動作; done`
```bash
# 印出水果名稱
for fruit in apple banana orange; do
echo "水果名稱:$fruit"
done
```
**範例 2:數字範圍(使用序列)**
語法:`for 變數 in {起始..結束}; do 動作; done`
```bash
# 印出 1 到 5 的數字
for i in {1..5}; do
echo "數字:$i"
done
```
**範例 3:C 語言風格(雙括號計數器)**
語法:`for (( 初始值; 條件; 遞增 )); do 動作; done`
```bash
# 從 1 數到 5,每次加 1
for (( i=1; i<=5; i++ )); do
echo "當前計數:$i"
done
```
**範例 4:處理目錄下的檔案**
```bash
# 批次印出所有 .txt 檔名
for file in *.txt; do
echo "找到文字檔:$file"
done
```
---
# 串列 List
In Bash 中,「串列」主要指 **陣列(Array)**。Bash 支援一維陣列,常用於儲存與處理多個資料項目。
## 📌 核心知識
* **宣告與建立**:使用圓括號 `(項1 項2)`,項目之間以**空格**分隔(非逗號)。
* **讀取全部元素**:使用 `${陣列名[@]}`。
* **取的元素數量**:使用 `${#陣列名[@]}`。
* **索引取值**:使用 `${陣列名[索引]}`,索引從 0 開始。
## 🔸 類似概念
* Bash 陣列就像 Python 的 `list` 或 JavaScript 的 `Array`,但 Bash 預設**沒有多維陣列**,且元素間嚴格以空白區隔。
## 📝 範例說明
**範例 1:宣告陣列與讀取**
```bash
# 宣告陣列
fruits=("apple" "banana" "orange")
# 讀取單一元素(索引從 0 開始)
echo "第一個水果:${fruits[0]}" # 輸出:apple
# 讀取所有元素
echo "所有水果:${fruits[@]}" # 輸出:apple banana orange
# 取得陣列長度(元素個數)
echo "總共有 ${#fruits[@]} 個水果" # 輸出:3
```
**範例 2:搭配 for 迴圈走訪陣列**
```bash
servers=("web01" "db01" "cache01")
# 方式 A:直接走訪元素
for server in "${servers[@]}"; do
echo "正在連接至:$server"
done
# 方式 B:透過索引走訪
for (( i=0; i<${#servers[@]}; i++ )); do
echo "第 $i 個伺服器是:${servers[$i]}"
done
```
**範例 3:新增與修改元素**
```bash
names=("Alice" "Bob")
# 附加新元素到末端
names+=("Charlie")
# 修改指定索引的值
names[1]="Bobby"
echo "更新後的名單:${names[@]}" # 輸出:Alice Bobby Charlie
```
---
# 輸入 Input
```bash
# 提示使用者輸入
echo "請輸入您的名字:"
# 讀取使用者輸入並將其存入變數中
read name
# 使用讀取到的值進行後續處理
echo "您輸入的名字是:$name"
# 使用 Esc 作為結束字元
read -d $'\e' input
```
```bash
#!/bin/bash
# 使用 read 命令接收輸入,如果使用者直接按下 Enter 鍵,則使用預設值 "John"
read -p "Enter your name (default is John): " -r name
name=${name:-John}
# 顯示輸入值
echo "Your name is: $name"
```
### 連續輸入
以下是一個擁有連續請使用者輸入功能的bash腳本:
```bash
#!/bin/bash
inputs=()
while true; do
read -p "請輸入內容 (按下Esc退出): " input
# 檢查使用者是否按下Esc鍵
if [[ $input == $'\e' ]]; then
break
fi
inputs+=("$input")
done
echo "輸入的內容為:"
for item in "${inputs[@]}"; do
echo "$item"
done
```
可以保存上面的程式碼到一個檔案(例如 `input_script.sh`),然後在終端中執行以下命令:
```bash
bash input_script.sh
```
使用者將可以連續輸入內容,直到按下Esc鍵。所有輸入的內容將被存入串列並顯示在終端中。
### Here Document
```bash
#!/bin/bash
# 產生另一個 shell script
# 創建或覆蓋新的腳本文件 new_script.sh
cat << EOF > new_script.sh
#!/bin/bash
echo "This is the new script!"
EOF
# 給予可執行權限
chmod +x new_script.sh
echo "New script file 'new_script.sh' generated!"
```
### 資料夾或檔案
```bash
# 目前位置的所有檔案
for file in *; do
# 如果是資料夾
if [ -d "$file" ]; then
echo "$file is Folder"
fi
# 如果是檔案
if [ -f "$file" ]; then
echo "$file is File"
fi
done
```
### 檔名空白處理
```bash
filename=$(printf '%q' "$file")
```
### 僅檔名(不要副檔名)
```bash
filename_no_extension="${filename%.*}"
```
### eval
在bash shell中,`eval` 是一個用於執行字串中包含的命令的指令。它會將字串視為一個命令並執行它。`eval` 指令通常用於將變數或其他字串中的命令執行起來。
例如,假設有一個變數包含一個命令:
```
command="ls -l"
```
要執行這個命令,你可以使用 `eval` 指令:
```
eval $command
```
這將會顯示目前目錄下的所有檔案和資料夾的詳細資訊。
要注意的是,使用 `eval` 指令時,必須非常謹慎,特別是當命令是來自不受信任的來源時,因為它可能會造成安全漏洞或意外的結果。
```bash
#!/bin/bash
# 定義一個函式,接受命令字符串作為參數並執行它
run_command() {
command_to_run="$1"
echo "Running command: $command_to_run"
eval "$command_to_run"
}
# 使用 run_command 函式來執行不同的命令
run_command "ls -l"
run_command "echo Hello, World!"
```
### Debug
在 bash 腳本中,使用 `set -x` 可以啟用 debug 模式,會讓腳本的每一行在執行時都會被顯示出來。這樣可以幫助你追蹤程式執行的過程,找出問題所在。
要在腳本中啟用 debug 模式,只需要在腳本的一開始加入以下行:
```bash
set -x
```
接著執行腳本時,你會看到每一行指令被顯示出來,方便你檢查程式的執行過程。
```bash
$ bash myscript.sh
+ echo 'Hello, world!'
Hello, world!
+ echo 'This is a debug message'
This is a debug message
```
當你完成 debug 後,可以使用 `set +x` 來關閉 debug 模式:
```bash
set +x
```
這樣就可以關閉 debug 模式,腳本會恢復到正常執行狀態。
# While
`while` 是一個迴圈結構,能夠不斷重複執行特定的指令,直到滿足某個條件為止。
以下是一個簡單的 `while` 迴圈的範例:
```bash
#!/bin/bash
count=1
while [ $count -le 5 ]
do
echo "Count is: $count"
((count++))
done
```
在這個範例中,我們使用 `while [ $count -le 5 ]` 來設定迴圈的條件,即當 `count` 變數小於或等於 5 時,迴圈會不斷執行。在迴圈內部我們使用 `echo` 來輸出 `count` 的值,然後執行 `((count++))` 來將 `count` 變數加一。
當 `count` 變數等於 6 時,條件不再滿足,迴圈就會終止。
### While true
```bash
#!/bin/bash
count=1
while true
do
echo "Count: $count"
count=$((count+1))
if [ $count -eq 6 ]
then
break
fi
done
```
這個腳本會不斷地印出 count 變數的值,直到 count 等於 6 時才會停止執行並結束迴圈。當 count 變數達到 6 時,便會觸發 if 條件式並執行 break 來跳出迴圈。
### While 檔案讀取
在Bash腳本中,您可以使用while迴圈來讀取檔案的內容。以下是一個示例腳本,演示如何使用while迴圈從檔案中讀取每一行數據:
```bash
#!/bin/bash
# 請更換成您要讀取的檔案路徑
file="sample.txt"
# 使用while迴圈讀取檔案內容
while IFS= read -r line
do
echo "$line"
done < "$file"
```
在這個示例中,我們將檔案路徑指定給變數file,然後使用while迴圈和read命令來一行一行地讀取檔案中的內容並進行輸出。注意,我們使用<運算符將檔案作為迴圈的輸入。
您可以將這個示例腳本保存為一個.sh檔,然後在終端中運行它,來讀取檔案的內容並進行處理。
### 參數
在bash中,以下是一些特殊變數的含義和用法:
1. `$0`:代表目前執行的腳本名稱。
2. `$@`:代表所有參數的列表。
3. `$1`、`$2`、`$3`...:代表分別是第一個、第二個、第三個參數的值。
4. `$#`:代表參數的個數。
下面是一個示例腳本,假設腳本名稱為example.sh,有三個參數:
```bash
#!/bin/bash
echo "腳本名稱:$0"
echo "所有參數:$@"
echo "第一個參數:$1"
echo "第二個參數:$2"
echo "第三個參數:$3"
echo "參數個數:$#"
```
執行腳本時,可以透過以下方式傳入參數:
```bash
./example.sh arg1 arg2 arg3
```
輸出結果將如下:
```
腳本名稱:example.sh
所有參數:arg1 arg2 arg3
第一個參數:arg1
第二個參數:arg2
第三個參數:arg3
參數個數:3
```
### 讀取檔案
你可以使用以下的 bash 腳本來迴圈列出檔案的每一行:
```bash
#!/bin/bash
file="example.txt"
while IFS= read -r line; do
echo "$line"
done < "$file"
```
在這個範例中,`example.txt` 是要處理的檔案名稱,`while IFS= read -r line; do` 是一個迴圈,使用 `read` 命令來讀取檔案的每一行,再將每一行的內容輸出至螢幕上。這樣就可以逐行輸出檔案的內容了。
IFS 是 Input Field Separator 的縮寫,可以用來設定用來切割讀取資料的分隔符號。在這個例子中,我們將 IFS 設定為空格,以便讀取包含空格的每一行。這樣可以確保讀取到完整的每一行,並避免遺失空格。
### 隨機字串
```bash
#!/bin/bash
randomString() {
# 宣告字串亂數池
pool="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
# 要生成的亂數字串長度
length=$1
# 初始化結果字串
result=""
# 使用迴圈從亂數池中隨機取出字元,直到達到指定長度
for (( i=0; i<length; i++ )); do
# 取得隨機索引值
index=$((RANDOM % ${#pool}))
# 取得隨機字元並加入結果字串
result="${result}${pool:index:1}"
done
# 輸出結果
echo $result
}
randomString 10 #ph0DQjVcM3
randomString 20 #09WdUYAZm5OxI5V0bZdQ
randomString 30 #4yDkmgxdB73Cu3hj6VGvns0GnYGlS1
```
### 數字格式化
```bash
formatNumber() {
num=$1
result=$(printf "%06d" $num) # 使用 printf 函式補零到6位數
echo $result
}
# 呼叫函式並輸出結果
result=$(formatNumber 123)
echo $result
```
---
# 引號困惑
Bash 中的引號主要用來控制「字串邊界」**以及**「變數與特殊符號是否會被解析(替換)」。
## 📌 核心知識
* **雙引號 `"..."`(弱引號)**:保持字串中的空格,但**允許**解析變數(`$VAR`)、命令替換(`$(cmd)`)與轉義符號(`\`)。
* **單引號 `'...'`(強引號)**:完全字面化,包在裡面的所有字元都會**原封不動**保留,不解析任何變數或符號。
* **不使用引號**:Bash 會以**空白**進行字詞分割(Word Splitting),且會觸發檔名展開(Wildcards/Globbing,如 `*`、`?`)與變數解析。
## 🔸 類似概念
* 雙引號就像 Python 的 `f"Hello {name}"` 或 JavaScript 的 ``Hello ${name}``(會替換變數)。
* 單引號就像 Python 的 raw string `r"..."` 或純字串,變數寫進去只會被當成普通文字。
## 📝 範例說明
**範例 1:變數解析與空格處理**
```bash
name="World"
# 1. 雙引號:會解析變數 $name
echo "Hello $name"
# 輸出:Hello World
# 2. 單引號:不解析變數,寫什麼印什麼
echo 'Hello $name'
# 輸出:Hello $name
# 3. 不加引號:若變數含空格,傳給指令時會被拆成多個參數
msg="Hello World"
echo $msg # 輸出:Hello World(多餘空格被吃掉了)
echo "$msg" # 輸出:Hello World(完整保留空格)
```
**範例 2:檔名展開(通配符 `*`)**
```bash
# 1. 不加引號:會展開目前目錄下的所有 .txt 檔名
echo *.txt
# 輸出:file1.txt file2.txt ...
# 2. 加單/雙引號:不會展開,直接輸出字詞本身
echo "*.txt"
# 輸出:*.txt
```
**範例 3:何時一定要加雙引號?**
在處理檔案路徑或變數時,**隨時加雙引號**是避免程式出錯(如路徑包含空格)的最安全習慣:
```bash
file_path="My Documents/notes.txt"
# 錯誤示範(不加引號):cat 會把 "My" 和 "Documents/notes.txt" 當成兩個檔案
cat $file_path
# 正確示範(加雙引號):完整傳遞帶空格的路徑
cat "$file_path"
```
## 💡 快速使用建議
1. **優先用雙引號 `"$VAR"**`:日常寫腳本時,只要用到變數,一律養成用雙引號包起來的習慣,可預防 90% 以上因空格引發的 Bug。
2. **需要純文字用單引號 `'...'**`:當內容包含大量的 `$`、`\`、`!` 等符號,且不希望 Bash 自動幫你解析時使用(例如編寫 regex 正則表達式或印出程式碼範例)。
3. **只有純數字、固定無空格字詞時才不加引號**:例如 `count=1` 或 `cd dir_name`。
---
# Regex
Bash 處理字串的 Regex(正則表達式)判斷與取值,主要採用 **`[[ ... ]]` 搭配 `=~` 運算子**,並透過 Bash 內建變數 **`BASH_REMATCH`** 來提取結果。
## 📌 核心知識
* **判斷條件**:使用 `[[ "$str" =~ $pattern ]]` 進行正則表達式比對。
* **取值機制**:比對成功後,結果會存入陣列 `BASH_REMATCH`:
* `${BASH_REMATCH[0]}`:代表整個比對到的完整字串。
* `${BASH_REMATCH[1]}`:代表第一個小括號 `()` 捕捉(Capture Group)到的內容。
* `${BASH_REMATCH[2]}`:代表第二個小括號捕捉到的內容,以此類推。
* **語法陷阱**:Pattern(正則表達式)**千萬不能加雙引號**,否則會被當作普通字串完全比對。
## 🔸 類似概念
* 類似 Python 的 `re.search(pattern, string)` 與 `match.group(1)`。
* 類似 JavaScript 的 `string.match(regex)` 與取用索引陣列。
## 📝 範例說明
**範例 1:Regex 條件判斷**
```bash
email="user@example.com"
# 判斷是否符合 Email 格式
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
echo "這是有效的 Email 格式"
else
echo "無效的 Email 格式"
fi
```
**範例 2:從字串中擷取數值(捕獲組 Capture Group)**
```bash
log="[2026-08-18] ERROR: Connection failed (code: 404)"
# 使用 () 捕捉日期與錯誤碼
pattern='\[([0-9]{4}-[0-9]{2}-[0-9]{2})\].*code: ([0-9]+)'
if [[ "$log" =~ $pattern ]]; then
echo "完整比對文字:${BASH_REMATCH[0]}"
echo "擷取到的日期:${BASH_REMATCH[1]}" # 輸出:2026-08-18
echo "擷取到的代碼:${BASH_REMATCH[2]}" # 輸出:404
fi
```
**範例 3:將 Pattern 拆成變數(建議做法)**
將複雜的 Regex 存入變數後再使用,可以避免特殊字元轉義引發的語法錯誤:
```bash
url="https://api.github.com/v1/users"
# 建議將正則表示式存在變數中(注意:變數給予值時不需要加引號,或者正常雙引號)
re_domain='https://([^/]+)'
if [[ "$url" =~ $re_domain ]]; then
echo "Domain 為:${BASH_REMATCH[1]}" # 輸出:api.github.com
fi
```
## 💡 注意事項
* 如果需要全域多重比對(例如尋找字串中「所有」出現的數字並全部取出),Bash 內建語法較難一次做完,此時建議搭配外部工具(如 `grep -oP`、`sed` 或 `perl`)。