kdbom avatar

shell 編程快速入門教程

🕙 by kdbom

shell 腳本就是命令行的堆砌,在堆砌的命令中加了一點魔法,讓它變得更加強大。

本教程的所有代碼,建議運行的時候進行抄寫而不是複制粘貼,因為大腦會偷懶!。

變量

你可以將變量想像當成一個容器,用來容納數據。

比如:你叫什麼名字。然後我回答我的名字,這裡的我的名字就可以稱為變量。不同的人擁有不同的名字這裡的 我的名字

不多bb直接上代碼,這將有助於理解變量,但不是變量最常用的方式,稍後我會講解代碼的詳細定義。

Bash:
1
2
3
4
5
#! /usr/bin/env bash

read

echo

將以上代碼保存為 hello.sh``chmod +x hello``./hello

1
2
3
$ ./test.sh
What's your name?  :  niconiconi
hello niconiconi

你可以多運行幾次,然後輸入不同的名字,看看程序是不是對不同的名字打招呼了

這裡對剛才的代碼進行講解

#! /usr/bin/env bash這一句在文本的第一行作用是指定運行腳本所用到的解釋器,當然我們也可以不寫它然後運行的時候選擇解釋器運行它。不過為了方便建議寫上。別問我為什麼和別人的寫法不一樣,我只能說這種寫法更好。

而變量最主要的作用是當我們編寫程序時數據可能還不存在,我們需要一個填充位來代替它。已進行後面的代碼編寫。

1
2
read -p "What's your name? : " -r name`先輸出一段字符串`What's your name? :``name
echo "hello $name"`這裡就是我我們的代碼最後打招呼的部分了,這裡我們還是使用了,如果不能替代,那我們是不是要先知道別人的名字才能打招呼啊。在 你可以將代碼中的 `echo``name``name``$``$

在上面的例子中我使用了取變量操作,但是只使用了一次取變量。我們可以多次使用取變量操作,來完成數據的拼接。

Bash:
1
2
3
4
5
#! /usr/bin/env bash

name="niconiconi"
age=9
echo  "I am $name, $age years old."

具體拼接幾次就看你的實際需求了。

數據類型

shell 腳本是沒有數據類型,shell 一切數據皆字符串。

開篇我們說過shell 是命令行的堆砌。我我們的數據最終都會化為一個一個的參數去調用命令(bool 是直接調用命令)。所以shell 並不關心數據類型。關心數據的是命令。命令對輸入參數是有要求的。使用最多的就是字符串,如果涉及數學運行的話。那麼命令會要求輸入數字,還有就是決定程序要不要執行的狀態。這裡可以對應上其他編程語言的三個類型字符型(siring) 整型(int)布爾型(bool)。對應類型只是為了為了方便理解,但是要記住shell 是沒有類型的。

shell 使用等號 不過 例如:等號右邊的稱為數據,等號左邊的稱為變量,這一個句子稱為賦值。賦值操作操作後 =``=``name="niconiconi"``name``niconiconi

shell 中的賦值,等號兩邊不能有空格類似於這樣 如果你學習過其它語言請不要按其它語言的寫法來,請遵守shell 的規範。name ="niconiconi" name= "niconiconi"

string

前面說過shell 沒有類型一切皆使用字符串(string)

string 使用雙引號 與其它用途的字符串進行區分。""``""``name="niconiconi"``niconiconi``""

類似於這樣

Bash:
1
2
3
4
5
#! /usr/bin/env bash

name="niconiconi"

echo  $name

輸出結果

1
2
$ ./test.sh
niconiconi

在shell 中string 不止一種寫法還可以寫作 但它被單引號包裹時也就失去了這一種特殊能力。比如我們用雙引號和單引號來分別輸出一下環境變量 和 將這兩句代碼保存為代碼在運行。直接在終端下運行幾乎沒有區別。name='niconiconi'``$``PATH``echo "PATH"``echo $PATH

Bash:
1
2
3
#! /usr/bin/env bash

echo  "$PATH"

雙引號的輸出結果

1
2
3
[email protected]:~$ echo "$PATH"
/home/test/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
[email protected]:~$

將雙引號改成單引號

Bash:
1
#! /usr/bin/env bashecho '$PATH'

單引號的輸出結果

最後一種寫法也就是命令行一下最常用的寫法,不帶引號的寫法,例如:但是不建議在shell 腳本中使用這種寫法。因為shell 一切數據皆以字符串進行儲存,然而一些字符串有特使用途。如 為了防止混淆,建議在聲明的時候直接使用引號進行強調。echo niconiconi``true

string 一般用來輸出信息,或者在我們的腳本中,用來用來調用程序,將程序需要的數據,裝在變量裡去調用這樣做是很有用的,在講的到循環的時候我會講。

int

前面說活shell 是沒有類型了。但是具體的命令是需要特定類型的數據的。比如我們進行數學運算。在上數學課的時候我們可以說我們寫下的數字是字符,也可以說它是數字在進行計算的時候他就是數字。沒進行計算的時候可以是字符也可以是數字。這裡的int 就有這種感覺。在就行運算的時候它就是數字,在沒進行運算前可以當成字符串。

可以進行運算的不止整數(int)對吧,還有小數(float)對吧,但是這裡只是演示運算所以就不講小數了,需要的可以自己查。

shell 的數學計算不止一種寫法,可以使用epxr bc let 等命令,但是在使用他們的時候需要注意乘法使用的符號 為了防止它轉義需要寫作*``\*

這種寫法是雙括號(())

Bash:
1
#! /usr/bin/env basha=5b="2"#b=2c=$(($a / b))#c=$((a / b)echo $c

運行結果

1
$ ./test.sh2

這裡的變量 一般字符串用途的數據使用雙引號進行強調以進行區分是很有必要的。然後再雙括號類進行計算 這裡進行了一個除法操作。並將除法操作取出來放入了變量 你可能發現了5 除以2 結果不是2.5 麼怎麼結果怎麼是2 了。那是因為整數運算結果也是整數後邊的小數部分直接丟棄了。a``b``(($a / b))``c``echo

語句中的 /``*``-``+

bool

bool 只有兩種狀態true 和false,可以從字面意思理解為真(true)和假(false),條件成立即為真,條件不即為假,也可以直接定義真假,true 和false 實際上是對 設置退出狀態碼為 只是bash 將這兩個命令內置了,使用不加引號的字符串 /bin/true /bin/flase``0``0``/bin/true``0``/bin/flase``1``true``false

如果有其它語言基礎(比如python)的話,直接帶入其它語言的就行了。沒有shell 解釋那麼複雜。用法是一樣的。

bool 一般作用於條件和循環,在後面的章節會講他們的用法。

/bin/true 執行結果

1
[email protected]:~$  foo=true; $foo; echo $?0

/bin/false 執行結果

1
[email protected]:~$ ber=false;$ber; echo $?1

條件

條件建立在變量之上,在程序運行中,我們可能會遇到不同的情況,而我們需要對這些不同進行處理以保證我們的程序能正常運行。

在沒有條件之前我們的程序就是一個流程,不懂變通。只會固定的做某事,就像閉著眼睛走路一樣,如果沒有紅綠燈那是沒有什麼問題的。而條件就是用來處理走路遇到紅綠燈這種情況,因為我們就算是走同一條線路會遇到紅綠燈不同的情況。

比如我們下載一個文件,如果文件存在於本地那我們是不是就可以不用下載了,這裡的存在與不存在就可以抽象為bool 的兩種狀態true 和false。在程序中的條件你可以理解為如果,以一個狀態來決定要不要做某事。

下面我將通過一個例子來為大家講解bool 和條件。

比如說: 我們去水果店賣橘子,水果店需要有橘子,那我們才能買到,如果沒有的話那就買不到了,這裡的有或沒有就可以看成bool 值,有的話我們是不是要買啊,這裡的買就可以寫入 要是有了橘子,那麼檸檬是不會買的, 條件只會從滿足的多個條件中調比較靠上的執行。如果我們要買的東西都沒有那麼還可以加一個默認操作 記得要在條件的最後加上有=true 没有=false``if``else``fi

那麼就基於水果店的這個例子寫代碼。

Bash:
1
#! /usr/bin/env bashorange=trueif $orangethen    echo "Selling oranges"fiecho "end"

這是只買橘子的情況,這裡判斷橘子是否存在,後面不用跟 當本個條件結束時使用 最後使用echo 輸出end 以方便你將 這段代碼只判斷了橘子是否存在,橘子不存在我們也不會進行其他的操作。orange=true``if $orange``then``else``else``then``fi``orange=true``orange=false

接下來演示,橘子不存在賣檸檬的操作。

Bash:
1
#! /usr/bin/env bashorange=falselemon=trueif $orangethen    echo "orange"elif $lemonthen    echo "lemon"fiecho "end"

這裡我們將orange 的變量改成了false ,這樣的話條件語句第一個 當然這裡我只寫你兩個判斷條件。你也可以寫更多在後面加 類似於這樣if``elif``elif``if``elif``elif

Bash:
1
#! /usr/bin/env bashorange=falselemon=flasefoo=trueif $orangethen    echo "orange"elif $lemonthen    echo "lemon"elif $foo #第二个 elif,当然你也可以加更多then    echo "foo"fiecho "end"

然後是橘子與檸檬都存在的情況,也就是多個條件滿足優先選擇靠前的的語句進行執行

Bash:
1
#! /usr/bin/env bashorange=truelemon=trueif $orangethen    echo "orange"elif $lemonthen    echo "lemon"fiecho "end"

這裡兩個條件都滿足,但是會優先選擇靠前的條件進行執行,以下是輸出。

1
$ ./test.shorangeend

還有一種情況是所有的條件都不滿足那麼程序相當於跳過了這個條件判斷,我們將上面的例子所有變量改成false 就會跳過這一條件語句塊

Bash:
1
#! /usr/bin/env bashorange=falselemon=falseif $orangethen    echo "orange"elif $lemonthen    echo "lemon"fiecho "end"

這段代碼會直接輸出 "end"

最後也就是條件中的 else

Bash:
1
#! /usr/bin/env bashorange=falselemon=falseif $orangethen    echo "orange"elif $lemonthen    echo "lemon"else    echo "emmm"fiecho "end"

這裡判斷中的所有操作條件都不滿足,所以執行了 用到的情況比較少,一般用在條件滿足就不用操作,而條件不滿足就需要操作的情況下。條件滿足或條件不滿足都要操作的情況一般放在條件語句塊自外。比如上面例子中的 就是放在條件語句塊外的。比如我們一個程序只能輸入一二三,結果用戶硬是要輸四五六。或者一些其他的數據。那麼這些不合格的數據我們直接在 在講循環的時候會再講一次。這次不懂的話沒關係。else``emm``else``echo "end"``else

比較操作

在條件中除了直接判斷變量的 因為用到操作符的機會比較少,這裡我就不講了,但要知道有這種操作。true``false

在比較時建議對變量加上雙引號,防止發生意想不到的轉義情況。

比較操作使用 bash``[]``[[]]

單中括號本質上是 bash``test``test

Bash:
1
#! /usr/bin/env bashnum=1if [ "$num" -eq 1 ]then    echo "true"fi

等價於

Bash:
1
#! /usr/bin/env bashnum=1if test "$num" -eq 1then    echo "true"fi

下面是 test

1
-eq`等於`-gt``-lt``-ge``-le

test 除了對字符或數字進行判斷還可以對文件系統進行判斷,這裡就不講的,感興趣的可以自己查詢資料。

相等判斷

先將相等判斷是因為這個判斷在比較操作中比較常用,例如:我們知道一等於一在shell中寫作 shell 使用 當然也可以替換為其他編程語言常用的 [ 1 -eq 1 ]``-eq``==

OK 上例子

Bash:
1
#! /usr/bin/env bashage=9if [ "$age" -eq 9 ]then    echo "age = 9"fiecho "end"

以下是輸出結果

1
$ ./test.shage = 9end

這裡我使用了一個變量與一個真值進行比較,因為shell 中不存在常量所以我一般都這這麼幹,變量也是可以替代常量的,大不了不修改它就是了。不過這裡演示的代碼比較少。所以我就這麼乾了。雖然使用了變量代替實際值,變量 age``9``age

不等判斷

既然有相等就會有不等判斷,將相等的 -eq``-ne``!=

Bash:
1
#! /usr/bin/env bashage=10if [ "$age" -eq 9 ]then    echo "age = 9"elif [ "$age" -ne 9 ]then    echo "age != 9"fiecho "end"

這裡我在上面的例子中將 age

1
$ ./test.shage != 9end

age 等於9麼,很明顯不等於,age不等於9麼,是的。所以執行了第二個語句,這裡我將等於放在前面是因為,等於出現的概率一般來說說沒有不等於出現的概率大,如果是多個判斷的話,作用域更廣的放前面會出現問題。

大小於判斷

一般來說,等於操作使用頻率要高於其他幾個判斷,不過這裡還是講一下。要知道有這麼一操作。

在shell 腳本中大於寫作-gt``>``-lt``<

ok 直接上例子

Bash:
1
#! /usr/bin/env bashread -p "My age? :  " -r ageif [ "$age" -eq 9 ]then    echo "猜对了"elif [ "$age" -lt 9 ]then    echo "猜小了"elif [ "$age" -gt 9 ]then    echo "猜大了"fiecho "end"

這裡我再次使用了 當用戶輸入小於9 是會提示用戶,輸大了。大於9 時提示用戶,輸大了。只有等於9 時才會提示輸入正確。read

循環

shell 有三種循環,for while until``until``until``while

for 循環

shell 的for 循環就是一個迭代操作,那麼什麼是迭代操作了,先上代碼再解釋

Bash:
1
#! /usr/bin/env bashfor i in $(seq 0 10)do    echo "$i"done

這裡使用了單括號語句 這裡執行了 然後使用for 的迭代操作(生成對少個for 就執行多少次直到迭代完為止,我感覺這個操作適合迭代數組,也就是類型中我沒有講的一個。為什麼不講是因為使用頻率不高,而且我將shell 當成膠水,直接用文件代替了數組。$()``seq``in``seq``i``seq

既然用到了 是新式的寫法更符合我們的操作習慣。$()``$()``\``$()

Bash:
1
#! /usr/bin/env bashecho "print `echo \$HOSTNAME`"# 这里的反斜杠,没有按预期的对 $ 进行转义

改用$() 後

Bash:
1
#! /usr/bin/env bashecho "print $(echo \$HOSTNAME)"# 对 $ 进行了转义结果与预期相同

for的另外一種就是作為計數器了。

Bash:
1
#! /usr/bin/env bashfor ((i=0; i<10; i++))do    echo "$i"done

這個代碼也是執行10 次,在for中初始 i``i++``i++``i + 1``i++

while 循環

while 循環使用的頻率應該是幾種循環當中最高的了,既然稱shell 為膠水語言,那麼鏈接兩個或多個程序,肯定是必要操作了,我一般將一個程序輸出到文件然後將文件一行一行的讀出來餵給其他程序。比如有一個軟件一次只能掃描一個IP,然而我們要掃描一個IP段這是我就會寫一個轉化程序,將IP段轉化為IP 然後再使用shell的while 讀出來調用它。這個不講,畢竟涉及了IP 。

先演示一下最常用的讀文件,這應該是最常用的循環用法了。

首先創建一個文件,方便我們後邊的演示echo -e "aa\nbb\ncc" > test.txt

Bash:
1
#! /usr/bin/env bashwhile read -r linedo    echo "$line"done < ./test.txt

這裡使用了 和 這裡我打印了一下就完事了。read``do``done

除了在循環的第一句判斷循環是否繼續我們也可以通過 break

如果說條件是做某事的話,那麼循環就是一直做某事。在循環中我們可以加入條件,在條件滿足時退出(就像我們上班一樣,時間到了就下班,或者說上課,時間到了就下課,這裡決定循環的條件就是時間。當然循環也有不用結束的時候也就是死循環,一般用在web服務,或者定時服務。在shell 中應該是定時服務多一點。應該也沒有人用shell 做web 吧。在使用死循環的時候,所處理的數據一定要是堵塞式的,不然腳本佔用資源會暴增,然後腳本崩潰。break

在上麵條件猜年齡的例子中,不管我們猜的對錯程序都會退出,這裡我們使用 如果你想提前退出請按 while``CTRL + c

Bash:
1
#! /usr/bin/env bash while true do    read -p "My age? :  " -r age    if [ "$age" -eq 9 ]    then        echo "猜对了"        break    elif [ "$age" -lt 9 ]    then        echo "猜小了"    elif [ "$age" -gt 9 ]    then        echo "猜大了"    fidoneecho "end"

在猜錯的情況下,這個循環會一直運行。因為是一個死循環。只有在猜對的情況下回執行 如果我們的程序字接受某幾個輸入,那麼這種方式是很有用的。break``while

Bash:
1
#! /usr/bin/env bash while true do    read -p "只能输入 1 或 2  :  " -r data    if [ "$data" -eq 1 ]    then        echo "你输入的是一"        break    elif [ "$data" -eq 2 ]    then        echo "你输入的是一"        break    else [ "$data" -lt 9 ]        echo "你输入的是 $data,只能输入 1 或 2"    fidoneecho "end"

死循環一定要有東西堵住,上面的兩個例子使用的 read

Bash:
1
#! /usr/bin/env bashfoo=0 while true do    sleep 1    ((foo++))    echo $foodone

這例子是一個死循環,使用的 sleep

管道與重定向

程序的核心概念就是輸入數據(input)處理數據(程序本身的邏輯代碼)到輸出數據(output)的過程。

輸入輸出數據的方式有很多種,比如讀取網絡數據或者讀取本地的文件,還有就是將其他程序的輸出做為當前程序的的輸入等等。與之對應的輸出也有很過種方法,具體採用哪一種方式來進行輸入和輸出就看實際需求了。

在我們編寫腳本的過程中有時候會用到管道與重定向。所以這一個概念我在這裡也講一下。

在我們使用終端的時候時候可可能最常用到的就是 這裡 grep``grep``grep``|

例如:

1
history | grep psql

使用上面的兩個命令查我們我在歷史中使用過的所有psql 命令因為psql 的參數太多了懶得重寫一遍,所以查找出來直接複製粘貼。當然你也可以試著查找一下你使用過的歷史命令。

當然不是所有的程序都支持管道輸入,支持管道輸入的前提是能從stdin 中讀取數據。

重定向

輸出重定向

輸出重定向有兩種方式,覆蓋> 和在尾部追加>> 具體使用方式看你的實際需求而定。

例如:

1
echo "hello" > hello.txt

我們將echo 的輸出重定向到hello.txt 這個文件現在我們可以打印它查看一下輸出內容 不出意外的話應該會輸出。cat hello.txt

1
[email protected]:~$ cat hello.txt [email protected]:~$

然後我們再來試一次看一下我們的輸出是不是被覆蓋了。

1
echo "foober" > hello.txt

不出意外的話應該會輸出

這裡我們用我們的新輸出覆蓋了原來的輸入,注意哦不是覆蓋一行是用我們的新輸出覆蓋我們的整個文件。如果你需要快速清空某個文件可以使用echo "" > file

然後另外一種方式就是追加了,追加顧名思義就是在已有內容的基礎上進行添加。

比如:

1
echo "Hello World!" >> hello.txt

現在我們查看hello.txt 的內容cat hello.txt 下面是輸出

1
2
3
4
5
[email protected]:~$ echo "Hello World!" >> hello.txt
[email protected]:~$ cat hello.txt
foober
Hello World!
[email protected]:~$

追加方式重定向會在文件的末尾新建一行並添加內容。

這裡說一下我的個人使用感受,在我們編寫腳本的過程中可能有很多數據需要處理,然後將這些處理都的數據供給下一個程序再次處理這種時候我一般會把處理過的數據放進一個文件下一個程序再進行讀取。

輸入重定向 具體看實際使用需求而定。 <``<<

首先來講從文件讀取的方式吧。

比如我們以一個文件包含很多內容我們需要把它讀取進我們的程序,我們可是使用輸入重定向的方式。不過一般還是使用其他方式從文件中讀數據不過這裡講到了還是演示一下。

然後我們使用輸出重定向從文件中讀取內容。

1
cat < hello.txt

不出意外的話你的屏幕上會打印Hello World! 我們上面的while 循環的例子中就使用了這種方式將我們要處理的文件重重定向進循環進行處理。

另外一種就是從終端中讀取內容並重定向的方式了,這種方式我感覺在寫演示的時候用的比較多一點,因為這樣我們就不用再終端和文件編輯直接換來換去了。

例如:

1
2
3
4
5
[email protected]:~$ cat >> hello.txt <<EOF
> say
> hello
> EOF
[email protected]:~$

這個例子中使用了輸出重定向和輸入重定向,先將輸入重定向,<< 後面更了一個字符串EOF 在最後也有一個EOF 字符串,其意思是兩個EOF 之間的內容重定向到前面的命令 然後將 cat``cat

管道 管道輸入的要求稍微苛刻一點,要管道前面的程序支持輸出,這個比較簡單,但是後面的程序支持從stdin 中讀取數據這個支持的比較少,看程序怎麼設計的,比如前面例子中使用的grep 就支持從stdin 中去取數據,簡單的說就是將多個程序串起來,就像之前舉的例子一樣。

篇外

到這裡不知道你發現沒有,我們所學的知識一層跌一層,如果前面的知識沒有了解,那麼後面的操作就無法進行,其實大多的後端語言都是這種立體式的,正因為一層壓一層我們才能更容易的在大腦裡產生一個立體的概念。

我個人覺得shell 語法是混亂的,怎麼寫都行,像一些現代的編程語言,寫起來語法就比較爽了,簡單直覺共通。在條件比對環節我稍微提了一下。shell 的比較操作符與其他語言的比較操作符的區別,是想告訴大家,這一部分並不通用。但是shell依然有它的價值在適合的地方,可以比其它編程語言少寫很多代碼。

這篇教程只是引導你了解shell 編程的最核心部分。在實際編寫中有不懂的地方建議google 一下。看看是目前的概念無法支撐你的程序,還是有其他的原因。如果你的程序中使用了很多概念是我沒有講的。那麼我建議你可以學更高級的編程語言了,比如python。因為再學習shell 的收益,我個人覺得沒有python 高。關於python 建議學python最新版。Python2 對於初學者來說已經沒有學習的價值了。

💘 相关文章

写一条评论

Based on Golang + fastHTTP + sdb | go1.16.4 Processed in 2ms