Linux 從 0 到 1(六) - Shell 基礎(chǔ)

shell 是英語“殼,外殼”的意思。你可以把它想象成嵌入在 Linux 這樣的操作系統(tǒng)中的一個(gè)“微型編程語言”。

寫一個(gè) shell 腳本:

  1. 創(chuàng)建腳本文件
vim test.sh

.sh,這已經(jīng)成為一種約定俗成的命名慣例了 ,其實(shí) Shell 腳本文件和普通的文本文件并沒有什么區(qū)別。我們給它加上 .sh 以強(qiáng)調(diào)這是一個(gè) Shell 腳本文件。我們大可以給這個(gè)文件起名叫 test (不帶 .sh 后綴)。

  1. 指定腳本要使用的Shell
#!/bin/bash

上面這句代碼中, /bin/bash 是 bash 程序在大多數(shù) Linux 系統(tǒng)中的存放路徑,而最前面的 #! 被稱作 Sha-bang,或者 Shebang。

在計(jì)算機(jī)科學(xué)中,Shebang(也稱為 Hashbang )是一個(gè)由井號(hào)和嘆號(hào)構(gòu)成的字符串行 #! ,其出現(xiàn)在文本文檔的第一行的前兩個(gè)字符。
在文檔中存在 Shebang 的情況下,類 Unix 操作系統(tǒng)的進(jìn)程載入器會(huì)分析 Shebang 后的內(nèi)容,將這些內(nèi)容作為解釋器指令,并調(diào)用該指令,并將載有 Shebang 的文檔路徑作為該解釋器的參數(shù)。

這一行( #!/bin/bash )其實(shí)并不是必不可少的,但是它可以保證此腳本會(huì)被我們指定的 Shell 執(zhí)行。

如果你沒有寫這一行,那么此腳本文件會(huì)被用戶當(dāng)前的 Shell 所執(zhí)行。這就可能產(chǎn)生問題:假如你的腳本是用 bash 的語法來寫的,而運(yùn)行這個(gè)腳本的用戶的 Shell 是 ksh,那么這個(gè)腳本就應(yīng)該不能正常運(yùn)行了。

  1. 運(yùn)行命令
#!/bin/bash

ls
  1. 注釋

Shell 腳本的注釋以 # (井號(hào))開頭。例如:

#!/bin/bash

# 列出目錄的文件
ls
  1. 運(yùn)行 shell 腳本

給腳本文件添加可執(zhí)行的權(quán)限

chmod +x test.sh

運(yùn)行:

./test.sh

以調(diào)試模式運(yùn)行

我們需要學(xué)習(xí)如何調(diào)試一個(gè)腳本程序。用法如下:

bash -x test.sh

我們直接調(diào)用 bash 這個(gè) Shell 程序,并且給它一個(gè)參數(shù) -x (表示以調(diào)試模式運(yùn)行),后面再跟上要調(diào)試運(yùn)行的腳本文件。

如此一來,Shell 就會(huì)把我們的腳本文件運(yùn)行時(shí)的細(xì)節(jié)打印出來了,在出現(xiàn)錯(cuò)誤時(shí)可以幫助我們排查問題所在。

創(chuàng)建屬于自己的命令

目前,我們的 Shell 腳本文件須要這樣運(yùn)行:

./test.sh

而且我們需要位于正確的目錄中。

那么其他的一些程序(命令其實(shí)都是程序),比如 git,pwd,ls,等等,為什么可以直接從不論哪個(gè)目錄執(zhí)行(不需要在前面加上 ./ 這樣的路徑)呢?

秘密就在于這些程序存放的目錄是在 PATH 這個(gè)環(huán)境變量中的。

PATH 是 Linux 的一個(gè)系統(tǒng)變量。這個(gè)變量包含了你系統(tǒng)里所有可以被直接執(zhí)行的程序的路徑。

如果我們?cè)诮K端輸入

echo $PATH

我們就可以看到目前自己系統(tǒng)里的那些“特殊”的目錄了。
例如我的情況:


每一個(gè)路徑之間是用冒號(hào)( : )來分隔的。

因此,只要你把 test.sh 這個(gè)文件拷貝到上述路徑列表的任意一個(gè)目錄(例如 /usr/local/bin ,/usr/bin,等等)中,你就可以在隨便什么目錄中運(yùn)行你的 Shell 腳本了。

test.sh

那么,現(xiàn)在就開始正式學(xué)習(xí) shell。

變量

就讓我們來定義一個(gè)變量吧。所有的變量都有一個(gè)名字和一個(gè)值。

message='Hello World'

注意:在等號(hào)兩邊不要加空格。

這樣我們就定義了一個(gè)變量,但他只是存在內(nèi)存中,如果我們想要他顯示則需要:

echo $message

如果想要使用轉(zhuǎn)義字符,則需要家 -e 參數(shù):

echo -e what \n $message

而且還需要注意引號(hào)的使用:

我們可以用引號(hào)來界定包含空格的字符串。

引號(hào)一共有三種:

類型 表示
單引號(hào) '
雙引號(hào) "
反引號(hào) `

根據(jù)引號(hào)類型不同,bash 的處理方式也會(huì)不同。

  • 單引號(hào)忽略被它括起來的所有特殊字符。
    比如:
echo 'The message is $message'

則,輸出

The message is $message
  • 雙引號(hào)忽略大多數(shù)特殊字符,但不包括:美元符號(hào)( $ )、反引號(hào)( ` )、反斜杠( \ ),這3種特殊字符將不被忽略。 不忽略美元符號(hào)意味著 Shell 在雙引號(hào)內(nèi)部可進(jìn)行變量名替換。
echo "The message is $message"

輸出:

The message is Hello World
  • 反引號(hào),會(huì)執(zhí)行變量(前提時(shí)變量時(shí)命令),如:
message=`pwd`
echo "You are in the directory $message"

輸出:

You are in the directory /home/exe

read : 請(qǐng)求輸入

我們可以請(qǐng)求用戶輸入文本,這就要用到 read 命令了。

read 命令讀取到的文本會(huì)立即被儲(chǔ)存在一個(gè)變量里。

read name
echo "Hello $name !"

read 可以同時(shí)給幾個(gè)變量賦值:

read firstname lastname
echo "Hello $firstname $lastname !"

-p :顯示提示信息,如:

read -p 'Please enter your name : ' name
echo "Hello $name !"

運(yùn)行以上腳本:



這下我們的程序就比較友好了,因?yàn)橛脩糁酪鍪裁础?/p>

-n :限制字符數(shù)目

read -p 'Please enter (5 characters max) : ' -n 5 name
echo "Hello $name !"

運(yùn)行這個(gè)腳本,我們發(fā)現(xiàn)一旦輸入的字符數(shù)達(dá)到了我們限定的 5 個(gè),那么 bash 會(huì)立即顯示 「 Hello + 我們輸入的字符 !」,都不需要我們按下回車鍵。

-t :限制輸入時(shí)間

用 -t 參數(shù),我們可以限定用戶的輸入時(shí)間(以秒為單位),也就是說超過這個(gè)時(shí)間,就不讀取輸入了。

-s :隱藏輸入內(nèi)容

如果你想要用戶輸入的是一個(gè)密碼,那 -s 參數(shù)還是有用的。

數(shù)學(xué)運(yùn)算

在 bash 中,所有的變量都是字符串,因此它也不會(huì)做運(yùn)算。但我們可以用 let 命令來讓他做計(jì)算:

let "a = 5"
let "b = 2"
let "c = a + b"

可用的運(yùn)算符是以下幾種:

運(yùn)算 符號(hào)
加法 +
減法 -
乘法 *
除法 /
冪( 乘方) **
余( 整數(shù)除法的余數(shù)) %

和其他大多數(shù)主流編程語言一樣,bash 也支持運(yùn)算的「連寫」:

let "a = a * 3"

環(huán)境變量

到目前為止,我們?cè)谀_本文件中創(chuàng)建的變量只存在于腳本中。換言之,在 A 腳本程序中定義的變量不能被 B 腳本程序使用。

我們來學(xué)習(xí)一個(gè)被稱為「環(huán)境變量」的特殊變量。顧名思義,Shell 的環(huán)境變量可以被此種 Shell 的任意腳本程序使用。我們有時(shí)也把環(huán)境變量稱為「全局變量」。

我們可以用 env 命令來顯示你目前所有的環(huán)境變量:

env

其中比較重要的幾個(gè)環(huán)境變量是:

  • SHELL :指明目前你使用的是哪種 Shell。我目前用的是 zsh。

  • PATH :是一系列路徑的集合。只要有可執(zhí)行程序位于任意一個(gè)存在于 PATH 中的路徑,那么我們就可以直接輸入可執(zhí)行程序的名字來執(zhí)行,而不需要加上所在路徑前綴或進(jìn)入到可執(zhí)行程序所在目錄去執(zhí)行。上一課我們已經(jīng)學(xué)習(xí)過 PATH 了。

  • HOME :你的家目錄所在的路徑。

  • PWD :目前所在的目錄

  • OLDPWD :你上次所在的目錄

可以看到,這些環(huán)境變量的名字都約定俗稱是大寫的。

如何使用這些環(huán)境變量呢?很簡(jiǎn)單,就和平時(shí)使用變量一樣:

#!/bin/bash

echo "Your default Shell is $SHELL"

有時(shí),我們需要自己定義環(huán)境變量。你可以用 export 命令來完成。在 .bashrc 或 .zshrc 這樣的 Shell 配置文件里可以找到這樣的命令:



如上圖,我就定義了一些環(huán)境變量,比如 NDK_CCACHE 的值是 ccache,CCACHE_DIR 的值是 ~/.ccache。

參數(shù)變量

就跟我們之前學(xué)過的各種 Linux 命令一樣,你的 Shell 腳本也可以接收參數(shù)。

假設(shè),我們可以這樣調(diào)用我們的腳本文件:

./variable.sh 參數(shù)1 參數(shù)2 參數(shù)3

這些個(gè) 參數(shù)1,參數(shù)2,參數(shù)3 被稱為「參數(shù)變量」。

但問題是我們還不知道如何接收這些參數(shù)到我們的腳本中。其實(shí)不難,因?yàn)檫@些變量是被自動(dòng)創(chuàng)建的。

$# :包含參數(shù)的數(shù)目。
$0 :包含被運(yùn)行的腳本的名稱 (我們的示例中就是 variable.sh )。
$1:包含第一個(gè)參數(shù)。
$2:包含第二個(gè)參數(shù)。
...
$8 :包含第八個(gè)參數(shù)。
...
依次類推。

echo "You have executed $0, there are $# parameters"
echo "The first parameter is $1"

如果我們有很多很多參數(shù)怎么辦呢?可以用 shift 命令來「挪移」參數(shù),以便依次處理。

#!/bin/bash

echo "The first parameter is $1"
shift
echo "The first parameter is now $1"

當(dāng)然了,shift 命令常被用在循環(huán)中,使得參數(shù)一個(gè)接一個(gè)地被處理。

數(shù)組

定義數(shù)組:

array=('value0' 'value1' 'value2')

訪問:

${array[2]}

我們也可以單獨(dú)給數(shù)組的元素賦值,例如:

array[3]='value3'

我們可以一次性顯示數(shù)組中所有的元素值,需要用到通配符 *(星號(hào))。

#!/bin/bash

array=('value0' 'value1' 'value2')
array[5]='value5'
echo ${array[*]}

條件語句

if

if [ 條件測(cè)試 ]
then 
    做這個(gè)
fi

注意:方括號(hào) [] 中的「條件測(cè)試」兩邊必須要空一格。不能寫成 [test],而要寫成[ test ]。

else,elif

if [ 條件測(cè)試 ]
then
    做這個(gè)
elif [ 條件測(cè)試 2 ]
then
    做 2 的事情
else
    做那個(gè)
fi

不同的測(cè)試類型

在 bash 中我們可以做三種測(cè)試:

  • 測(cè)試字符串
  • 測(cè)試數(shù)字
  • 測(cè)試文件
測(cè)試字符串
條件 意義
$string1 = $string2 兩個(gè)字符串是否相等。Shell 大小寫敏感,因此 A 和 a 是不一樣的。
$string1 != $string2 兩個(gè)字符串是否不同。
-z $string 字符串 string 是否為空。z是 zero 的首字母,是英語「零」的意思。
-n $string 字符串 string 是否不為空。n 是英語 not 的首字母,是英語「不」的意思。
測(cè)試數(shù)字
條件 意義
$num1 -eq $num2 兩個(gè)數(shù)字是否相等。和判斷字符串所用的符號(hào)( = )不一樣。eq 是 equal 的縮寫,是英語「等于」的意思。
$num1 -ne $num2 兩個(gè)數(shù)字是否不同。ne 是 not equal 的縮寫,是英語「不等于」的意思。
$num1 -lt $num2 數(shù)字 num1 是否小于 num2。lt 是 lower than 的縮寫,是英語「小于」的意思。
$num1 -le $num2 數(shù)字 num1 是否小于或等于 num2。le 是 lower or equal 的縮寫,是英語「小于或等于」的意思。
$num1 -gt $num2 數(shù)字 num1 是否大于 num2。gt 是 greater than 的縮寫,是英語「大于」的意思。
$num1 -ge $num2 數(shù)字 num1 是否大于或等于 num2。ge 是 greater or equal 的縮寫,是英語「大于或等于」的意思。
測(cè)試文件

相比于主流編程語言,Shell 的一大優(yōu)勢(shì)就是可以非常方便地測(cè)試文件:文件存在嗎?我們可以寫入文件嗎?這個(gè)文件比那個(gè)文件修改時(shí)間更早還是更晚?

條件 意義
-e $file 文件是否存在。e 是 exist 的首字母,表示「存在」。
-d $file 文件是否是一個(gè)目錄。因?yàn)?Linux 中所有都是文件,目錄也是文件的一種。d 是 directory 的首字母,表示「目錄」。
-f $file 文件是否是一個(gè)文件。f 是 file 的首字母,表示「文件」。
-L $file 文件是否是一個(gè)符號(hào)鏈接文件。L 是 link 的首字母,表示「鏈接」。
-r $file 文件是否可讀。r 是 readable 的首字母,表示「可讀的」。
-w $file 文件是否可寫。w 是 writable 的首字母,表示「可寫的」。
-x $file 文件是否可執(zhí)行。x 是 executable 的首字母,表示「可執(zhí)行的」。
$file1 -nt $file2 文件 file1 是否比 file2 更新。nt 是 newer than 的縮寫,表示「更新的」。
$file1 -ot $file2 文件 file1 是否比 file2 更舊。ot 是 older than 的縮寫,表示「更舊的」。

一次測(cè)試多個(gè)條件

在一個(gè)條件測(cè)試中,我們可以同時(shí)測(cè)試多個(gè)條件。需要用到兩種符號(hào):

符號(hào) 意義
&& 兩個(gè)&。表示「邏輯與」。此符號(hào)兩端的條件必須全為真,整個(gè)條件測(cè)試才為真;只要有一個(gè)不為真,整個(gè)條件測(cè)試為假。
II 兩個(gè)豎線。表示「邏輯或」。此符號(hào)兩端的條件只要有一個(gè)為真,整個(gè)條件測(cè)試就為真;只有兩個(gè)都為假,整個(gè)條件測(cè)試才為假。

反轉(zhuǎn)測(cè)試

我們可以用「否定」來反轉(zhuǎn)測(cè)試條件,要用到感嘆號(hào)( ! )。

來看一個(gè)例子:

#!/bin/bash

read -p 'Enter a file : ' file

if [ ! -e $file ]
then
    echo "$file does not exist"
else
    echo "$file exists"
fi

條件測(cè)試中我們寫了 「 ! -e $file 」,表示「如果文件 file 不存在」。

case : 測(cè)試多個(gè)條件

相當(dāng)與其他語言的 switch:

#!/bin/bash

case $1 in
    "Matthew")
        echo "Hello Matthew !"
        ;;
    "Mark")
        echo "Hello Mark !"
        ;;
    "Luke")
        echo "Hello Luke !"
        ;;
    "John")
        echo "Hello John !"
        ;;
    *)
        echo "Sorry, I do not know you."
        ;;
esac

來分析一下上面的程序。因?yàn)橛泻芏嘈碌膬?nèi)容:

  • case $1 in :$1 表示我們要測(cè)試的變量是輸入的第一個(gè)參數(shù)。in 是英語「在...之中」的意思。

  • "Matthew") :測(cè)試其中一個(gè) case,也就是 $1 是否等于 "Matthew"。當(dāng)然,我們也可以用星號(hào)來做通配符來匹配多個(gè)字符,例如 "M*") 可以匹配所有以 M 開頭的字符串。

  • ;; :類似于主流編程語言中的 「 break; 」,表示結(jié)束 case 的讀取,程序跳轉(zhuǎn)到 esac 后面執(zhí)行。

  • *) :相當(dāng)于 if 條件語句的 else,表示「否則」,就是「假如不等于上面任何一種情況」。

  • esac :是 case 的反寫,表示 case 語句的結(jié)束。

循環(huán)

Shell 中,主要的循環(huán)語句有三種:while 循環(huán),until 循環(huán) 和 for 循環(huán)。

while 循環(huán)

while 循環(huán)的邏輯是這樣的:

while [ 條件測(cè)試 ]
do
    做某些事
done

until 循環(huán)

與 while 這個(gè)關(guān)鍵字相反的有一個(gè) until 關(guān)鍵字,until 在英語中是 「直到」的意思。

它也可以實(shí)現(xiàn)循環(huán),只不過邏輯和 while 循環(huán)正好相反。

#!/bin/bash

until [ "$response" = 'yes' ]
do
    read -p 'Say yes : ' response
done

for 循環(huán)

Shell 的 for 循環(huán)和主流的程序語言的循環(huán)略有不同。

  1. 遍歷列表
    for 循環(huán)可以遍歷一個(gè)「取值列表」,基本的邏輯如下:
for 變量 in '值1' '值2' '值3' ... '值n'
do
    做某些事
done

如:

#!/bin/bash

for animal in 'dog' 'cat' 'pig'
do
    echo "Animal being analyzed : $animal" 
done

for 循環(huán)的取值列表不一定要在代碼中定義好,我們也可以用一個(gè)變量,如下例:

#!/bin/bash

listfile=`ls`

for file in $listfile
do
    echo "File found : $file" 
done

我們還可以簡(jiǎn)化上面的程序,不需要用到 listfile 這個(gè)變量:

#!/bin/bash

for file in `ls`
do
    echo "File found : $file" 
done

我們可以再改進(jìn)這個(gè)程序,讓它復(fù)制當(dāng)前目錄下的文件,并且把每個(gè)副本的名字修改為「現(xiàn)有名字 + -copy」 (copy 是英語「拷貝」的意思):

#!/bin/bash

for file in `ls`
do
    cp $file $file-copy
done

常規(guī)點(diǎn)的 for 循環(huán):

#!/bin/bash

for i in `seq 1 10`
do
    echo $i
done

以上程序中,「 seq 1 10 」會(huì)返回一個(gè)取值列表,是從 1 到 10 的整數(shù)值。因此,echo 就會(huì)遍歷輸出 1 到 10 這 10 個(gè)整數(shù)。

Shell 函數(shù)

定義(或創(chuàng)建) Shell 函數(shù)是非常容易的。有兩種方式:

函數(shù)名 () {
    函數(shù)體
}

function 函數(shù)名 {
    函數(shù)體
}

注意的地方:

  • 函數(shù)名后面跟著的圓括號(hào)里不加任何參數(shù):這一點(diǎn)與主流編程語言很不相同。

  • 函數(shù)的完整定義必須置于函數(shù)的調(diào)用之前。

傳遞參數(shù)

在 Shell 函數(shù)中,我們給它傳遞參數(shù)的方式其實(shí)很像給 Shell 腳本傳遞命令行參數(shù)。我們把參數(shù)直接置于函數(shù)名字后面,然后就像我們之前 Shell 腳本的參數(shù)那樣:$1, $2, $3, 等等。

#!/bin/bash

print_something () {
    echo Hello $1
}

# 這里調(diào)用函數(shù)時(shí)傳遞了參數(shù)
print_something Luke
print_something John

返回值

大多數(shù)主流編程語言都有函數(shù)返回值的概念,可以讓函數(shù)回傳一些數(shù)據(jù)。

Shell 的函數(shù)卻沒辦法做到。但是 Shell 的函數(shù)可以返回一個(gè)狀態(tài),有點(diǎn)類似一個(gè)程序或命令退出時(shí)會(huì)有一個(gè)退出狀態(tài),表明是否成功。

Shell 函數(shù)要返回狀態(tài),也用 return 這個(gè)關(guān)鍵字。

#!/bin/bash

print_something () {
    echo Hello $1
    return 1
}

print_something Luke
print_something John
echo Return value of previous function is $?

返回的狀態(tài)不一定要是被硬編碼的(比如上例中的 1 ),也可以是一個(gè)變量。

變量 $? 包含前一次被運(yùn)行的命令或函數(shù)的返回狀態(tài)。

運(yùn)行結(jié)果:



一般來說,返回狀態(tài) 0 表示一切順利;一個(gè)非零值表示有錯(cuò)誤。

變量作用范圍

變量的作用范圍意味著一個(gè) Shell 腳本的哪些部分可以訪問到這個(gè)變量。

默認(rèn)來說,一個(gè)變量是全局的(global),意味著在腳本的任何地方都可以訪問它。

我們也可以創(chuàng)建局部(local)變量。當(dāng)我們?cè)诤瘮?shù)中創(chuàng)建局部變量時(shí),這個(gè)變量就只能在這個(gè)函數(shù)中被訪問。

要定義一個(gè)局部變量,我們只要在第一次給這個(gè)變量賦值時(shí)在變量名前加上關(guān)鍵字 local 即可( local 是英語「 本地的」的意思)。

定義局部變量有一個(gè)好處,就是可以防止被腳本的其他地方的代碼意外改變數(shù)值。

重載命令

我們可以用函數(shù)來實(shí)現(xiàn)命令的重載,也就是說把函數(shù)的名字取成與我們通常在命令行用的命令相同的名字。

例如,也許我們每次在腳本中調(diào)用 ls 命令時(shí),其實(shí)是想要實(shí)現(xiàn) ls -lh 的效果。那么我們可以這么做:

#!/bin/bash

ls () {
    command ls -lh
}

ls

如果沒有 command 這個(gè)關(guān)鍵字,那么程序會(huì)陷入無限循環(huán)。

如果你不小心忘了 command 關(guān)鍵字而陷入無限循環(huán),可以用 Ctrl + c 的組合快捷鍵來停止程序。

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

相關(guān)閱讀更多精彩內(nèi)容

  • 中國(guó)人口的老齡化正在愈演愈烈,不得不說,中國(guó)已經(jīng)進(jìn)入了一個(gè)老齡化的社會(huì)。 “關(guān)愛老人健康”是當(dāng)今社會(huì)永恒的主題。 ...
    順?biāo)优?/span>閱讀 474評(píng)論 0 0
  • 小時(shí)候逢年過節(jié),家里是一定會(huì)做臘肉的,用放養(yǎng)的肥豬肉灌起來,用大量的粗鹽腌制。風(fēng)干以后是半紅半白的樣子,一半瘦肉,...
    鄭小蒙閱讀 428評(píng)論 0 0
  • 你聽他的? 加了幾個(gè)群,聊感情,聊生活,聊工作聊學(xué)習(xí) 群里的小伙伴們也很是熱心 每逢有誰有了困難都會(huì)幫著出謀劃策 ...
    萌阿瑪閱讀 419評(píng)論 1 2
  • 現(xiàn)在的我們究竟在堅(jiān)持什么? 流逝的日子像一片片凋落的枯葉與花瓣,漸去漸遠(yuǎn)的是青春的純情與浪漫。不記得曾有多少...
    淺澤野子閱讀 661評(píng)論 2 0

友情鏈接更多精彩內(nèi)容