簡單的 Shell 腳本入門教程


Shell腳本 運作方式與解釋型語言相當,如果有語言基礎,學起 Shell 腳本就非常容易,但是 Shell 與常見的語言不同,一些常見的函數在 Shell 中需要組合一些命令得以實現

工具推薦

Shell 似乎沒有定制的 IDE,這里推薦 VS Code 搭配對應的插件:

  1. shellman 智能提示和自動補全,在插件頁面有介紹常用代碼片段的觸發關鍵詞,作者在 Shellman reborn 中寫到了 Shellman 誕生的故事,挺有趣的
  2. shellcheck 語法靜態檢查工具,插件安裝后需要本地安裝 shellcheck,參考 shellcheck Installing,Mac OS 可以使用 brew install shellcheck,這樣在寫 Shell 的時候,語法有誤的地方就會以波浪線的方式提示
  3. shell-format 代碼整理,Win 快捷鍵:Alt + Shift + F,Mac OS 快捷鍵:option + shift + F
  4. 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 下運行,有這么幾種方式:

  1. 先給腳本添加執行權限:chmod +x test.sh,然后運行腳本:./test.sh,這種方式執行會讀取 Shebang,用指定的解釋器執行腳本
  2. sh test.sh,使用 sh 這個解釋器執行腳本,當然也可以用其他執行,比如:bash test.sh。與第一種方式相同,當前的 shell 是父進程,生成一個子 shell 進程(子進程會繼承父進程的環境變量),在子 shell 中執行腳本,腳本執行完畢,退出子 shell 回到當前 shell
  3. source 點命令方式:source test.sh 等效於 . test.sh。source 讓腳本在當前 shell 執行,不生成新的子進程。使用 source 執行腳本,腳本中對於環境變量的修改會作用於當前 shell,這就是為什么我們在修改了一些配置如:~/.bashrc,執行 source ~/.bashrc 后配置就生效了
  4. 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 原生不支持數學運算,可以使用 awkexpr

注意乘號需要加上轉義:\*,而且運算符兩側必須空格

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

其他常用判斷:

  1. 直接在 [ ] 中放字符串變量 如 [ ${str} ] 則就是判斷 str 這個字符串是否非空
  2. -f 判斷是否為普通文件,如:[ -f $file ]
  3. -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

判斷語句

使用 iffi 定義判斷的邊界,使用 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 命令運行時都會打開三個文件:

  1. 標准輸入 (stdin):stdin 的文件描述符為 0,Unix 程序默認從 stdin 讀取數據
  2. 標准輸出 (stdout):stdout 的文件描述符為 1,Unix 程序默認向 stdout 輸出數據
  3. 標准錯誤輸出 (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,可以這樣寫:

坑梳理

  1. 變量賦值時,變量名命名規則和其他語言類似,注意變量賦值時 = 兩邊不能有空格
  2. 數組 unset 元素,並不是真正的移除元素
  3. 獲取參數時,當 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 $*和$@的區別


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM