Shell指令碼 運作方式與解釋型語言相當,如果有語言基礎,學起 Shell 指令碼就非常容易,但是 Shell 與常見的語言不同,一些常見的函式在 Shell 中需要組合一些命令得以實現
工具推薦
Shell 似乎沒有定製的 IDE,這裡推薦 VS Code 搭配對應的外掛:
- shellman 智慧提示和自動補全,在外掛頁面有介紹常用程式碼片段的觸發關鍵詞,作者在 Shellman reborn 中寫到了 Shellman 誕生的故事,挺有趣的
- shellcheck 語法靜態檢查工具,外掛安裝後需要本地安裝 shellcheck,參考 shellcheck Installing,Mac OS 可以使用
brew install shellcheck
,這樣在寫 Shell 的時候,語法有誤的地方就會以波浪線的方式提示 - shell-format 程式碼整理,Win 快捷鍵:Alt + Shift + F,Mac OS 快捷鍵:option + shift + F
- Code Runner 指令碼執行,右鍵
Run Code
,Win 快捷鍵:Ctrl + Alt + N,Mac OS 快捷鍵:control + option + N
執行 shell 指令碼
新建指令碼:test.sh
#!/usr/bin/env bash
# 使用echo 列印字串或者變數
echo 'hello world'
可以用 Code Runner 執行,就會輸出:hello world
在 Shell指令碼 的第一行一般會寫 #!/bin/bash
這個是 Shebang,#!
後面是直譯器的絕對路徑,指令碼將用該直譯器執行。還有一種寫法是:#!/usr/bin/env bash
,/usr/bin/env
是 env 命令的絕對路徑,而 env 命令用於顯示系統中已存在的環境變數,其中包含了 $PATH
,會在 $PATH
包含的目錄依次找 bash
,常見的命令列直譯器有:sh ,bash ,zsh(Mac OS 預設直譯器)
如果在 Linux 或 類Unix 下執行,有這麼幾種方式:
- 先給指令碼新增執行許可權:
chmod +x test.sh
,然後執行指令碼:./test.sh
,這種方式執行會讀取 Shebang,用指定的直譯器執行指令碼 sh test.sh
,使用 sh 這個直譯器執行指令碼,當然也可以用其他執行,比如:bash test.sh
。與第一種方式相同,當前的 shell 是父程式,生成一個子 shell 程式(子程式會繼承父程式的環境變數),在子 shell 中執行指令碼,指令碼執行完畢,退出子 shell 回到當前 shell- source 點命令方式:
source test.sh
等效於. test.sh
。source 讓指令碼在當前 shell 執行,不生成新的子程式。使用 source 執行指令碼,指令碼中對於環境變數的修改會作用於當前 shell,這就是為什麼我們在修改了一些配置如:~/.bashrc
,執行source ~/.bashrc
後配置就生效了 - exec 方式:有需要先給指令碼新增執行許可權:
chmod +x test.sh
,執行exec ./test.sh
,也是讓指令碼在同一個程式上執行不生成新的子程式,與 source 的區別就是,在指令碼執行完成後程式會被結束
基礎命令
可以按照 [Bash Shell] Shell學習筆記 學習,這篇文章講的非常詳細,本篇部落格也是在學習這篇文章後寫下的
獲取輸入
使用 read
命令,從標準輸入流 (stdin) 獲取輸入
#!/usr/bin/env bash
read var
echo "${var}"
執行指令碼,輸入任意字元,回車確認,輸入的值會賦值給變數 var
,並列印出該變數
輸出
#!/usr/bin/env bash
var=1
# 輸出變數
echo ${var}
# 輸出字串 顯示部分字元需要轉義
echo "\"hello world\"" # "hello world"
# 換行使用 -e 引數:使轉義字元生效
# 使用 \n 換行
echo -e "newline\n"
也可以讓 shell 輸出不同顏色的字元,可以參考:shell指令碼中echo顯示內容帶顏色
#!/usr/bin/env bash
echo -e "\033[30m 黑色字 \033[0m"
echo -e "\033[31m 紅色字 \033[0m"
echo -e "\033[32m 綠色字 \033[0m"
echo -e "\033[33m 黃色字 \033[0m"
echo -e "\033[34m 藍色字 \033[0m"
echo -e "\033[35m 紫色字 \033[0m"
echo -e "\033[36m 天藍字 \033[0m"
echo -e "\033[37m 白色字 \033[0m"
變數使用
# = 兩邊不能有空格
var="hello world"
num=100
# 在引用變數時,這種方式可以,但是推薦下面一種
echo $var
# 推薦在使用字串變數時,在兩側加上雙引號,否則如果變數字串中存在空格,則字串會被切分
echo "$var"
# 如果涉及字串拼接,可以在變數名兩側加上花括號
echo "變數為: ${var}."
# 將變數設定為只讀,再次修改會報錯
readonly var
# var="wolrd"
# 刪除變數,不能刪除 readonly 修飾的變數
unset num
變數賦值時,變數名命名規則和其他語言類似,注意變數賦值時 =
兩邊不能有空格
使用時在變數名前加上 $
,推薦所有的變數都使用 ${}
的方式使用變數
運算
算術運算:Bash 原生不支援數學運算,可以使用 awk
和 expr
注意乘號需要加上轉義:\*
,而且運算子兩側必須空格
a=10
b=3
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a \* $b`
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
執行命令
$()與 ``(反引號)都可以用於執行命令,並會將執行的結果返回,shellcheck 推薦使用第一種 $() 的方式
#!/usr/bin/env bash
result=`date "+%Y-%m-%d"`
echo "${result}"
result=$(date "+%Y-%m-%d")
echo "${result}"
運算子
關係運算子只支援數字,如果字串為數字也可以,關係運算子包括:
運算子 | 含義 |
---|---|
-eq | 等於 |
-ne | 不等於 |
-gt | 大於 |
-lt | 小於 |
-ge | 大等於 |
-le | 小等於 |
條件表示式必須放在 []
中,並且 [
的右側,和 ]
的左側必須留有空格
布林運算子列表:
運算子 | 含義 |
---|---|
! | 非 |
-o | 或 (or) |
-a | 與 (and) |
#!/usr/bin/env bash
a="10"
b="3"
c=1
if [ ${a} -ne ${b} ]
then
echo "相同"
else
echo "不相同"
fi
if [ ${a} -gt ${b} -a ${b} -gt ${c} ]
then
echo "a > b & b > c"
fi
其他常用判斷:
- 直接在
[ ]
中放字串變數 如[ ${str} ]
則就是判斷str
這個字串是否非空 - -f 判斷是否為普通檔案,如:
[ -f $file ]
- -d 判斷是否為資料夾,如:
[ -d $file ]
字串擷取
字元擷取的格式:${string: start :length}
索引從 0 開始,可以省略 :length
這樣就擷取到最後,注意空格要空在 :
後,否則可能提示:bad substitution
#!/usr/bin/env bash
string="hello world"
echo ${string: 1 : 3} # ell
# 擷取到最後
echo ${string:1} # ello world
陣列
#!/usr/bin/env bash
# 1. 定義陣列:使用括號宣告,用“空格”分隔開,也可以換行隔開
arr=(1 2 3)
strArr=(
"first"
"second"
)
# 2. 讀取陣列:通過下標讀取,下標從 0 開始計算
echo "${arr[0]}"
# 使用 * 或者 @ 讀取所有元素
echo ${arr[*]}
echo ${arr[@]}
# 讀取陣列長度 讀取全部元素前面加上 #
echo ${#arr[*]}
echo ${#arr[@]}
# 遍歷下標
for(( i=0;i<${#strArr[@]};i++))
do
echo ${strArr[i]};
done;
# for in 遍歷元素
for element in ${strArr[*]}
do
echo $element
done
# 3. 修改陣列元素
strArr[0]="modify"
echo ${strArr[0]}
# 4. 刪除元素
unset arr[1]
echo ${#arr[*]}
echo ${arr[*]} # 1 3
# !使用 unset 要注意,這其實並不是真正刪除了該元素,而只是將該元素置空,所以使用下標遍歷會出問題,如下
echo "陣列遍歷:"
for(( i=0;i<${#arr[@]};i++))
do
echo "index ${i} -> ${arr[i]}";
done;
# index 0 -> 1
# index 1 ->
# 解決 unset 無法真正刪除的方法:重新賦值給新的陣列
echo "陣列遍歷:"
arr=( "${arr[@]}" )
for(( i=0;i<${#arr[@]};i++))
do
echo "index ${i} -> ${arr[i]}";
done;
# index 0 -> 1
# index 1 -> 3
判斷語句
使用 if
和 fi
定義判斷的邊界,使用 then
, elif
, else
定義條件
#!/usr/bin/env bash
#!/usr/bin/env bash
a=10
b=20
if [ $a == $b ]
then
echo "相等"
else
echo "不相等"
fi
if [ $a == $b ]
then
echo "相等"
elif [ $a -lt $b ]
then
echo "a 小於 b"
else
echo "其他情況"
fi
函式
呼叫函式時,我們可以傳入引數,可以通過 $n
來獲取引數,這裡的 n
表示 需要取的引數的索引,當n>=10時,需要使用${n}來獲取引數
$#
傳遞給函式的引數個數,$*
和 $@
顯示所有傳遞給函式的引數,$?
表示函式的返回值,也可以用於獲取上一個命令的退出狀態,執行成功會返回 0,失敗返回 1
# 定義函式
#!/usr/bin/env bash
funWithParam(){
echo "引數個數:$#" # 引數個數:11
echo "傳遞給函式的所有引數:$*" # 傳遞給函式的所有引數:1 2 3 4 5 6 7 8 9 34 73
echo "$1" # 1
# 超過 9 的引數需要用 ${} 接收引數,否則直接顯示數值
echo "$10" # 10
echo "${11}" # 73
}
# 呼叫函式:函式名後面直接跟上引數
funWithParam 1 2 3 4 5 6 7 8 9 34 73
echo "$?" # 0
輸入輸出重定向
使用 >
將應該輸出到終端上的資料重定向輸出到檔案,>
預設為覆蓋檔案,使用 >>
追加寫入檔案
使用 <
將預設從鍵盤輸入的資料,定向為從檔案輸入
# who 命令用於顯示系統中有哪些使用者正在上面
# 將結果輸入 who.txt
who > who.txt
# wc -l 作用是計算文字行數
wc -l < who.txt
一般情況下,每個 Unix/Linux 命令執行時都會開啟三個檔案:
- 標準輸入 (stdin):stdin 的檔案描述符為 0,Unix 程式預設從 stdin 讀取資料
- 標準輸出 (stdout):stdout 的檔案描述符為 1,Unix 程式預設向 stdout 輸出資料
- 標準錯誤輸出 (stderr):stderr 的檔案描述符為 2,Unix 程式會向 stderr 流中寫入錯誤資訊
所以一般我們後臺啟動應用並且輸出日誌檔案都使用:
nohup java -jar xxx.jar >> nohup.log 2>&1 &
nohup
:(no hang up) 保證在退出帳戶或者關閉終端之後繼續執行相應的程式
>> nohup.log
:將 java -jar xxx.jar
的輸出追加到 nohup.log
檔案
2>&1
:將 java -jar xxx.jar
的 標準錯誤輸出 也重定向到 標準輸入
&
:讓程式在後臺執行
預設情況下,command > file 將 stdout 重定向到 file,command < file 將stdin 重定向到 file。
如果希望 stderr 重定向到 file,可以這樣寫:
坑梳理
- 變數賦值時,變數名命名規則和其他語言類似,注意變數賦值時
=
兩邊不能有空格 - 陣列 unset 元素,並不是真正的移除元素
- 獲取引數時,當 n>=10 時,需要使用${n}來獲取引數
常見的特殊 Shell 環境變數
$$
表示當前Shell程式的ID,即pid$0
表示當前指令碼的絕對路徑$#
傳遞給指令碼或函式的引數個數$n
傳遞給指令碼或函式的引數$?
上個命令的退出狀態$*
和$@
傳遞給指令碼或函式的所有引數$n
n 代表 1~9 其中任意一個數字,傳遞給指令碼或函式該位置的引數
$*
和 $@
區別:
#!/usr/bin/env bash
function asterisk () {
echo "\"\$*\""
for var in "$*"
do
echo "$var"
done
}
function mail () {
echo "\"\$@\""
for var in "$@"
do
echo "$var"
done
}
asterisk a b c
mail a b c
輸出
"$*"
a b c
"$@"
a
b
c
當 $*
和 $@
直接使用效果相同,都是接收一份資料如上所示的例子,接收到的就是:a b c
,一份資料,以空格隔開。加了雙引號後 "$@"
會將每個引數都當成一份獨立的資料
參考資料
VS code 打造 shell指令碼 IDE
#!/bin/bash 和 #!/usr/bin/env bash 的區別
Shell指令碼 - wiki
Linux跑指令碼用sh和./有什麼區別?
執行shell指令碼三種方法的區別:(sh、exec、source)
Shell特殊變數:Shell $0, $#, $*, $@, $?, $$和命令列引數
exec 跟 source 差在哪?
bash - 如何刪除陣列中的元素,然後在 Shell 指令碼中移動陣列?
nohup /dev/null 2>&1 含義詳解
Linux—shell中$(( ))、$( )、``與${ }的區別
Shell $*和$@的區別