簡單的 Shell 腳本入門教程
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傳遞給腳本或函數的參數$?上個命令的退出狀態$*和$@傳遞給腳本或函數的所有參數$nn 代表 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 $*和$@的區別


