Shell 腳本學習

配圖源自 Freepik

前言

雖然一直都在用,但有些命令仍是半知半懂的,所以就好好學一下吧。

一些輔助工具:

  • shellcheck - Shell 腳本靜態(tài)檢查工具,主流編輯器都有插件。類似 ESLint 的工具。
  • zx - Google 出品,用 JavaScript 寫 Shell 腳本。

本文大部分內容來自阮一峰老師的 Bash 腳本教程。

一、Shell 命令格式

$ command [ arg1 ... [ argN ] ]

其中 command 是一個具體的命令或者一個可執(zhí)行文件,arg1... argN 是傳遞給命令的參數(shù),是可選的。

命令與參數(shù),參數(shù)與參數(shù)之間通過「一個空格」隔開。若有「多個空格」,多余空格會被自動忽略,作用相當于一個空格。

$ ls -l

其中 ls 是命令,-l 是參數(shù)。有些參數(shù)是命令的配置項,它們一般以一個「短橫線」開頭,比如上面的 -l。通常配置項參數(shù)有短形式和長形式兩種形式,比如 -l 是短形式,--list 是長形式。兩種寫法作用完全相同,短形式便于輸入,長形式可讀性、語義更好。

通常命令都是一行的,可有些命令較長,寫成多行有利于閱讀和編輯,只要在每行結尾處加上反斜杠 \ 可以,Shell 會將下一行跟當前行一起解析。

$ echo Hello World

# 等同于
$ echo Hello \
World

二、命令的組合與繼發(fā)

命令組合符 &&,前一個命令執(zhí)行成功,才會接著執(zhí)行第二個命令。

$ command1 && command2

命令組合符 ||,前一個命令執(zhí)行失敗,才會接著執(zhí)行第二個命令。

$ command1 || command2

命令結束符 ;(分號),前一個命令執(zhí)行結束后(無論成功與否),接著執(zhí)行第二個命令。命令結束符可使得一行中放置多個命令。

$ clear; ls

管道符 |,前一個命令的輸出作為第二個命令的輸入。

$ command1 | command2

# 相當于
$ command1 > tempfile
$ command2 < tempfile
$ rm tempfile

三、引號

  • 單引號:單引號用于保留字符的字面含義,各種特殊字符在單引號里面,都會變?yōu)槠胀ㄗ址?/li>
  • 雙引號:比單引號寬松,大部分特殊字符在雙引號里面,都會失去特殊含義,變成普通字符。但是,三個特殊字符除外:美元符號($)、反引號(`)和反斜杠(\)。這三個字符在雙引號之中,依然有特殊含義,會被 Bash 自動擴展。
$ echo '$USER'
$USER

$ echo "$USER"
frankie

換行符在雙引號之中,會失去特殊含義,Bash 不再將其解釋為命令的結束,只是作為普通的換行符。所以可以利用雙引號,在命令行輸入多行文本。

$ echo "hello
world"

hello
world

echo 發(fā)音 [?ekō](才發(fā)現(xiàn)原來一直讀錯了,慚愧)。其參數(shù) -e 會解析引號中的特殊字符(比如換行符 \n)。若在 CLI 中直接輸入 echo 命令 \n 也會解析為換行符,而不是普通的 \n 字符串。

$ echo -e "Hello\nShell"
Hello
Shell

四、子命令擴展

$(...) 可以擴展成另一個命令的運行結果,該命令的所有輸出都會作為返回值。還有另一種較老的語法,子命令放在反引號之中,也可以擴展成命令的運行結果。

$ echo $(date) 
2022年 6月27日 星期一 00時31分14秒 CST

$ echo `date`
2022年 6月27日 星期一 00時32分01秒 CST

五、讀取變量

  • 在變量名前加上 $,比如 $SHELL
  • 讀取變量時,變量名可以使用花括號 {} 包圍,比如 $SHELL 可以寫成 ${SHELL}。
  • 如果變量的值本身也是變量,可以使用 ${!varname} 語法,讀取最終的值。(好像不太對,待進一步驗證)

六、算術運算

  • 除法運算符的返回結果總是為「整數(shù)」,比如 $(( 5 / 2 )) 的結果為 2,而不是 2.5。
  • $(( ... )) 的圓括號之中,不需要在變量名之前加上 $,不過加上也不報錯。
  • 如果 $((...)) 里面使用不存在的變量,也會當作 0 處理。
  • $[...] 是以前的語法,也可以做整數(shù)運算,不建議使用。

小數(shù)運算,需借助 bc 命令,其中 scale 表示小數(shù)位,ibaseobase 進行其他進制數(shù)運算。比如:

$ var1=3
$ var2=6  
$ result=$(echo "scale=2; $var1 / $var2" | bc) 
$ echo $result 
.50

七、目錄堆棧

cd - 命令可以返回前一次的目錄。默認情況下,只記錄上一次所在的目錄。

$ cd ~/Desktop/
$ cd -
~

八、腳本

8.1 Shebang 行

腳本的第一行通常是指定解釋器,即這個腳本必須通過什么解釋器執(zhí)行。這一行以 #! 字符開頭,這個字符稱為 Shebang,所以這一行就叫做 Shebang 行。

#! 后面就是腳本解釋器的位置,Bash 腳本的解釋器一般是 /bin/sh/bin/bash。

#!/bin/sh

# 或者
#!/bin/bash

#! 與腳本解釋器之間有沒有空格,都是可以的。

如果 Bash 解釋器不放在目錄 /bin,腳本就無法執(zhí)行了。為了保險,可以寫成下面這樣。

#!/usr/bin/env bash

上面命令使用 env 命令(這個命令總是在 /usr/bin 目錄),返回 Bash 可執(zhí)行文件的位置。env 命令的詳細介紹,請看后文。

Shebang 行不是必需的,但是建議加上這行。如果缺少該行,就需要手動將腳本傳給解釋器。

舉例來說,腳本是 script.sh,有 Shebang 行的時候,可以直接調用執(zhí)行。

$ ./script.sh

上面例子中,script.sh 是腳本文件名。腳本通常使用 .sh 后綴名,不過這不是必需的。

如果沒有 Shebang 行,就只能手動將腳本傳給解釋器來執(zhí)行。

$ /bin/sh ./script.sh

# 或者
$ bash ./script.sh

8.2 執(zhí)行權限和路徑

前面說過,只要指定了 Shebang 行的腳本,可以直接執(zhí)行。這有一個前提條件,就是腳本需要有執(zhí)行權限??梢允褂孟旅娴拿睿x予腳本執(zhí)行權限。

給所有用戶執(zhí)行權限

$ chmod +x script.sh

給所有用戶讀權限和執(zhí)行權限

$ chmod +rx script.sh

# 或者
$ chmod 755 script.sh

只給腳本擁有者讀權限和執(zhí)行權限

$ chmod u+rx script.sh

腳本的權限通常設為 755(擁有者有所有權限,其他人有讀和執(zhí)行權限)或者 700(只有擁有者可以執(zhí)行)。

除了執(zhí)行權限,腳本調用時,一般需要指定腳本的路徑(比如 path/script.sh)。如果將腳本放在環(huán)境變量 $PATH 指定的目錄中,就不需要指定路徑了。因為 Bash 會自動到這些目錄中,尋找是否存在同名的可執(zhí)行文件。

建議在主目錄新建一個 ~/bin 子目錄,專門存放可執(zhí)行腳本,然后把 ~/bin 加入 $PATH。

export PATH=$PATH:~/bin

上面命令改變環(huán)境變量 $PATH,將 ~/bin 添加到 $PATH 的末尾。可以將這一行加到 ~/.zshrc 文件里面,然后重新加載一次 .zshrc,這個配置就可以生效了。

$ source ~/.zshrc

以后不管在什么目錄,直接輸入腳本文件名,腳本就會執(zhí)行。

$ script.sh

上面命令沒有指定腳本路徑,因為 script.sh$PATH 指定的目錄中。

上面的配置文件,取決于你當前所用的 Shell。比如我這里是 zsh,配置文件為 ~/.zshrc,如果你是 bash,可能是 ~/.bash_profile、~/.bashrc 等。

九、條件判斷

if 關鍵字后面跟的是一個命令。這個命令可以是 test 命令,也可以是其他命令。命令的返回值為 0 表示判斷成立,否則表示不成立。

if commands; then
  commands
[elif commands; then
  commands...]
[else
  commands]
fi

判斷條件 commands 可以是一條命令,這條命令執(zhí)行成功(返回值為 0),就意味著判斷條件成立。

但更多地是使用 test 命令,語法如下:

# 寫法一
test expression

# 寫法二
[ expression ]

# 寫法三
[[ expression ]]

以上三種形式是等價的,第三種形式支持正則判斷,前兩種不支持。需要注意的是,后兩種寫法中 [] 與內部命令之間必須要有「空格」。因為 [test 命令的簡寫形式,因此它后面必須要有空格。舉個例子,使用 if 語句判斷一個文件是否存在:

# 寫法一
if test -e /tmp/foo.txt ; then
  echo "Found foo.txt"
fi

# 寫法二
if [ -e /tmp/foo.txt ] ; then
  echo "Found foo.txt"
fi

# 寫法三
if [[ -e /tmp/foo.txt ]] ; then
  echo "Found foo.txt"
fi

9.1 文件判斷

以下表達式用來判斷文件狀態(tài):

  • [ -a file ]:如果 file 存在,則為 true。
  • [ -b file ]:如果 file 存在,并且是一個塊(設備)文件,則為 true。
  • [ -c file ]:如果 file 存在,并且是一個字符(設備)文件,則為 true
  • [ -d file ]:如果 file 存在,并且是目錄,則為 true。
  • [ -e file ]:如果 file 存在,則為 true
  • [ -f file ]:如果 file 存在,并且是一個普通文件,則為 true。
  • [ -g file ]:如果 file 存在,并且設置了組 ID,則為 true。
  • [ -G file ]:如果 file 存在,并且屬于有效的組 ID,則為 true
  • [ -h file ]:如果 file 存在,并且是符號鏈接(軟鏈接),則為 true。
  • [ -k file ]:如果 file 存在,并且設置了它的 sticky bit,則為 true
  • [ -L file ]:如果 file 存在,并且是一個符號鏈接(軟鏈接),則為 true
  • [ -N file ]:如果 file 存在,并且自上次讀取后已被修改,則為 true。
  • [ -O file ]:如果 file 存在,并且屬于有效的用戶 ID,則為 true。
  • [ -p file ]:如果 file 存在,并且是一個命名管道,則為 true
  • [ -r file ]:如果 file 存在,并且可讀(當前用戶有可讀權限),則為 true。
  • [ -s file ]:如果 file 存在,并且其長度大于零,則為 true。
  • [ -S file ]:如果 file 存在,并且是一個網(wǎng)絡 socket,則為 true
  • [ -t fd ]:如果 fd 是一個文件描述符,并且重定向到終端,則為 true。 這可以用來判斷是否重定向了標準輸入/輸出/錯誤。
  • [ -u file ]:如果 file 存在,并且設置了 setuid 位,則為 true。
  • [ -w file ]:如果 file 存在,并且可寫(當前用戶擁有可寫權限),則為 true。
  • [ -x file ]:如果 file 存在,并且可執(zhí)行(當前用戶擁有可執(zhí)行/搜索權限),則為 true。
  • [ file1 -nt file2 ]:如果 file1file2 的更新時間最近,或者 file2 存在而 file1 不存在,則為 true
  • [ file1 -ot file2 ]:如果 file1file2 的更新時間更舊,或者 file2 存在而 file1 不存在,則為 true。
  • [ file1 -ef file2 ]:如果 file1file2 引用相同的設備和 inode 編號,則為 true
if [ -f "$FILE" ]; then
  echo "$FILE is a regular file."
fi

上面代碼中,$FILE 要放在雙引號之中,這樣可以防止變量 $FILE 為空,從而出錯。因為 $FILE 如果為空,這時 [ -e $FILE ] 就變成 [ -e ],這會被判斷為真。而 $FILE 放在雙引號之中,[ -e "$FILE" ] 就變成 [ -e "" ],這會被判斷為假。

未完待續(xù)...

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • # 一、shell基礎1 ## 1.shell是什么 - Shell是一個命令行解釋器,它為用戶提供 了一個向Li...
    如果我是閱讀 401評論 0 0
  • [TOC] 簡介: Shell 是一個用C語言編寫的程序,它是用戶使用Linux的橋梁。Shell既是一種命令語言...
    黃海濱_x閱讀 933評論 0 2
  • 變量 變量定義 Shell 支持以下三種定義變量的方式: variable 是變量名,value 是賦給變量的值。...
    rill_閱讀 349評論 0 0
  • 目錄 替換 運算符 1、替換 如果表達式中包含特殊字符,Shell 將會進行替換。例如,在雙引號中使用變量就是一種...
    CholMay閱讀 634評論 0 2
  • 本節(jié)gawk是awk的gun版本,gawk實際上是一種編程語言而不是一個命令。gawk要用單引號和大括號包含進來,...
    滌除而玄覽閱讀 571評論 0 1

友情鏈接更多精彩內容