<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/)