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 $*和$@的區別