Bash 學習筆記

Undefined443發表於2024-08-15

Shell

Frequently Used Commands

Basic

open <dir/file>   # 在訪達中開啟 dir/file
start <dir/file>  # DOS 中的 open
xdg-open <dir/file>  # ubuntu 中的 open

clear  # 清屏
cls    # DOS 清屏

ls -a  # 檢視當前目錄所有檔案(包括隱藏檔案)

mv <file> <dir>     # 將 file 移動到 dir
mv <name1> <name2>  # 重新命名

mkdir -p <dir>  # 建立目錄(自動建立中間目錄)

rm <file>     # 刪除檔案
rm -rf <dir>  # 強制遞迴刪除
rmdir         # 刪除空目錄
del           # DOS 刪除命令

cat <file>     # 檢視檔案內容
cat -n <file>  # 顯示行號
type <file>    # DOS 中的 cat
tee <file>     # 將 stdin 的內容輸出到 file 中,同時將 stdin 的內容輸出到 stdout

less  # 分頁顯示文字檔案內容
more  # 分頁顯示文字檔案內容,比 less 功能少

touch <file>  # 建立檔案

echo -e  # 使用跳脫字元
echo -n  # 關閉自動換行

ps  # process status,顯示程序狀態

cd -  # 回到上個目錄

cmp file1 file2  # 顯示兩個檔案的不同

sudo reboot  # 重啟
sudo shutdown -h now  # 關機

sudo systemctl start sshd     # Linux 啟動 sshd 服務;比 service 命令好
sudo systemctl daemon-reload  # 重新載入配置檔案

abc/ 表示目錄,abc 表示檔案或目錄——當目錄存在時就是目錄,目錄不存在就是檔案。

Advanced

ln -s <source_file> <target_file>  # 建立軟連結

head -n 5 <file>  # 顯示 file 前 5 行內容
tail -n 5 <file>  # 顯示 file 後 5 行內容

tail -f <file>    # 根據檔案描述符實時監控檔案內容
tail -F <file>    # 根據檔名實時監控檔案內容

read VAR  # 從終端讀取使用者輸入,並賦值給 VAR 變數。
          # 按下 Ctrl + D 表示讀取到檔案流的末尾

basename "/bin/zsh"  # 獲取檔名 (zsh)
dirname "/bin/zsh"   # 獲取目錄名 (/bin)

file --mime-encoding <file>  # 獲取 file 的編碼方式 (GBK 會被當作 iso-8859-1)

iconv -f gbk -t utf-8 <file>  # 將 file 以 GBK 編碼開啟,並以 UTF-8 編碼輸出到 stdin

find <file>  # 在當前目錄下查詢檔案

which -a <prog>  # 找到 <prog> 的所有檔案
whereis <prog>   # 找到 <prog> 的所有檔案及 man 檔案

tr -d '\r' <file>  # 刪除檔案中的 \r
echo $PATH | tr ':' '\n'  # 將 PATH 中的 : 替換為換行符

python3 --help | grep -C 2 pip  # -C 2 表示顯示匹配行的上下 2 行

pbpaste  # 從剪貼簿中讀取內容
pbcopy   # 將剪貼簿內容貼上進來
# npm start sf 5-2 $(pbpaste)

# Stream Editor
# -i: in-place
sed -i.bak "s/hello/world/g" <file>  # 將 file 中的 "hello" 替換為 "world",並將原檔案備份為 file.bak

tree  # 顯示目錄結構

arch  # 顯示系統架構

nohup <prog> &  # & 用於將命令作為後臺程序執行,nohup 會忽略 SIGHUP 訊號,避免程序因終端被關閉發出的 SIGHUP 訊號導致程序終止
  • sed: Command-Line Options | GNU

  • sed: The s Command | GNU

Windows 上的 pbpaste:

pasteboard | GitHub

scoop bucket add extras
scoop install pasteboard

終端快捷鍵

  • ⌃R:搜尋歷史命令
  • ⌃D: 在新行輸入,表示 EOF 。在行中輸入,表示輸出“標準輸入”的緩衝區。此時連按兩下 ⌃D 表示 EOF
  • ⌃Z: 掛起,可用 fg 命令恢復
  • ⌃C: 終止程序
  • ⌃V: 字面值輸入
  • ⌃C⌃D 的區別:⌃C 是終止程序,⌃D 是結束輸入

執行 Shell 指令碼

#! 開頭的語句叫做 Shebang,用於指定指令碼直譯器。

不推薦:

#!/bin/bash

推薦:

#!/bin/env bash

在新程序中執行 Shell 指令碼

# 將 Shell 指令碼作為程式執行
# 如果不寫 ./,Linux 只會到系統路徑(由 PATH 環境變數指定)下查詢外部命令
chmod +x script.sh # 給指令碼新增執行許可權
./script.sh        # 執行指令碼

# 將 Shell 指令碼作為引數傳遞給 Bash 直譯器
bash script.sh  # 這種方式將忽略指令碼檔案第一行的指定直譯器資訊

在當前程序中執行 Shell 指令碼

source script.sh  # 在 zsh 下依然需要使用 ./script.sh
. script.sh       # source 命令的簡化寫法

Bash 配置檔案

/etc/profile.d 下的所有 .sh 檔案以及 /etc/profile 檔案等價於全域性的使用者 ~/.profile 檔案,會在每次登入時執行,而在 ~/.bash_profile 下的指令碼只會在第一次登入時執行。

Shell 有多個配置檔案。對普通使用者來說,~/.bashrc 是最重要的檔案,因為不管是否登陸都會載入該檔案。

Git Bash for Windows 的配置檔案位置在 C:\Program Files\Git\etc\profile.d\git-prompt.sh

flowchart TD X(登入) --> A0[登入 Shell] A0 --> A1["/etc/profile"] --> A2["/etc/profile.d"] A2 --> A3["~/.bash_profile"] A3 --> A4["~/.bash_login"] A4 --> A5["~/.profile"] A5 --> Y["~/.bashrc"] X --> B0["互動式非登入 Shell"] B0 -------> Y classDef c1 fill:#afa,stroke:#080; class A0,B0 c1; classDef c2 fill:#f96,stroke:#f66; class X,Y c2;

Shell 配置檔案(配置指令碼)的載入 | C 語言中文網

例項

# 給 PATH 變數增加新的路徑
PATH="$PATH:$HOME/addon" # 將主目錄下的 addon 目錄也設定為系統路徑
# 修改命令提示符的格式
PS1="[bash]\$" # 命令提示符改為 [bash]$

PATH 使用冒號 : 分隔不同的路徑

PS1 和 PS2 中的特殊字元 | C 語言中文網

變數

在 Bash shell 中,每一個變數的值都是字串。在預設情況下,Bash shell 不會區分變數型別。當然,如果有必要,也可以使用 declare 關鍵字顯式定義變數的型別。

# 定義變數
variable=value    # 如果要賦的值不包含任何空白符及跳脫字元,那麼可以不使用引號。不建議這種寫法
variable='value'  # '' 相當於原生字串,將不解析內容中的變數、命令
variable="value"  # 最常用的情況。會解析其中的變數,命令。但是不會解析跳脫字元

readonly variable # 將變數定義為只讀變數
unset variable    # 刪除變數。unset 命令不能刪除只讀變數

# 使用變數
author="John"
echo $author
echo "作者是 ${author}。" # 用花括號指明變數名的邊界

⚠️ 注意: 不要使用 path 作為變數名,在大小寫不敏感的系統中 path 等價於環境變數 PATH

Globbing

Bash 會在沒有被引號包圍的命令列引數上應用檔名擴充套件。

Wildcard (萬用字元)

*:表示任意長度的字串,除了以 . 開始的檔名和包含路徑分隔符 / 的檔名

?:表示一個字元

假設當前目錄下有檔案 file1.txt, file2.txt, ...,file9.txt,以及 filea.txtfileb.txtfilec.txt,那麼:

file[135].txt  # 單字元匹配,匹配 file1.txt, file3.txt, file5.txt
file[1-9].txt  # 範圍匹配,匹配 file1.txt, file2.txt, ..., file9.txt
file[!1].txt # 否定匹配,匹配 file2.txt, file3.txt, ..., file9.txt
file[1-3a-c].txt # 複合集合,匹配 file1.txt, file2.txt, file3.txt, filea.txt, fileb.txt, filec.txt

file[[:digit:]].txt  # 字元類,匹配 file1.txt, file2.txt, ..., file9.txt

常見的字元類包括 [:alpha:][:alnum:][:lower:][:upper:] 等。

說明

當使用方括號匹配時,如果模式不匹配任何檔案或目錄,它的行為將取決於 shell 的設定和選項(例如,Zsh 中的 nullglobnomatch 選項影響這種行為)。

引數展開

${...} 是引數展開的一般形式,在這種形式中,你可以對變數進行各種操作。

引數展開修飾符

示例

${(o)array}  # 對陣列排序
修飾符 含義
L 將字串轉換為小寫
U 將字串轉換為大寫
f 將字串按換行符分割為陣列
o 陣列按字典升序排序
O 陣列按字典降序排序
n 陣列按數值升序排序
k 展開關聯陣列的鍵
v 展開關聯陣列的值
@ 當陣列被引號包圍時,@ 確保每個元素被當作獨立的個體處理

引數替換

var=${var:-new}  # 如果 var 為空或未定義,那麼將 var 替換為 new

var=${var/old/new}  # 將 var 中的第一個 old 替換為 new
var=${var//old/new} # 將 var 中的所有 old 替換為 new
var=${var/#old/new} # 如果 var 的字首匹配 old,那麼將匹配的 old 替換為 new
var=${var/%old/new} # 如果 var 的字尾匹配 old,那麼將匹配的 old 替換為 new

引數替換 | 簡書

命令替換

將命令的結果賦給變數

var=`cat log.txt`  # 如果有多個命令,命令之間以分號 ; 分隔
var=$(cat log.txt) # 推薦,並且這種方式最常見
Fir_File_Lines=$(wc -l $(ls | sed -n '1p')) # 兩層巢狀
# 注意,如果被替換的命令的輸出內容有換行符,或者含有多個連續的空白符,那麼在輸出變數時應該將變數用雙引號包圍,否則系統會使用預設的空白符來填充。
echo "$var"

修改變數的值時不能在變數名前加 $,只有在使用變數時才能加 $

= 的周圍不能有空格

變數的作用域

區域性變數

function func() {
  local a=99  # 若不使用 local,則 a 是全域性變數
}

全域性變數

在當前 Shell 程序中有效。在 Shell 中定義的變數預設就是全域性變數。

環境變數

How To Read and Set Environmental and Shell Variables on Linux | DigitalOcean

在所有的子程序中有效。

export a     # 將變數匯出為環境變數
export b=22  # 在定義的同時匯出為環境變數

環境變數被建立時所處的 Shell 程序稱為父程序,父程序中建立的程序叫子程序。建立子程序最簡單的方式是執行 bash 命令。透過 exit 命令可以一層一層地退出 Shell。

exit 可以返回命令的退出狀態:exit 0

進入 Shell 子程序

特殊變數

變數 含義
$0 當前指令碼的檔名
$1 傳遞給指令碼或函式的引數(從 1 開始計數)
$# 傳遞給指令碼或函式的引數個數
$* 傳遞給指令碼或函式的所有引數
$@ 傳遞給指令碼或函式的所有引數
$? 上個命令的退出狀態,或函式的返回值
$$ 當前 Shell 程序 ID

$1 是一種宏替換

命令的退出狀態由 exit 命令返回,函式的返回值由 return 返回

$*$@ 的區別

$*$@ 不被雙引號 "" 包含時,它們沒有任何區別。

當它們被雙引號 "" 包含時,會有如下區別:

  • "$*" 會將所有的引數從整體上看做一份資料,而不是把每個引數都看做一份資料。
  • "$@" 仍然將每個引數都看作一份資料,彼此之間是獨立的。

$@ 更常用

位置引數

Shell 指令碼檔案和函式可以自動接收傳入的引數,並以 $1, $2, ... 的方式表示。

如果引數個數達到了 10 個,那麼需要用 ${10} 的方式指明引數名的邊界

給指令碼檔案傳遞位置引數

script.sh:

#!/bin/bash

echo "Language: $1"
echo "Name: $2"
$ source script.sh Shell LiXiao
Language: Shell
Name: LiXiao
給函式傳遞位置引數
$ function func() {
$   echo "Language: $1"
$   echo "Name: $2"
$ }
$ func C++ LiXiao
Language: C++
Name: LiXiao

字串

# 獲取字串長度
echo ${#string}  # 6

# 字串拼接
str1="a"
str2="b"
str3=$str1$str2"c"  # 只需將字串放在一起就能拼接

字串擷取

# 擷取範圍,從左邊開始計數
$ str=111a222b333a444  # a 是第 3 位(從 0 開始計數)
$ echo ${str:3:5}  # 從第 3 位開始輸出 5 位

a222b

$ echo ${str:3}  # 從第 3 位開始輸出到末尾
a222b333a444

# 從右邊開始計數
$ echo ${str:0-4:5}  # 從倒數第 4 位開始輸出 4 位。b 是倒數第 4 位(從 1 開始計數)
b333a

# 截去子串
# 截去左邊(截去字首)
$ echo ${str#*a}  # 從左邊截去第一個匹配 a 及之前的所有字元
222b333a444

$ echo ${str#111a}  # 省略萬用字元 *,此時需要寫出完整的字首
222b333a444

$ echo ${str##*a}  # 從右邊截去第一個匹配 a 及之前的所有字元
444

# 截去右邊(截去字尾)
$ echo ${str%a*}  # 從右邊截去第一個匹配 a 及之後的所有字元
111a222b333

$ echo ${str%%a*}  # 從左邊截去第一個匹配 a 及之後的所有字元
111

陣列

陣列的宣告與使用

在 Zsh 中,陣列索引從 1 開始計數;在 Bash 中,陣列索引從 0 開始計數。

在 Shell 中,用 () 來表示陣列,陣列元素之間用空格分隔:

# 陣列沒有長度限制
nums=(1 2 3)  # 定義一個陣列
nums=(1 2 3 "abc")  # 陣列沒有型別限制
nums+=(4 5 6)  # 在陣列末尾新增元素
nums[20]=88  # 可以只給特定元素賦值
nums=([3]=24 [5]=19 [10]=12)

# 獲取陣列元素
echo ${nums[0]}  # 輸出第 0 個元素:1(Bash)
echo ${nums[*]}  # 輸出陣列所有元素
echo ${nums[@]}  # 另一種寫法

unset nums[0]  # 刪除陣列元素
unset nums  # 刪除陣列

⚠️ Zsh 中陣列的行為與 Bash 有所不同:

array=(1 2 3)

echo $array  # Bash:輸出第 0 個元素;Zsh:輸出所有元素
echo ${array[1]}  # Bash:1;Zsh:2
echo $array[1]  # 只有 Zsh 可以省略 {}

獲取陣列長度

和獲取字串長度的方法類似,使用 # 號:

# 兩種形式是等價的
echo ${#nums[*]}
echo ${#nums[@]}

陣列拼接

先將陣列展開,然後再合併到一起:

# 兩種形式是等價的
arr=(${nums_1[*]} ${nums_2[*]})
arr=(${nums_1[@]} ${nums_2[@]})

關聯陣列

也即 Python 中的字典

定義與使用

declare -A color  # 定義一個名為 color 的關聯陣列
color["red"]="#ff0000"
color["green"]="#00ff00"
color["blue"]="#0000ff"

# 在定義的同時賦值
declare -A color=(
  ["red"]="#ff0000"
  ["green"]="#00ff00"
  ["blue"]="#0000ff"
)

echo $(color["red"])  # 訪問關聯陣列

操作關聯陣列

Bash
# 獲取所有鍵
# 兩種形式是等價的
${!color[*]}
${!color[@]}
Zsh
# 獲取所有鍵
${(k)color}
"${(@k)color}"  # @ 使得每個鍵作為獨立個體存在,這種用法最常用

# 遍歷所有鍵和值
for key val in ${(kv)color}; do
    echo "$key: $val"
done

數學運算

除了 C 語言中包含的所有算數運算子外,Shell 還包括冪運算子:**

整數運算

將表示式寫在雙小括號之間:((1+2))

多個表示式用 , 分隔,最後一個表示式的值將作為命令的執行結果。

使用 $ 獲取 (( )) 命令的結果。

Shell 中還有 let 命令,$[] 命令,它們的功能和 (()) 一樣,都是計算整數。 除此之外,還有 expr 命令也可以用於整數計算。

e.g.

$ ((a=2-1))
$ ((b=a+4))
$ echo $((1+2, 2**3))
8
$ echo $((a<2&&b==5))
1
$ echo $((a++))
1

浮點數運算

使用 bc 命令

一行一個表示式,或者一行中多個表示式用分號 ; 分開。

quit 退出

C 語言中文網:Linux bc 命令

內建變數

變數名 作用
scale 指定精度,預設為 0
ibase 指定輸入進位制
obase 指定輸出進位制
last. 表示最近列印的數字

💡 Tip: obase 要儘量放在 ibase 前面,因為指定 ibase 後,所有後續輸入的數字都是以 ibase 的進位制來換算的。

內建函式

在輸入 bc 命令時需要使用 -l 選項啟用數學庫。

函式名 作用
s(x) 正弦函式,x 是弧度值
c(x) 餘弦函式,x 是弧度值
a(x) 反正切函式,返回弧度值
l(x) 自然對數函式
e(x) 自然指數函式
j(n,x) 貝塞爾函式,計算從 n 到 x 的階數

在 Shell 中使用 bc 計算器

echo "expression" | bc  # expression 可以包含 Shell 中的變數:$var
var=$(echo "expression" | bc)

e.g. 進位制轉換:

# 10 進位制轉 16 進位制
i=15
j=$(echo "obase=16;$i" | bc)
echo $j

# 16 進位制轉 10 進位制
k=$(echo "obase=10;ibase=16;$j" | bc)  # 別忘了 obase 要寫在 ibase 前面
echo $k
藉助重定向使用 bc 計算器
var=$(bc << EOF  # 分界符(EOF)可以自定義
2*3
last/2
EOF

這種方式在有大量數學計算時比較方便

declare 命令

語法:

declare [-/+] [選項] expression

declare 和 typeset 都是 Shell 內建命令,它們用法相同,不過 typeset 已經棄用。

- 表示設定屬性,+ 表示取消屬性。

選項 含義
-f 列出之前由使用者在指令碼中定義的函式名稱和函式體
-F 僅列出自定義函式名稱
-g 在 Shell 函式內部建立全域性變數
-p 顯示指定變數的屬性和值
-a 宣告變數為普通陣列
-A 宣告變數為關聯陣列
-i 將變數定義為整型
-r 將變數定義為只讀
-x 將變數設定為環境變數

e.g.

$ declare -i n m num  # 將 n, m, num 宣告為整型
$ declare -r CONST=23 # 將 CONST 宣告為只讀,等價於 readonly CONST

$ declare -p CONST    # 顯示變數的屬性和值
declare -r CONST="23"

重定向

檔案描述符

為了表示並區分已經開啟的檔案,Linux 會給每個檔案分配一個 ID,即檔案描述符(File Descriptor)。stdin,stdout,stderr 是預設開啟的,它們已經有了自己的檔案描述符:

FD 檔名
0 stdin
1 stdout
2 stderr

Linux 始終從檔案描述符 0 讀取內容,向檔案描述符 1 輸出正確結果,向檔案描述符 2 輸出錯誤提示。

檔案描述符運算子(>, <)可以修改檔案描述符指向的檔案,從而實現重定向的功能。

輸出重定向

格式:FD>file

在輸出重定向中,> 表示覆蓋,>> 表示追加。

💡 Tip: FD> 之間不能有空格,否則 Shell 會解析失敗;>file 之間的空格可有可無。

command >>file           # 以追加輸出的方式開啟 file,並讓檔案描述符 1 指向 file。這裡 1 被省略
command 2>>file
command >>file1 2>>file2 # 正確結果輸出到 file1,錯誤提示輸出到 file2
command >>file 2>&1      # 2>&1:讓檔案描述符 2 指向檔案描述符 1 所指的檔案。結果是 1 和 2 都指向 file
command &>file           # 以覆寫輸出的方式開啟 file,並讓檔案描述符 1 和 2 都指向 file
command n>&-             # 關閉檔案描述符 n 及其代表的檔案。n 預設為 1

建議將正確結果和錯誤輸出儲存到不同檔案

/dev/null 檔案

如果不想顯示或儲存任何輸出,可以將輸出重定向到 /dev/null 檔案中:

ls > /dev/null   # 僅拋棄標準輸出
ls 2> /dev/null  # 僅拋棄錯誤輸出
ls &> /dev/null  # 拋棄所有輸出
dir > NUL  # CMD,僅拋棄標準輸出
dir 2>NUL  # CMD,僅拋棄錯誤輸出
dir > NUL 2>&1  # CMD,拋棄所有輸出;2>&1 用於將標準錯誤重定向到標準輸出,從而同時拋棄標準輸出和標準錯誤。
Get-ChildItem | Out-Null  # PowerShell,僅拋棄標準輸出

Get-ChildItem > $null      # PowerShell,僅拋棄標準輸出
Get-ChildItem 2> $null     # PowerShell,僅拋棄錯誤輸出
Get-ChildItem *> $null     # PowerShell,拋棄所有輸出

輸入重定向

格式:FD<file

command <file          # 以輸入的方式開啟 file,並讓檔案描述符 0 指向 file。這裡 0 被省略
command <input >output # 從 input 輸入,正確結果輸出到 output
command n<>file        # 同時以輸入和輸出的方式開啟 file,並讓檔案描述符 n 指向 file。n 預設為 0

# 使用 Here Document
command <<EOF
  document
EOF

Here Document

語法:

command <<END  # END 是分界符,可以自定義
  document
END  # 行內不能有其他內容

Shell 將把 document 的內容輸入到命令中。

忽略命令替換

預設情況下,document 中出現的變數和命令也會被求值或執行。你可以將分界符用單引號或雙引號包圍來使 Shell 替換失效:

command <<'END'
  document
END
忽略製表符

預設情況下,行首的製表符也被當做正文的一部分。不過我們輸入製表符僅僅是為了格式對齊,並不希望它作為正文的一部分。為了去掉製表符,你可以在 <<END 之間新增 -

cat <<-END
  document
END
常見用法

Here Document 最常用的功能還是向使用者顯示命令或者指令碼的用法資訊:

usage(){
  cat <<-END
    usage: command [-x] [-v] [-z] [file ...]
    A short explanation of the operation goes here.
    It might be a few lines long, but shouldn't be excessive.
END
}

程式碼塊重定向

將重定向命令放在程式碼塊的結尾處,就可以對程式碼塊中的所有命令實施重定向:

sum=0
while read n; do
  ((sum += n))
done <input >output  # 輸入重定向為 input,輸出重定向為 output 。

# 組命令重定向
{
  echo "line 1"
  echo "line 2"
  echo "line 3"
} >file

管道

command1 | command2  # 管道符 | 左邊命令的 stdout 將連線到右邊命令的 stdin

注意管道只處理正確的輸出(stdout),如果想讓管道也處理錯誤輸出(stderr),需要將 stderr 重定向到 stdout: command1 2>&1 | command2

管道資料流指示

curl -sSL https://install.python-poetry.org | python3 -  # 從網路獲取安裝指令碼,然後執行

python3 - 表示 Python 將從管道讀取輸入

Runoob: Shell 中的特殊字元

管道與輸入重定向

command1 <input | command2 | command3 >output  # 第一個命令從 input 獲取輸入,最後一個命令向 output 寫入輸出。

C 語言中文網:管道詳解

過濾器

過濾器:從標準輸入讀取資料,向標準輸出輸出結果的命令

常用過濾器:

命令 說明
awk 用於文字處理的解釋性程式設計語言,通常被作為資料提取和報告的工具。
cut 用於將每個輸入檔案(如果沒有指定檔案則為標準輸入)的每行的指定部分輸出到標準輸出。
grep 用於搜尋一個或多個檔案中匹配指定模式的行。
tar 用於歸檔檔案的應用程式。
head 用於讀取檔案的開頭部分(預設是 10 行)。如果沒有指定檔案,則從標準輸入讀取。
paste 用於合併檔案的行。
sed 用於過濾和轉換文字的流編輯器。
sort 用於對文字檔案的行進行排序。
split 用於將檔案分割成塊。
strings 用於列印檔案中可列印的字串。
tac cat 命令的功能相反,用於倒序地顯示檔案或連線檔案。
tail 用於顯示檔案的結尾部分。
tee 用於從標準輸入讀取內容並寫入到標準輸出和檔案。
tr 用於轉換或刪除字元。
uniq 用於報告或忽略重複的行。
wc 用於列印檔案中的總行數、單詞數或位元組數。

grep

grep 'word' file    # 在 file 中查詢並顯示包含 word 的行
grep -i 'word' file # 忽略大小寫
grep -R 'word' .    # 在當前目錄及其子目錄下的所有檔案中查詢並顯示包含 word 的行
grep -c 'word' file # 搜尋並顯示 word 在 file 中出現的次數

Shell 結構語句

if else

if condition  # 也可以寫作:if condition; then
then
  statements
else
  statements
fi

💡 Tip: condition 檢測的是命令的退出狀態。通常退出狀態為 0 表示 “成功” ,其他狀態表示 “失敗” 。((1)) 的退出狀態為 0 。

if elif else

if condition1; then
  statements
elif condition2; then
  statements
elif condition3; then
  statements
else
  statements
fi

if 語句也支援使用邏輯運算子將多個退出狀態組合起來。

case in

語法:

case expression in  # expression 可以是變數、數學表示式,或者是命令的執行結果。只要能得到 expression 的值就可以
  pattern1)         # pattern 可以是數字,字串,甚至是簡單的正規表示式
    statements
    ;;              # 兩個分號表示一條 case 語句的結束
  pattern2)
    statements
    ;;
  pattern3)
    statements
    ;;
  *)            # 可以沒有 *) 部分。* 實際上是正規表示式
    statements  # 最後的雙分號可以省略
esac

case in 支援的正規表示式

格式 說明
* 表示任意字串
[abc] 表示 a, b, c 三個字元中的任意一個。比如 [15ZH] 表示 1, 5, Z, H 中的任意一個
[m-n] 表示從 m 到 n 的任意一個字元。比如 [0-9] 表示一個數字,[0-9a-zA-Z] 表示一個字母或數字
| 表示多重選擇,相當於或運算。比如 abc|xyz 表示匹配字串 "abc" 或 "xyz"

e.g.

read -n 1 char
case $char in
  [a-zA-Z])
    printf "\n字母\n"
    ;;
  [0-9])
    printf "\n數字\n"
    ;;
  [,.?!])
    printf "\n標點符號\n"
    ;;
  *)
    printf "\n錯誤\n"
esac

select in

select in 可以自動顯示帶編號的選單,使用者輸入編號就可以為迴圈變數賦予編號對應的值。select in 經常與 case in 一起使用。

語法(結合 case in):

select variable in value_list
do
  case variable in
    pattern1)
      statements
      break        # select in 只有遇到 break 或檔案結束符才會退出迴圈
      ;;
    pattern2)
      statements
      break
      ;;
    pattern3)
      statements
      break
      ;;
    *)
      echo "輸入錯誤,請重新輸入。"
  esac
done

可以修改環境變數 PS3 來自定義輸入提示符

break,continue

break n
continue n

其中 n 表示作用的迴圈的層數(從內向外)。

while

語法:

while condition
do
  statements
done
while true; do
  statements
done

until

until 的使用場景很少,一般使用 while 即可。

語法:

until condition # 若 condition 不成立則執行迴圈語句
do
  statements
done

for

C 風格 for

sum=0
for((i=1; i<=5; ++i))
do
  ((sum += i))
done
echo $sum

Python 風格 for in

for variable in value_list
do
  statements
done
value_list
  1. 直接給出具體的值:1 2 3 4 5"john" "jack" "tom"
  2. 給出一個取值範圍(只支援數字和字母):{1..5}{a..f}
  3. 使用命令的執行結果:$(ls)
  4. 使用 Shell 萬用字元:*.sh
  5. 使用特殊變數:$@(如果省略 value_list 的話相當於使用 $@)

Shell 萬用字元

[[#特殊變數]]

函式

語法:

# 定義
function func() {  # function 關鍵字可以省略。如果寫了 function 關鍵字,那麼 () 可以省略
  statements
  [return value]
}

function func {  # 省略 ()
  ...
}

func() {  # 省略 function
  ...
}

function func --description "some description"  # 你可以為函式新增描述

# 呼叫
func [param1] [param2] [...] # 函式名後面不加括號

Shell 不限制定義和呼叫的順序。

函式返回值

Shell 中的函式返回值是一個介於 0~255 的整數,用來表示函式的退出狀態:返回值為 0 表示函式執行成功,非 0 表示函式執行失敗。函式執行失敗時可以根據退出狀態來判斷具體出現了什麼錯誤。

💡 Tip: 在 Shell 中不要用返回值表示函式的處理結果

如果函式體中沒有 return 語句,那麼函式將使用最後一條命令的退出狀態作為自己的返回值。

如何得到函式的處理結果

要想得到函式的處理結果,可以使用以下兩種方案:

  • 在函式內部使用 echoprintf 等命令將結果輸出,並在函式外部使用 $()`` 捕獲結果。(推薦)
  • 將處理結果賦給全域性變數

條件表示式

[[ ]] 用來檢測某個條件是否成立。

[[ ]] 是 Shell 內建關鍵字,不是命令,在使用時沒有給函式傳遞引數的過程。因此相比 test 命令,[[ ]] 不需要關注某些細枝末節:

  • 不需要把變數名用雙引號 "" 包圍起來。即使變數是空值,也不會出錯。
  • 不需要對 >< 進行轉義。

語法:

[[ expression ]]  # 注意兩側的空格

檔案檢測運算子

expression 作用
-e path 判斷 path 是否存在
-d path 判斷 path 是否存在,並且是否為目錄檔案
-f path 判斷 path 是否存在,並且是否為普通檔案
-s path 判斷 path 是否存在,並且是否非空
-r path 判斷 path 是否存在,並且是否擁有讀許可權
-w path 判斷 path 是否存在,並且是否擁有讀許可權
-x path 判斷 path 是否存在,並且是否擁有執行許可權
path1 -nt path2 判斷 path1 的修改時間是否比 path2 新
path1 -ot path2 判斷 path1 的修改時間是否比 path2 舊
read fileName
read msg

if [[ -w $fileName ]] && [[ -n $msg ]]; then
  echo $msg >$fileName
  echo "寫入成功"
else
  echo "寫入失敗"
fi

使用 ! 的情況:

if [[ ! -e $path ]]; then
  echo "$path 不存在"
fi

# 不要寫成下面這樣
if ![[ -e $path ]]
# 或者
if [[ !(-e $path) ]]

字串判斷運算子

expression 作用
-z str 判斷 str 是否為空
-n str 判斷 str 是否非空
str1 = str2
str1 == str2
判斷 str1 是否和 str2 相等
str1 != str2 判斷 str1 是否和 str2 不相等
str1 > str2 判斷 str1 是否大於 str2
str1 < str2 判斷 str1 是否小於 str2

💡注意:在使用 ===!=>< 運算子比較字串時,運算子與兩邊的字串之間一定要有空格!否則可能會出現奇葩問題。

read str1
read str2

# 檢測字串是否為空
# 條件表示式支援邏輯運算子
if [[ -z $str1 || -z $str2 ]]; then
  echo "字串不能為空"
# 比較字串
elif [[ $str1 < $str2 ]]; then
  echo "str1 < str2"
else
  echo "str1 >= str2"
fi

條件表示式支援正規表示式

可以使用 =~ 來檢測字串是否符合某個正規表示式:

# 檢測一個字串是否是手機號
read tel
if [[ $tel =~ ^1[0-9]{10}$ ]]; then
  echo "你輸入了一個手機號"
else
  echo "你輸入的不是手機號"
fi

更多運算子請參見 C 語言中文網

echo 輸出彩色字元

echo -e "\033[背景色;前景色m輸出文字"

echo -e "\e[31m輸出文字" # 只指定前景色,輸出紅色字元
echo -e "\e[41m輸出文字" # 只指定背景色,輸出紅底白字
echo -e "\e[0m輸出文字"  # 使用預設配色

# 樣例
echo -e "\e[31mHello, World\e[0m" # 紅色 Hello, World
# 注意,在 -e 選項的 echo 命令中,輸出語句如果含有感嘆號 !,則感嘆號的後面只能是空白符或語句結束的雙引號。否則 ! 會被解析成事件提示符
echo -e "文字"'!'"文字" # 解決 echo -e 輸出 ! 的問題

# 設定顏色變數
GREEN="\e[32m"
RES="\e[0m"

echo -e "${GREEN}Hello, World$RES"

# 設定顏色動作
PRT_GREEN="echo -e \e[32m"

${SET_GREEN}"message"$RES
背景色 前景色 顏色
40 30 黑色
41 31 紅色
42 32 綠色
43 33 黃色
44 34 紫色
45 35 粉色
46 36 藍色
47 37 灰色

\033[背景色;前景色m 是轉義序列,其中 \033[ 是轉義起始符,m 是轉義終止符。

\033 對應 ASCII 碼錶的 Esc,可以用 \e\E 代替。

背景色和前景色沒有先後順序。適應我們慣常的思維順序,一般先確定背景色,再確定前景色。

C 語言中文網:echo 命令:顯示文字並給文字新增顏色

事件提示符

!100  # 執行第 100 條命令
!-1   # 執行倒數第一條命令
!!    # !-1 的 alias

!abc  # 引用最近的以 abc 開頭的命令。可以在執行一條命令之後忘記這個命令的引數時使用。
!?abc # 引用最近的包含 abc 的命令

# 間接取值
$ a="Hello"
$ b="a"
$ echo ${!b}
Hello

在 Shell 中,使用 ⬆️ 和 ⬇️ 也可以快速切換上一條、下一條命令。

Linux 多命令順序執行連線符

| 符號 | 作用 |
| :--: | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| ; | 沒有任何邏輯關係的連線符。當多個命令用分號連線時,各命令之間的執行成功與否彼此沒有任何影響,都會一條一條執行下去。 |
| | | | 邏輯或,當用此連線符連線多個命令時,前面的命令執行成功,則後面的命令不會執行。前面的命令執行失敗,後面的命令才會執行。 |
| && | 邏輯與,當用此連線符連線多個命令時,前面的命令執行成功,才會執行後面的命令,前面的命令執行失敗,後面的命令不會執行 |
| | | 管道符,當用此連線符連線多個命令時,前面命令執行的正確輸出,會交給後面的命令繼續處理。若前面的命令執行失敗,則會報錯,若後面的命令無法處理前面命令的輸出,也會報錯。 |

查詢歷史記錄

history

對於 Zsh 使用者,可以使用 fc 命令來搜尋歷史記錄,它是 history 命令的增強版本:

fc -l 1

這裡 -l 選項意味著列出歷史條目,1 是從歷史記錄的第一條命令開始搜尋。

最後,如果你在 Zsh 中啟用了增強的歷史搜尋功能,也可以直接透過按下特定的快捷鍵(如 Ctrl+R)進入增量式搜尋模式。例如:

  1. 在命令列提示符下按下 Ctrl + R
  2. 然後開始輸入你要搜尋的命令片段,如 cd

(reverse-i-search)`cd': cd /path/to/directory

在增量式搜尋模式下,你可以繼續輸入來精確搜尋,或者按下 Ctrl + R 來檢視匹配項的上一個條目。按下回車即可執行搜尋結果中的命令,或按下 Esc 或者 Ctrl + G 來退出搜尋模式而不執行任何命令。