--- title: '建構腳本 - 基礎引用變量、管道、數學運算' disqus: kyleAlien --- 建構腳本 - 基礎引用變量、管道、數學運算 === ## Overview of Content 如有引用參考請詳註出處,感謝 :smile_cat: :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**探索腳本與命令:Shell 腳本的必備相關知識 | 腳本的數學運算**](https://devtechascendancy.com/shell-scripting-essential-knowledge/) ::: [TOC] ## 腳本 & 命令:命令集、多命令 使用多個不同命令時,只需在每個命令之間使用 `;` 隔開即可 ```shell= who ; date ``` >  ### 多命令:複合執行 `&&`、`||` * 一般來說我們在使用多命令時會使用 `;` 符號,稍加詳細地說這個符號會執行多個命令,而不判斷其中哪個命令是否成功 * **複合執行** * `&&` 符號: 如果要讓命令依照順序執行,並且判斷其中是否有錯誤,可以使用 `&&` 符號;從以下範例就可以清楚地看出其差異 ```shell= # 有錯誤仍往下執行 echo "Start" ; nocmd ; echo "End" # 有錯誤就不往下執行 echo "Start" && nocmd && echo "End" ``` >  * `||` 符號: 如果想要偵測前一步指令異常後,再繼續執行下一步指令,那就可以使用 `||` 符號;這個符號會感測第一個指令的結果非 `0` 時,才繼續往下執行 ```shell= # 感測 nocmd 是否執行成功 nocmd || echo "Pre cmd Failure" # 增加把錯誤丟掉 /dev/null nocmd 2> /dev/null || echo "Pre cmd Failure" # 如果 `echo "Success cmd"` 成功,Pre cmd 就不執行 echo "Success cmd" || echo "Pre cmd Failure" ``` >  ### 創建 shell 腳本:命令集 :::info * **腳本 & 命令** 腳本就是透過多個命令組成(所以上面這個多命令也是一個簡單的腳本) > 命令一次不要下達超過 255 個 ::: * shell 腳本其實就是將指令寫入某個文件中;首先 ^1.^ 需要指定該腳本文件由哪個 Shell 分析 (`#!<Shell 路徑>`)、^2.^ 寫入需要的指令 > 指令可以使用換行分開、也可以使用 `;` 分開 ```shell= #!/bin/bash # first bash script date who ``` >  :::warning * **腳本不可運行**? 預設創建出來的文件會因為 umask(002),將文件權限設定為 664,也就是預設該文件是不可執行,這時 **只需要透過 `chmod` 改變即可** ```shell= chmod u+x firstShell.sh ``` * **想讓腳本在任何目錄中都可以被呼叫到**? 1. 將腳本目錄加入 PATH 2. 使用絕對路徑去呼叫腳本 ::: * 腳本中如果要輸出訊息給使用者看,可以使用 `echo` 命令 ```shell= #!/bin/bash # first bash script date who ## -n 代表不換行 echo -n "Hello script~" echo "Yo" ``` * 運行腳本 ```shell= ./firstScript.sh ``` >  ## 腳本內使用變量 `$` * 要在腳本中使用變量有兩種,^1.^ **環境變數變量**(`$<環境變量名>`)、^2.^ **腳本臨時變量**(也稱為用戶變量) > 變量有分大小寫 * **引用變量時都是使用 `$` 符號**,如果不使用這個符號,那變量就只是普通的字串,而不是真正的數值(透過 `$` 符號才可引出數值) ### 環境變量 * **腳本中使用環境變量** :::info * 查詢環境變量,可以使用 `set`、`printenv`、`env` ... 等等命令 ::: ```shell= #!/bin/bash # use variable in script # echo 雙引號中使用 $ 符號就可以引用變數 # # 如果你要使用 $ 符號,請配合跳脫符號 `\` echo "User: $USER" echo "Uid: $UID" echo "Hime: $HOME" ``` * 運行腳本 ```shell= chmod u+x ./variable.sh ``` >  ### 腳本臨時變量 * **腳本臨時變量**:脫離腳本後就不復存在 ```shell= #!/bin/bash # local variable for script # 臨時變量 hello=1111 world=2222 echo "hello=$hello, world=$world" ``` * 運行腳本 ```shell= chmod u+x ./local_variable.sh ``` >  ## 腳本內執行命令 Shell 腳本中可以從命令 A 的輸出提取訊息,並賦予變量,這種腳本內執行命令的方式也稱之為「命令替換」 :::danger * **命令替換符號中的命令,會由 sub shell 執行?** 這不一定,是否由 **sub shell 執行這取決於你使用的命令**,如果是內建命令則不啟用 sub shell 執行,外部命令則啟用 sub shell 執行 > 可使用 `type` 命令來分別內建、外部命令 ```shell= # cd 就是內建命令 type cd ``` >  ::: ### 反引號 & 引用格式 * **以下有兩種方法可以把可以將命令輸出給變量** 1. **反引號** \`<命令>\`:**bash 會執行反引號內的命令**(這種使用方式較為傳統) 2. **引用格式** $(<命令>):**bash 會執行 $() 內的命令** ```shell= #!/bin/bash # catch the command output # 反引號 catch_1=`date` echo "catch the output by \` \`: $catch_1 " # 引用格式 catch_2=$(date) echo "catch the output by \$( ): $catch_2 " ``` * 執行命令 ```shell= chmod u+x catch_cmd.sh ./catch_cmd.sh ``` >  ## 退出腳本 退出腳本對於 Shell 來說很重要,Shell 的判斷式就是判斷 Shell 退出時的退出碼來決定該命令是否執行成功! > **每個指令都會執行過後都會返回 ++退出狀態碼(exist status)++**,來告知該腳本執行的結果 :::info * 狀態碼範圍為 0 ~ 255 的整數 ::: ### 查看退出碼:`$?` * Linux 提供一個特殊符號來 **`$?` 來保存上一個命令的退出狀態碼** (只會保留上一次的結果),其狀態碼也代表了不同意義 1. 成功:退出碼為 0 ```shell= date && echo "\$? $?" ``` >  從上面實驗我們可以知道指令執行成功後返回的結果是 0,也就是 **數字 0 代表命令執行成功** 2. 失敗:退出碼非 0 都是失敗,並沒有特殊規則可循 ```shell= # 無用命令 abc echo "\$? $?" ``` >  :::success * 失敗退出碼雖然沒有規則但有 **慣例**,我們可以按照以下慣例去判斷(但並非決定) | 退出狀態碼 | 慣例意義 | | - | - | | 0 | 成功 | | 1 | 一般未知錯誤 | | 2 | 不合適的 shell 命令 | | 126 | 該命令不可執行 | | 127 | 找不定該命令 | | 128 | 無效的退出參數 | | 128+x | 與 Linux 信號 x 相關的嚴重錯誤 | | 130 | 透過 Ctrl+C 終止命令 | | 255 | 正常範圍之外的退出狀態碼 | > 以下分別執行了錯誤指令(127) & 不可執行的命令(126) > >  ::: ### 退出腳本:exit 命令 * 我們在退出腳本時可以使用 `exit` 命令,該指令可以指定退出碼 > 格式:exit <退出碼> ```shell= #!/bin/bash var1=10 var2=20 result=$(bc<< EOF a=$var1 * $var2 b=$var2 / $var1 a+b EOF ) echo "result=($result)" exit 27 ``` >  :::warning * 當給予退出碼大於 255 則會取 256 餘數 > 假設給 exit 555,那最終我們讀取退出碼時就會是 43 (555 % 256 的結果) ::: ## 重定向:輸入/輸出 * 如果要將命令的結果輸出到另一個位置(有可能是另一個命令中、或是輸出到檔案),那就需要重定向 | 重定向類型 | 符號 | 說明 | | - | - | - | | 輸出重定向(覆蓋)| > | 將命令結果輸出到另一個位置;如果輸出到文件,則會覆蓋文件內容 | | 輸出重定向(追加) | >> | 同上,不過不會覆蓋文件內容,而是追加文件內容 | | 輸入重定向 | < | 將文件內容輸入到命令中 | | 輸入重定向(條件型) | << | 可一直輸入內容直到指定符號,才完整輸入到命令中 | ### 輸出重定向 * **輸出重定向**: 以下範例示範輸出的重定向,使用重定向的追加符號(`>>`)、覆蓋符號(`>`) ```shell= # 輸出命令 (覆蓋) ./catch_cmd.sh > redirection_output_1.txt cat redirection_output_1.txt # 追加輸出 ./catch_cmd.sh >> redirection_output_1.txt cat redirection_output_1.txt ``` >  ### 輸入重定向 * **輸入重定向**:使用 `wc` 指令測試輸入重定向 > `wc` 命令是 words counts,用來判斷文件字數 > > wc 輸出格式「文本行數 詞數 字節數」 以下範例將 `redirection_output_1.txt` 檔案輸入到 `wc` 指令中 ```shell= # 將文本內容輸入給 wc 命令 wc < redirection_output_1.txt ``` >  * **條件型** 輸入重定向:(`<<` 符號) 另外一個有趣的用法是作為「**輸入的結尾條件**」,當判斷到輸入的內容為指定內容,那就停止輸入,範例如下… ```c= # 你可以一直輸入文字,直到輸入 'YoYo' 為止 # 才會將文字匯入 wc 命令運行 wc << YoYo ``` >  ## 管道 Pipe 如果要將某個命令作為下一個命令的輸入,這時就可以使用管道技巧;管道格式如下 ```shell= # 管道用法 <命令 A> | <需要 A 命令結果的命令> ``` ### 管道的使用範例 * Unix 管道的使用範例 ```shell= # 首先列出所有已安裝的軟體 # 將安裝軟體的結果輸出給 sort 使用(sort <上一個結果>) # 最後將 sort 結果在輸出給 head (head <上一個結果>) apt list --installed | sort | head ``` >  :::info * 命令的執行順序? **兩個命令會同時執行**,由系統內部將命令進行串接(就像是物件導向的 Link 鏈式設計) ::: ## 數學運算 對於任何程式語言來說操作數字都是基礎能力,對於 Shell 當然也是,不過 **Shell 對於數字的處理較為麻煩** ### Bourne shell - `expr` 命令 * Bourne shell 提供了一個特別的命令 `expr` 來處理數學表達式,其可以處理的內容如下表(提及幾個常用的) | 操作符 | 描述 | 成立後返回 | 不成立返回 | | - | - | - | - | | `ARG1 \| ARG2` | ARG1 非 0 也不是 NULL 就返回 ARG1,否則返回 ARG2 | ARG1 | ARG2 | | `ARG1 & ARG2` | ARG1 和 ARG2 都非 0 也不是 NULL,則返回 ARG1 | ARG1 | 0 | | `ARG1 < ARG2` | 小於 | 1 | 0 | | `ARG1 <= ARG2` | 小於等於 | 1 | 0 | | `ARG1 = ARG2` | 等於 | 1 | 0 | | `ARG1 != ARG2` | 不等於 | 1 | 0 | | `ARG1 >= ARG2` | 大於等於 | 1 | 0 | | `ARG1 > ARG2` | 大於 | 1 | 0 | | `ARG1 + ARG2` | 加法 | ARG1+ARG2 | - | | `ARG1 - ARG2` | 減法 | ARG1+ARG2 | - | | `ARG1 * ARG2` | 乘法 | ARG1+ARG2 | - | | `ARG1 / ARG2` | 除法 | ARG1+ARG2 | - | | `ARG1 % ARG2` | 取模 | ARG1%ARG2 | - | :::danger * **expr 符號的衝突** expr 符號很多會與原生 Shell 使用的符號衝突,當符號衝突時可以使用跳脫符號來避免(`\`) ```shell= # Error `*` 符號衝突 expr 5 * 2 # 使用跳脫字元 expr 5 \* 2 ``` >  ::: * 以下範例 **使用 `expr` 命令來運算數學** ```shell= #!/bin/bash # test expr command var1=10 var2=20 # 記得命令替換是使用 `$()` 符號 # 內部執行 expr 命令時仍須關心是否有符號衝突的問題 result=$(expr $var1 \* $var2) echo "$var1 * $var2 = $result" ``` * 執行命令 ```shell= chmod u+x expr_test.sh ./expr_test.sh ``` >  ### Bash shell - `$[ ]` 符號 * Bash shell 運算中有一種更方便的方式: 使用 `$[ ]` 符號,使用這個符號就不用擔心符號衝突的問題 ```shell= #!/bin/bash # test $[] command var1=10 var2=20 result=$[$var1 * $var2] echo "$var1 * $var2 = $result" ``` * 執行命令 ```shell= chmod u+x expr_test2.sh ./expr_test2.sh ``` >  :::warning * **`$[ ]` 符號只能計算整數**,**不能計算浮點數** >  ::: ### 浮點數計算:bc 命令 * 透過 `bc` 命令就可以計算浮點數,首先 `bc` 命令的基礎用法如下 (不用腳本的狀況) > 輸入 `quit` 就可以退出計算 > >  `bc` 命令還可以儲存暫時變量 >  :::info * **bc 內建變量 `scale` 的設置** 透過設定 scale 就可以控制計算結果輸出的小數點位數 ::: * 在腳本中使用 `bc` 命令,以下有兩種方式 1. **基本使用** ```shell= #!/bin/bash var1=100.1 var2=222.3 # scale 設定最多輸出 4 個小數點 result=$(echo "scale=4; $var1 * $var2" | bc) echo "result=($result)" ``` * 運行腳本 ```shell= chmod u+x float_cal.sh ./float_cal.sh ``` >  2. **配合重指向**:如果要較長的計算,那就可以使用「**輸入重指向**」 ```shell= #!/bin/bash var1=1.12 var2=3.1415926 var3=102 result=$( bc << END scale=5 a1=($var1 * $var2) a2=(($var2 + $var1) * $var3) a1 + b2 END ) echo "final result=($result)" ``` :::danger * 重新導向的內部局部變數是不可以使用的,否則會出錯(以上面範例來說 `a1`, `a2` 就不能拿來計算) ::: >  ## Appendix & FAQ :::info ::: ###### tags: `Linux Shell`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up