---
# System prepended metadata

title: Git筆記--Linux Shell入門(二)- Shell 變數、字串與註釋
tags: [$_, $-, Linux, $(), Linux Shell, 字串, $(()), grep, Git, $*, typeset, $0, $#, Shell, $@, unset, $$, declare, readonly, $, $?]

---

<h1>Git筆記--Linux Shell入門(二)- Shell 變數、字串與註釋</h1>

<code>Linux Shell</code> 指令是使用 <code>Linux</code> 系統時與作業系統互動的基本方式，而在 <code>GitLab CI</code> 中，每個 <code>Job</code> 都會在 <code>runner</code> 的環境中執行，執行的語言就是 <code>Shell</code>（<code>Linux</code> 預設是 <code>bash</code> 或 <code>sh</code>）。所以，<code>.gitlab-ci.yml</code> 裡的 <code>script:</code> 欄位，其實就是寫給 <code>Shell</code> 執行的指令。

在 <code>Linux Shell</code>（以 <code>Bash</code> 為例）中，**變數（variable）** 是一種用來儲存資料的「容器」，可以用來保存字串、數字、陣列，甚至命令輸出結果，字串（Strings）則用來保存文字的內容，是 <code>Shell</code> 裡最常見的資料型態，例如檔案名、訊息、路徑等。


>[!Note]<code>Shell</code> 是甚麼?</code>
><code>Shell</code> 是一個「命令解釋器」，它讀取你寫的文字指令，然後告訴電腦該做什麼。在 <code>GitLab CI</code> 中，每個 <code>script:</code> 區塊裡的內容就是 <code>Shell</code> 指令。

---
變數的命名規則
---
1. 只能包含字母、數字和底線 <code>_</code>
```bash=
user_name="PU"   # ✅ 正確
user-name="PU"   # ❌ 錯誤，不能有 "-"
```
2. 必須以字母或底線開頭，不能以數字開頭
```bash=
var1="hello"     # ✅ 正確
name="PU"        # ✅ 正確
_name="test"     # ✅ 正確
1var="hello"     # ❌ 錯誤
```
3. 大小寫敏感
```bash=
VAR="UPPER"
var="lower"
echo $VAR       # 輸出 UPPER
echo $var       # 輸出 lower
```
4. 不能使用空格
```bash=
full_name="PU Lin"    # ✅ 正確，整個字串用引號包起來
name = "PU"           # ❌ 錯誤，等號左右不能有空格
full name="PU Lin"    # ❌ 錯誤，變數名稱不能有空格
```
5. 避免使用特殊符號
```bash=
my_var="value"        # ✅ 正確
my$var="value"        # ❌ 錯誤，$ 是特殊符號
```
6. 避免使用 <code>shell</code> 關鍵字
```bash=
count="10"            # ✅ 正確
if="yes"              # ❌ 錯誤，if 是保留關鍵字
```

---
字串的宣告
---
在 Linux Shell 中，我們會使用 雙引號<code>&quot; &quot;</code> 和 單引號<code>&#39; &#39;</code> 來宣告字串，兩種方式分別代表不同的意義。

- [<code>" "</code>(雙引號)](https://www.runoob.com/linux/linux-shell-variable.html#:~:text=%E9%80%80%E5%87%BA%E7%8A%B6%E6%80%81%E7%AD%89%E3%80%82-,Shell%20%E5%AD%97%E7%AC%A6%E4%B8%B2,-%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF): 雙引號的內部字元「大部分」不會進行解析，但是諸如: <code>&#36;變數</code>、<code>&#36;(command)</code>、<code>&#92;</code> 仍會被解析為 <code>shell</code> 指令參數。
  ```bash=
  echo "$HOME"
  # 輸出: /home/yourname
  # $HOME 被作為指令參數解析了，所以會輸出 Home 的路徑。
  ```
- [<code>' '</code>(單引號)](https://www.runoob.com/linux/linux-shell-variable.html#:~:text=%E9%80%80%E5%87%BA%E7%8A%B6%E6%80%81%E7%AD%89%E3%80%82-,Shell%20%E5%AD%97%E7%AC%A6%E4%B8%B2,-%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%AF): 單引號的內部字元完全不會進行解析，會被直接當作純文字。
  ```bash=
  echo '$HOME'
  # 輸出: $HOME
  ```

---
註釋的宣告
---
我們在使用 Linux Shell 時，可以將程式碼標註為註釋，被標註為註釋的段落不會執行，主要有兩種作法。
1. 使用 <code>&#35;</code> 來將段落標記為註釋，這是最簡單基本的做法
```bash=
# 這是標準的單行註釋
echo "Hello"  # 也可以放在指令後面
```
2. 使用「空指令」(no-op),搭配 Here Document 語法或字串，讓 shell 讀取但不執行內容,達到註釋效果。嚴格來說，這是一種利用語法特性的「技巧」，不是正式語法。
```bash=
# 方法 A: 使用 <<EOF
:<<EOF
這是多行註釋
可以寫很多行
EOF

# 方法 B: 使用 <<COMMENT
:<<COMMENT
這也是多行註釋
COMMENT 只是標記名稱,可以自訂
COMMENT

# 方法 C: 使用單引號
: '
這種寫法也可以
但不是最佳實踐
'
```



---
變數的基礎使用
---
<details><summary>1. 透過 <code>&#36;</code> 來傳遞變數的值</summary>
<div>

---
在 <code>Linux Shell</code> 中，我們可以透過 <code>&#36;</code> 來使用變數的值
```bash=
echo $name    # 印出 PU
echo $age     # 印出 25
```
在實務上建議在使用變數時，加上 <code>&#123;&#125;</code> 來將值框住來避免歧意，使其具有更高的安全性和可讀性。
```bash=
file="abc"
echo $file123    # shell 會以為你的變數叫 file123，而不是 file
echo ${file}123   # 明確：變數是 file，後面是字面文字 123
```

</div>
</details>
<details><summary>2. 使用 <code>&#36;&#40;command&#41;</code> 來由其他指令的結果來建立變數</summary>
<div>

---
我們可以透過利用 <code>&#36;&#40;command&#41;</code> 取得指令輸出，類似於數學中的括號，會先執行 <code>&#40;&#41;</code> 內的指令，然後才將結果存入變數。
```bash=
# 範例一: 直接取得日期
today=$(date)     # 把 date 指令放進變數
echo ${today}     # 輸出: Sun Dec  7 14:32:10 UTC 2025

# 範例二: 將 date 格式化輸出
current_date=$(date +%Y-%m-%d)
echo "今天是：$current_date"    # 2025-12-07
```
<code>&#36;&#40;command&#41;</code> 的運作本質上是使用 <code>&#40;&#41;</code> 開啟一個 [SubShell](#how_to_avoid_job_fail)，並將標準輸出回傳給主 <code>Shell</code>，類似於 <code>kotlin</code> 中的 <code>return</code>。


>[!Note] <code>&#43;</code> 是?
><code>&#43;</code> 是 <code>date</code> 格式化輸出的語法，想要格式化輸出日期，需要在 <code>date</code> 中加上 <code>&#43;</code> 才能啟動格式化輸出，這是 <code>date</code> 這個指令自己規定

</div>
</details>
<details><summary>3. 使用 <code>readonly</code> 建立唯讀變數</summary>
<div>

---
可以透過 <code>readonly</code> 關鍵字來創造唯讀變數，就像 <code>kotlin</code> 中的 <code>val</code> 一樣，當我們嘗試在該 <code>job</code> 中更改宣告為 <code>readonly</code> 的變數時，該 <code>Job</code> 會直接宣告失敗，進而造成整個 <code>pipeline</code> 中斷。
早期的 <code>readonly</code> 需要把 <code>readonly</code> 宣告和賦值分開做。
```bash=
url="https://google.com"
readonly url 
```
但其實在現代使用單行也是可以的
```bash=
readonly url="https://google.com" 
```

>[!Tip]<span id="how_to_avoid_job_fail">如何避免因變更唯讀變數而導致 job 失敗造成 Pipeline 中斷</span>?
><details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary>1. 使用 <code>SubShell</code> 語法</summary>
><div>
>
>---
>在[上面](#由其他指令的結果來建立變數)的時候，我們學到了使用 <code>&#36;&#40;command&#41;</code> 來由其他指令的結果來建立變數，其本質上是開啟一個「SubShell」來運行內部指令並傳出標準輸出；我們可以用 <code>&#40;&#41;</code> 來建立一個 <code>SubShell</code>，<code>SubShell</code> 有以下特點:
>- <code>SubShell</code> 可以理解成「一個小小的、獨立的 <code>Shell</code> 環境」
>- 變數、目錄切換、錯誤等不影響主 <code>Shell</code>
>- 錯誤也不會直接中斷主流程
>```bash=
>test_readonly_fail_Subshell_1:
>  stage: test
>  script:
>    - echo "測試 2-2-1 開始"
>    - echo "嘗試修改 readonly 變數(加上SubShell)"
>    - echo "宣告唯讀變數 myVal"
>    - readonly myVal="My Val"
>    - echo "嘗試修改 readonly"
>    - (myVal="You guessed wrong, JOJO — actually, I’m DIO!!") || echo "Nah, actually, I’m JOJO."
>    - echo "測試 2-2-1 完成"
>  allow_failure: true
>```
>
></div>
></details>
><details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary>2. 使用 <code>allow_failure: true</code></summary>
><div>
>
>---
>在 <code>GitLab</code> <code>CI</code>/<code>CD</code> 的 <code>.gitlab-ci.yml</code> 中，<code>allow_failure: true</code> 是一個屬性，用來控制 某個 <code>Job</code> 失敗時不會讓整個 <code>Pipeline</code> 中斷。它並不是 <code>Linux Shell</code> 本身的指令，而是 <code>CI</code>/<code>CD</code> 的設定，但它會影響你 <code>Shell</code> 指令在 <code>Pipeline</code> 中的容錯行為。
>- 雖然不會中斷 <code>Pipeline</code>，但是該 <code>Job</code> 仍會被標記為 「Failed」 且不會執行該 <code>Job</code> 中的後續命令，該 <code>Pipeline</code> 會被標記為 「Warning」
>```bash=
>stages:
>  - test
>    
>test_readonly_fail:
>  stage: test
>  script:
>    - echo "測試開始"
>    - echo "目標:嘗試修改 readonly 變數"
>    - echo "宣告唯讀變數 myVal"
>    - readonly myVal="My Val"
>    - echo "嘗試修改 readonly"
>    - myVal="You guessed wrong, JOJO — actually, I’m DIO!!"
>    - echo "測試完成"
>  allow_failure: true
># 結果: 雖然 pipeline 沒有中斷，但是該 Job 在執行到修改 myVal 時就失敗且停止了。
>```
>
></div>
></details>
>一般來說，要避免因 <code>Job</code> 失敗而崩潰還可以使用 <code>&#124;&#124;</code> 或 <code>&#124;&#124; true</code> 兩種方法，但如果是 <code>readonly</code> 的話，在嘗試修改唯讀變數的當下就會直接報錯並失敗，不會執行 <code>&#124;&#124;</code> 和 <code>&#124;&#124; true</code> 中的後續指令，因此這兩種方法無效。
>
>- <code>&#124;&#124;</code>: 如果左邊的指令 A 執行失敗，那麼就執行右邊的指令 B
>- [&#124;&#124; true](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/git_LinuxShell_9#%E5%B1%80%E9%83%A8%E7%95%B0%E5%B8%B8%E8%99%95%E7%90%86): 如果左邊的指令執行失敗，那就傳遞一個 true，使指令強制視為成功。


</div>
</details>
<details><summary>4. 使用 <code>&#36;&#40;&#40;&#41;&#41;</code> 來進行整數計算並賦值</summary>
<div>

---
<code>&#36;&#40;&#40;&#41;&#41;</code> 又稱為 「算術擴展（arithmetic expansion）」， 能執行內部數學運算，並把運算結果替換成字串輸出
- 完全在當前 <code>Shell</code> 內部計算，開銷極低
- 雖然看起來很像是 <code>&#36;&#40;command&#41;</code> 語法加上 <code>&#40;&#41;</code> <code>SubShell</code> 語法，但並不是，三者代表的涵義不同。
- 只支援整數運算，不支援小數
- <code>&#36;&#40;&#40;&#41;&#41;</code> 內的變數不需要加 <code>&#36;</code> 即可直接從外部引用。(要加 <code>&#36;</code> 也可以，但不必要)
- 支援標準的程式語言運算，如加減乘除、取餘數 (<code>&#37;</code>)、甚至位元運算 (<code>&lt;&lt;</code>, <code>&gt;&gt;</code>, <code>&amp;</code>, <code>&#124;</code>) 和三元運算符
- 會回傳一個結果值，可以用來賦值或顯示
```bash=
# 加法
echo $((5 + 3))    # 8

# 減法
echo $((10 - 4))   # 6
    
# 賦值
x=$(( 7 * 6 ))
echo $x    # 42
    
# 從外部引用變數，不需要加上"$"就可以直接引用
x=10
echo $(( x + 5 )) # 輸出: 15
```

</div>
</details>
<details><summary>5. 使用 <code>&#40;&#40;&#41;&#41;</code> 來進行整數計算，並將結果用於條件判斷或改變變數的數值</summary>
<div>

---
<code>&#40;&#40;&#41;&#41;</code> 又稱為 「算術評估（arithmetic evaluation）」，使用規定基本上和 <code>&#36;&#40;&#40;&#41;&#41;</code> 相同，使用相同的規定，但兩者在「回傳值」上有若干差異。
- <code>&#40;&#40;&#41;&#41;</code> 不回傳結果值，但會回傳退出狀態碼（exit status），類似於布林值，用來代表成功或失敗。
  - 若計算結果為 <code>true</code> 或非零的值，會回傳 <code>0</code>，<code>0</code> 在 <code>shell</code> 中帶表成功。
  - 若計算結果為 <code>false</code> 或等於零，會回傳 <code>1</code>，<code>1</code> 在 <code>shell</code> 中代表失敗。
- <code>&#40;&#40;&#41;&#41;</code> 適合用做條件判斷或是單純的改變變數的數值
- 雖然看起來和 <code>&#40;&#41;</code> <code>SubShell</code> 很像，但和 <code>SubShell</code> 不同，修改變數時，變數會真的在原 <code>Shell</code> 中被更新
```bash=
# 只執行運算，不回傳值
((a = 5 + 3))
echo $a  # 輸出：8

# 用於改變變數
a=10
((a++))  # 遞增 a
echo $a  # 輸出：11

# 將 (()) 用在做條件判斷上來回傳 true 或 false
read -p "請輸入分數：" score

if ((score >= 90)); then
    echo "優秀"
elif ((score >= 60)); then
    echo "及格"
else
    echo "不及格"
fi
```

</div>
</details>
<details><summary>6. 使用 <code>unset</code> 刪除變數</summary>
<div>

---
可以使用 <code>unset</code> 來「刪除變數」或「清除環境變數」，來刪除舊有的變數來確保之後不會誤用他。
- 會「移除變數」，不會移除數值，變數名稱會消失，但不是把值歸零，而是整個不再存在。
- 不能用來刪除 <code>readonly</code> 變數，試圖刪除會直接使 <code>Job</code> 被認定為失敗
- 也能刪除陣列元素
```bash=
# 基本語法
unset 變數名稱

# 範例
name="PU"
unset name    # 移除變數 name
echo $name    # 空，因為已被移除
```

</div>
</details>
<details><summary>7. 使用 <code>declare</code> 或 <code>typeset</code> 關鍵字來宣告變數的屬性</summary>
<div>

---
在 <code>Linux Shell</code> 中 <code>declare</code> / <code>typeset</code> 用於宣告和設定變數的屬性，它們就像是 <code>Kotlin</code> 中的 <code>val</code> 或 <code>var</code> 關鍵字，但功能更強大，可以設定變數的「類型」和「行為」。
- <code>declare</code> / <code>typeset</code> 是完全等價的，<code>declare</code> 是新式用法，<code>typeset</code> 是舊式用法。
- 能為變數加上特定的屬性，確保它在腳本中以特定的方式運作。
- 透過「旗標（Flags）」來設定屬性

<details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary>常用的 Flag 列表</summary>
<div>

---
| **旗標 (Flag)** | 原意                | **作用說明 (目的)**                                                                                                   | **類似 Kotlin 概念**                                 | **範例指令**                       |
| --------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------- |
| **`-r`**        | Read-only（唯讀）   | 宣告變數為唯讀變數                                                                                                    | 類似 `val`                                           | `declare -r PI=3.14`              |
| **`-x`**        | export（匯出）      | 宣告變數為環境變數                                                                                                    | 類似全域 `Environment Variable`                      | `declare -x DB_URL="localhost"`    |
| **`-i`**        | Integer（整數）     | 強制變數只能儲存整數值。當您試圖對其賦予非數字字串時，它會被評估為 0。                                                | 類似 `Int`                                           | `declare -i count=5`               |
| **`-a`**        | Array（陣列）       | 宣告變數為索引陣列，用數字當作索引（0, 1, 2...）                                                                      | 類似 <code>Array&lt;T&gt;</code>                     | `declare -a list=("a" "b" "c")`    |
| **`-A`**        | Associative（關聯） | 宣告變數為關聯陣列，用字串當索引的字典結構                                                                            | 類似 <code>Map&lt;String, T&gt;</code> 或 `HashMap`  | `declare -A map  map[name]="小明"` |
| **`-l`**        | Lowercase（小寫）   | 存入的內容自動轉為小寫                                                                                                | 類似 `String.lowercase()` / `setter` 內的轉換邏輯    | `declare -l NAME="Jojo"`           |
| **`-u`**        | Uppercase（大寫）   | 存入的內容自動轉為大寫                                                                                                | 類似 `String.uppercase()` / `setter` 內的轉換邏輯    | `declare -u NAME="dio"`            |
| **`-f`**        | Function（函數）    | 這個旗標是用於查詢的。它會列出所有已定義的 Shell 函數及其內容。                                                       | 類似 `Reflection` 查詢已定義函數                     | `declare -f`                       |
| **`-p`**        | Print（印出）       | 這個旗標是用於查詢的。顯示變數的所有屬性和當前值，它會以 `declare` 指令的形式，印出指定變數（或所有變數）的所有屬性。 | 類似 `Reflection` 或物件的 `toString()` 查詢變數狀態 | `declare -p count`                 |


</div>
</details>
    



>[!Note] <code>declare -r</code> 和 <code>readonly</code> 都是讓變數唯讀，兩者差在哪裡?
>- <code>declare</code> 就像是打開標籤選單，然後選一個貼上去，有很多種標籤可供選用， <code>readonly</code> 則是直接拿唯讀的標籤貼上去
>- <code>readonly</code> 無法用在宣告陣列屬性，也不能同時宣告其他屬性
>- <code>declare -r</code> 可以跟其他 <code>declare</code> 屬性一起用（例如 <code>-i</code>、<code>-a</code>）

</div>
</details>
<details><summary>8. 特殊變數</summary>
<div>

---
<code>Linux Shell</code> 在執行腳本時，會自動替你準備一些有著特定資訊的特殊變數，像是：腳本自己的名字、、呼叫時傳入的參數、目前有多少參數、上一個指令的成功或失敗...等等，就像是 <code>kotlin</code> 中的預設屬性，無需宣告即可直接使用。

<b><u>※ 位置參數 (Positional Parameters)</u></b>: 於存取使用者在執行腳本時傳入的參數
1. <code>$0</code>: 表示正在執行腳本本身的名稱或路徑。
  ```bash=
  echo "腳本名稱是：$0"   # 顯示腳本的檔名
  ```
    
2. <code>$1, $2...</code>: 依序表示使用者傳遞給腳本的第一個、第二個等個別參數。
  ```bash=
  # ./test.sh apple banana

  # 在 test.sh 裡
  echo $1   # apple
  echo $2   # banana
  ```

3. <code>$@</code>: 將所有參數以「參數原本的分組方式」逐一列出，能保留原始的參數分隔，例如: <code>"a" "b" "c"</code>
  ```bash=
  # 假設執行：./test.sh "hello world" "foo bar"
  echo "使用 \$@（分開）："
  for arg in "$@"; do
      echo "  參數：[$arg]"
  done
  # 輸出：
  # 參數：[hello world]
  # 參數：[foo bar]
  ```
4. <code>$*</code>: 將所有參數以「合併成一行」的方式列出，例如: <code>"a b c"</code>
  ```bash=
  # 假設執行：./test.sh "hello world" "foo bar"
  echo "使用 \$*（合併）："
  for arg in "$*"; do
      echo "  參數：[$arg]"
  done
  # 輸出：
  # 參數：[hello world foo bar]
  ```
<b><u>※ 環境狀態</u></b>: 這組變數用於存取 <code>Shell</code> 運行環境、上一個指令的狀態或腳本的執行資訊
1. <code>&#36;#</code>: 表示使用者傳遞給腳本的參數總數（不包含 <code>$0</code>）。
  ```bash=
  echo "參數總數：$#"
  ```
2. <code>&#36;?</code>: 判斷上個指令成功還是失敗，<code>0</code> 代表成功，非 <code>0</code> 則為失敗。[參見](https://hackmd.io/r7gGbtTCRCS1Dv9l9Wbp-A#%E5%9F%BA%E7%A4%8E%E9%98%B2%E7%A6%A6%E8%88%87%E7%8B%80%E6%85%8B%E6%84%9F%E7%9F%A5)。
  ```bash=
  echo $?   # 顯示失敗的狀態碼，例如 2
  ```
3. <code>&#36;&#36;</code>: 表示當前 <code>Shell</code> 或腳本的唯一行程 ID(PID)，類似於 <code>Shell</code> 的身分證。常用於建立臨時檔名以避免衝突
  ```bash=
  echo "當前 PID：$$"
  ```
4. <code>&#36;!</code>: 表示最後一個在背景執行的指令的行程 ID(PID)。
  ```bash=
  sleep 10 &
  echo $!   # 顯示剛剛那個 <code>sleep</code> 的 <code>PID</code>
  ```
5. <code>&#36;-</code>: 顯示當前 <code>Shell</code> 啟動時所使用的選項旗標(Flag)
  ```bash=
  echo $-
  ```
6. <code>&#36;_</code>: 表示上一個指令的最後一個參數/值
  ```bash=
  echo hello world
  echo $_       # world
  ```
    
    

</div>
</details>

---
字串的基本操作
---
<details><summary>1. 使用 <code>&quot; &quot;</code> 雙引號及 <code>&#36;</code> 進行字串拼接</summary>
<div>

---
<code>Linux shell</code> 的字串拼接不需要 <code>+</code> 號，只需使用 <code>&#36;</code> 呼叫變數並將字串並排、相鄰即可，但建議加上 <code>&#123;&#125;</code> 來對字串進行區隔。
- 需要在 <code>&quot; &quot;</code> 雙引號內執行，如果使用 <code>&#39; &#39;</code> 單引號，會被判讀為純文字。([參見](#字串的宣告))
- 可以使用 <code>&#43;&#61;</code> 運算符來進行連續拼接 
```bash=
a="Linux"
b="Shell"
c="指令"

all="${a}${b}${c}"
echo "$all"   # 輸出: LinuxShell指令
```
```bash=
# 連續拼接
message="Hello"     # Hellow    
message+=" "        # 一個空白
message+="World"    # World
echo $message  # 輸出: Hello World

```

</div>
</details>
<details><summary>2. 使用 <code>&#36;&#123;&#35;&#125;</code> 獲得字串長度</summary>
<div>

---
可以使用 <code>&#36;&#123;&#35;&#125;</code> 來獲得字串長度
- 空格算一個字元
- 多行字串會包含換行符號 <code>&#92;n</code>，也會被計算在長度內
- 計算的是字元數，不是 <code>byte</code>，若用多字節編碼（例如 <code>UTF-8 中文</code>），每個中文仍算作 1 個字元
```bash=
text="Hello World"
length=${#text}
echo $length  # 輸出: 11
    
# 多行字串
str="Line1
Line2"
echo ${#str}   # 11 (包含換行)
    
#  如何獲得一個空字串的長度
empty=""
length=${#empty}
echo $length  # 輸出: 0
```

</div>
</details>
<details><summary>3. 使用 <code>&#36;&#123;變數&#58;起始位置&#58;長度&#125;</code> 對字串進行切片</summary>
<div>

---
1. 基本語法: <code>&#36;&#123;變數&#58;起始位置&#58;長度&#125;</code>
  - 字串切片語法，起始值為 0。
  ```bash=
  text="Hello World"
  # 從位置 0 開始,取 5 個字元
  echo ${text:0:5}  # 輸出: Hello
  ```
2. 從指定位置開始切到結尾
  ```bash=
  text="Hello World"
  # 從位置 6 開始(W),一直取到最後
  echo ${text:6}  # 輸出: World
  ```
3. 從右邊開始計算(負數索引)
  ```bash 
  text="Hello World"
  # 注意: 負數前面要有空格,避免與其他語法混淆
  # -5 表示從右邊數來第 5 個字元開始
  echo ${text: -5}     # 輸出: World
  echo ${text: -5:3}   # 輸出: Wor
  ```
  - 當使用負數時，冒號前面必須有一個空格
4. 結合 <code>&#36;&#123;&#35;&#125;</code> 語法，自動計算長度，方便截取尾部或中段
  ```bash=
  str="Hello, PU"
  len=${#str}
  
  # 取倒數 2 個字元
  echo ${str:len-2}   # PU
  ```

</div>
</details>
<details><summary>4. 使用 <code>expr index</code> 查詢指定的字元在一個字串中最先出現的位置</summary>
<div>

---
- 一次只會查一個字元
- 一次可以有多個要查找的目標字元
  - 多個目標查找時，他會回目標中先出現的那一個
```bash=
text="Hello World"

position=$(expr index "$text" "ro") # 找 r 或 o 中任意一個
echo $position  # 'r'在第 9 個字元位置才出現，'o' 在位置 5 就先出現，，所以返回 5
# 輸出: 5
```

    
<details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary><code>expr index</code>的查找流程</summary>
<div>

---
```bash=
text="Hello World"
expr index "$text" "Wr"

掃描過程:
位置: 1  2  3  4  5  6  7  8  9  10 11
字元: H  e  l  l  o     W  o  r  l  d
      ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
找 W? ×  ×  ×  ×  ×  ×  ✓ ← 找到了!
找 r? ×  ×  ×  ×  ×  ×  ×  ×  ✓ ← 也找到了,但晚了

結果: 返回 7 (W 先出現)
```

</div>
</details>

</div>
</details>
<details><summary>5. 使用 grep 尋找符合特定模式的那一行</summary>
<div>

---
grep 的原意是 「Global Regular Expression Print」，意思是「在大量文字中，依規則找出符合的內容並印出來」，grep 指令除了能使用字串作為查找目標之外還能使用[正則表達式](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/kotlin_Regular_Expression)作為搜尋的目標樣本。
```bash=
# 基本語法: grep 關鍵字 檔案
# 在 log.exe 中搜尋 "ERROR" 字串
grep "ERROR" log.txt
```
    
<details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary>1. grep 預設會輸出整行內容,也就是包含你搜尋字串的那一整行。</summary>
<div>

---
```bash=
# 假設 log.txt 內容如下:
# 2024-01-10 INFO: System started
# 2024-01-10 ERROR: Connection failed
# 2024-01-10 INFO: Retry successful

grep "ERROR" log.txt
# 輸出: 2024-01-10 ERROR: Connection failed
    
# 也可以存成變數
errors=$(grep "ERROR" log.txt)
```

</div>
</details>
<details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary>2. grep 支援多種搜尋選項(Flag)，例如反向搜尋、顯示行號等等，各選項可混用且不分順序</summary>
<div>

---

| 選項                                            | 原意                                | 功用                                                                                                                        |
| ----------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| <code>-n</code>                                 | line-number                         | 標示行號，會在輸出中標示目標在第幾行                                                                                        |
| <code>-i</code>                                 | ignore-case                         | 不區分大小寫                                                                                                                |
| <code>-v</code>                                 | invert-match                        | 反向匹配，只顯示不包含指定字串的行                                                                                          |
| <code>-c</code>                                 | count                               | 回傳一個整數值，顯示至少包含一次指定字串的行數總和，同一行內出現多次也只會算成一次                                          |
| <code>-l</code>                                 | file-with-matches                   | 顯示有哪些檔案的檔案內容中具有指定字串                                                                                      |
| <code>-r</code>                                 | recursive                           | 使 grep 能夠搜尋資料夾下的子文件(遞迴搜尋)                                                                                  |
| <code>-A</code>/<code>-B</code>/<code>-C</code> |                                     | 顯示匹配的行以及該行 之後(After)/之前(Before)/上下文(Context) 相連的指定數量的行                                            |
| <code>-E</code>                                 | Extended Regular Expression         | 啟用[擴充型正則表達式(ERE)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/kotlin_Regular_Expression)                            |
| <code>-P</code>                                 | Perl Compatible Regular Expressions | 啟用 [Perl 相容型正則表達式(PCRE)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/kotlin_Regular_Expression)(部分系統可能不支援) |
| <code>-w</code>                                 | whole word                          | 只匹配「完整單字」，避免匹配到其他單字的一部分，例如只想找 `cat`，不要 `concatenate`                                        |
| <code>-m</code>                                 | max-count                           | 匹配次數限制，找到指定次數就停止搜尋                                                                                        |
| <code>-F</code>                                 | fixed string                        | 把文字模式當作「純文字」搜尋，不解析正則                                                                                    |
| <code>-x</code>                                 | exact match                         | 整行必須完全等於文字模式才匹配                                                                                              |
| <code>-s</code>                                 | silent / suppress errors            | 不輸出錯誤訊息，例如檔案不存在也不報錯，通常用在批次或自動化，避免錯誤干擾                                                  |
| <code>-q</code>                                 | quiet / suppress output             | 不輸出匹配內容，只回傳退出狀態（0 = 有匹配，1 = 無匹配）                                                                    |

---
    
1. <code>-n</code>: 顯示行號
  ```bash=
  grep -n "ERROR" log.txt
  # 輸出: 2:2024-01-10 ERROR: Connection failed
  ```
  2. <code>-l</code>: 顯示有哪些檔案的檔案內容中具有指定字串
  ```bash=
  # 假設有三個檔案
  # log1.txt 內容:
  2024-01-10 INFO: System started
  2024-01-10 ERROR: Connection failed
  # log2.txt 內容:
  2024-01-10 INFO: All systems normal
  # error_log.txt 內容:
  2024-01-10 ERROR: Database timeout
    
  grep -l "ERROR" *.txt
  # 輸出:
  # log1.txt
  # error_log.txt
  ```
  3. <code>-A</code>/<code>-B</code>/<code>-C</code>: 顯示匹配的行以及該行 之後(After) / 之前(Before) / 之前+之後(Context) 指定數量的行
  ```bash=
  # 假設 log.txt 內容:
  1: 2024-01-10 09:00:00 用戶登入
  2: 2024-01-10 09:00:05 開始連接資料庫
  3: 2024-01-10 09:00:10 ERROR: 連接逾時
  4: 2024-01-10 09:00:15 重試連接
  5: 2024-01-10 09:00:20 連接成功
    
  grep -A 2 "ERROR" log.txt
  # 輸出:
  # 2024-01-10 09:00:10 ERROR: 連接逾時  ← 符合的行
  # 2024-01-10 09:00:15 重試連接         ← 之後第1行
  # 2024-01-10 09:00:20 連接成功         ← 之後第2行
    
  grep -B 2 "ERROR" log.txt
  # 輸出:
  # 2024-01-10 09:00:00 用戶登入         ← 之前第2行
  # 2024-01-10 09:00:05 開始連接資料庫   ← 之前第1行
  # 2024-01-10 09:00:10 ERROR: 連接逾時  ← 符合的行

  grep -C 2 "ERROR" log.txt
  # 輸出:
  # 2024-01-10 09:00:00 用戶登入         ← 之前第2行
  # 2024-01-10 09:00:05 開始連接資料庫   ← 之前第1行
  # 2024-01-10 09:00:10 ERROR: 連接逾時  ← 符合的行
  # 2024-01-10 09:00:15 重試連接         ← 之後第1行
  # 2024-01-10 09:00:20 連接成功         ← 之後第2行
  ```
4. <code>-m</code>: 匹配次數限制，找到指定次數就停止搜尋
  ```bash=
  # 找到前 2 次匹配就停止搜尋
  grep -m 2 "error" app.log
  ```
5. <code>-F</code>: 把文字模式當作「純文字」搜尋，不解析正則
  ```bash=
  # 直接找字串 a+b*c
  grep -F "a+b*c" file.txt
  ```
6. <code>-x</code>: 整行必須完全等於文字模式才匹配
  ```bash=
  # 只有完全等於 SUCCESS 的行會被匹配
  grep -x "SUCCESS" result.txt
  ```
    
    
    
</div>
</details>



</div>
</details>





---
參考資料
---
- [Linux grep 命令](https://www.runoob.com/linux/linux-comm-grep.html)
- [grep簡介](https://ithelp.ithome.com.tw/m/articles/10265120)
- [Linux 匹配文字 grep 指令用法教學與範例](https://blog.gtwang.org/linux/linux-grep-command-tutorial-examples/)