Bash腳本編程學習筆記04:測試命令test、狀態返回值、位置參數和特殊變量

我自己接觸Linux主要是大學學習的Turbolinux --> 根據《鳥哥的Linux私房菜:基礎篇》(第三版) --> 馬哥的就業班課程。給我的感覺是這些課程對於bash的講解,理論上是不夠的,但是限於時間、篇幅和精力,確實無法講解的足夠深入。在接觸了駿馬金龍的博客以及bash官方站點後,就會理解駿馬兄說的“平常我們學的只是bash的形,而不是bash的神”。最近在寫這個系列的學習筆記,經常查閱bash官方手冊,真的是有種醍醐灌頂的感覺,但是限於能力和進度問題,有些問題暫且無法做到深入理解,只能給出治標的方法。

學習test命令以及後續的選擇語句if和case,需要注意的手冊有這些:

組合命令之條件結構體:[[ ... ]]

字符串匹配時使用到的模式匹配

bash特性之條件表達式

Bourne Shell的內置命令test及其常用格式[ .. ]

字符串匹配時,使用擴展的glob或者忽略大小寫匹配,需要了解的shell選項:extglob,nocaseglob,nocasematch等。

test簡介

測試命令test用於形成一個表達式,結合條件判斷語句if-else來判斷。

例如可以判斷某個文件是否存在,是否具備什麼樣的特性(可讀嗎?可寫嗎?可執行嗎?塊文件嗎?)等等。

測試命令test有三種語法格式:

test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]

前兩種是等價的,應該是沒有區別的。注意EXPRESSION兩邊與中括號之間需要有空格。

雙中括號與前兩者的區別,主要在於表達式中的操作符如果是這種情況的時候。

[[ string1 > string2 ]]
[[ string1 < string2 ]]

字符串之間比較比較大小,其實比的是在詞典中字符的先後順序。[]應該是基於ASCII來比較,而[[]]應該是基於shell當前的地理位置設置來比較(應該與locale相關的環境變量有關吧)。

更多的信息,可能需要大家去直接看bash的手冊吧,我稍微看了下,是真的很難以理解,所以暫時放棄了,只找到了這些簡單的區別,按照駿馬兄的話,咱只能先學習bash的形了,直接跳着看bash手冊會有一種被勸退的感覺。

測試表達式的結果爲真或者假,真返回0,假返回1。這個返回值不會顯示在終端上,而是保存在shell的特殊變量“$?”中(bash原文中提到的是特殊參數(parameter),變量是一個通過名稱表示的參數)。

因此我們只需要在每次測試後echo這個特殊變量的值就可以驗證了。

# [ EXPRESSION ]
# echo $?

 

test實戰

數值比較

-eq:equal,是否相等。

[root@c7-server ~]# age=28
[root@c7-server ~]# [ $age -eq 28 ]
[root@c7-server ~]# echo $?
0

-ne:not equal,是否不相等。

[root@c7-server ~]# [ $age -ne 28 ]
[root@c7-server ~]# echo $?
1

-gt:greater than,是否大於。

[root@c7-server ~]# [ $age -gt 28 ]
[root@c7-server ~]# echo $?
1

-ge:greater equal,是否大於等於。

[root@c7-server ~]# [ $age -ge 28 ]
[root@c7-server ~]# echo $?
0

-lt:less than,是否小於。

[root@c7-server ~]# [ $age -lt 28 ]
[root@c7-server ~]# echo $?
1

-le:less equal,是否小於等於。

[root@c7-server ~]# [ $age -le 28 ]
[root@c7-server ~]# echo $?
0

字符串比較

==:是否等於。字符串等值比較,使用一個=符號也是可以。

[root@c7-server ~]# name=alongdidi
[root@c7-server ~]# [ $name == alongdidi ] [root@c7-server ~]# echo $? 0

!=:是否不等於。

[root@c7-server ~]# [ $name != alongdidi ]
[root@c7-server ~]# echo $?
1

=~:基於regex3的擴展正則匹配,並且使用這個操作符的時候必須使用[[]]。

[root@c7-server ~]# [[ $name =~ along(di){1} ]]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ $name =~ along(di){1} ]
-bash: syntax error near unexpected token `('
[root@c7-server ~]# [ $name =~ along\(di\){1} ]
-bash: [: =~: binary operator expected

-z:判斷字符串是否爲空。

[root@c7-server ~]# [ -z $name ]
[root@c7-server ~]# echo $?
1

-n:判斷字符串是否不空。

[root@c7-server ~]# [ -n $name ]
[root@c7-server ~]# echo $?
0

在做字符串的等值比較時,無論是==、!=或者=~,它們都是二元(binary)的運算符,也就是在這個符號的左右兩邊都必須存在字符串。

如果其中一邊爲空的話,就會報錯。

[root@c7-server ~]# unset name
[root@c7-server ~]# [ $name == tom ]
-bash: [: ==: unary operator expected

因爲$name爲空,因此整個表達式就變成了。

[ == tom ]

所以bash會報錯並告訴你我們期待的是一元(unary)運算符(因爲只有tom這個字符串)。一元也可以叫單目,是一樣的意思。

解決的辦法有三個,對$name使用雙引號或者單引號包裹或者將[]換成[[]]。

[root@c7-server ~]# [ "$name" == tom ]
[root@c7-server ~]# echo $?
1
[root@c7-server ~]# [ '$name' == tom ]
[root@c7-server ~]# echo $?
1
[root@c7-server ~]# [[ $name == tom ]]
[root@c7-server ~]# echo $?
1

文件測試

存在性

-a FILE或者-e FILE:判斷文件是否存在。

[root@c7-server ~]# [ -a /etc/passwd ]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ -e /etc/passwd ]
[root@c7-server ~]# echo $?
0

文件類型

-b FILE:文件是否存在且爲塊設備文件。

[root@c7-server ~]# ls -l /dev/sda1
brw-rw---- 1 root disk 8, 1 Jan  2 13:51 /dev/sda1
[root@c7-server ~]# [ -b /dev/sda1 ]
[root@c7-server ~]# echo $?
0

-c FILE:文件是否存在且爲字符設備文件。

[root@c7-server ~]# ls -l /dev/autofs 
crw------- 1 root root 10, 235 Jan  2 13:51 /dev/autofs
[root@c7-server ~]# [ -c /dev/autofs ]
[root@c7-server ~]# echo $?
0

-d FILE:文件是否存在且爲目錄文件。

[root@c7-server ~]# ls -ld /root
dr-xr-x---. 16 root root 4096 Jan  7 09:54 /root
[root@c7-server ~]# [ -d /root ]
[root@c7-server ~]# echo $?
0

-f FILE:文件是否存在且爲普通文件(即文本文件)。

[root@c7-server ~]# ls -l /etc/passwd
-rw-r--r-- 1 root root 2296 Nov 11 14:28 /etc/passwd
[root@c7-server ~]# file /etc/passwd
/etc/passwd: ASCII text
[root@c7-server ~]# [ -f /etc/passwd ]
[root@c7-server ~]# echo $?
0

-h FILE:文件是否存在且爲字符鏈接文件,即軟連接、符號鏈接。

-L FILE:同上。

[root@c7-server ~]# ls -l /etc/rc.local 
lrwxrwxrwx. 1 root root 13 Oct 17 14:59 /etc/rc.local -> rc.d/rc.local
[root@c7-server ~]# [ -h /etc/rc.local ]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ -L /etc/rc.local ]
[root@c7-server ~]# echo $?
0

-p FILE:文件是否存在且爲命名管道文件。

[root@c7-server ~]# ls -l /run/dmeventd-client 
prw------- 1 root root 0 Jan  7 09:54 /run/dmeventd-client
[root@c7-server ~]# [ -p /run/dmeventd-client ]
[root@c7-server ~]# echo $?
0

-S FILE:文件是否存在且爲套接字文件。

[root@c7-server ~]# ls -l /run/systemd/shutdownd
srw------- 1 root root 0 Jan  7 09:54 /run/systemd/shutdownd
[root@c7-server ~]# file /run/systemd/shutdownd
/run/systemd/shutdownd: socket
[root@c7-server ~]# [ -S /run/systemd/shutdownd ]
[root@c7-server ~]# echo $?
0

文件權限

-r FILE:文件是否存在且對當前用戶可讀。

-w FILE:文件是否存在且對當前用戶可寫。

-x FILE:文件是否存在且對當前用戶可執行。

[root@c7-server ~]# ls -l /etc/passwd
-rw-r--r-- 1 root root 2296 Nov 11 14:28 /etc/passwd
[root@c7-server ~]# [ -r /etc/passwd ]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ -w /etc/passwd ]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ -x /etc/passwd ]
[root@c7-server ~]# echo $?
1

特殊文件權限

-u FILE:文件是否存在且具有SUID權限。

[root@c7-server ~]# ls -l /usr/bin/passwd
-rwsr-xr-x. 1 root root 27832 Jun 10  2014 /usr/bin/passwd
[root@c7-server ~]# [ -u /usr/bin/passwd ]
[root@c7-server ~]# echo $?
0

-g FILE:文件是否存在且具有SGID權限。

[root@c7-server ~]# ls -l /usr/bin/wall
-r-xr-sr-x. 1 root tty 15344 Jun 10  2014 /usr/bin/wall
[root@c7-server ~]# [ -g /usr/bin/wall ]
[root@c7-server ~]# echo $?
0

-k FILE:文件是否存在且具有STICKY權限。

[root@c7-server ~]# ls -ld /tmp/
drwxrwxrwt. 11 root root 4096 Jan  7 14:21 /tmp/
[root@c7-server ~]# [ -k /tmp/ ]
[root@c7-server ~]# echo $?
0

文件是否有內容測試

-s FILE:文件是否存在且有內容。

[root@c7-server ~]# ls -l test.txt
ls: cannot access test.txt: No such file or directory
[root@c7-server ~]# touch test.txt
[root@c7-server ~]# [ -s test.txt ]
[root@c7-server ~]# echo $?
1
[root@c7-server ~]# [ -s /etc/passwd ]
[root@c7-server ~]# echo $?
0

時間戳測試

-N FILE:文件自身從上一次讀操作後是否被修改過。

[root@c7-server ~]# cat test.txt
[root@c7-server ~]# [ -N test.txt ]
[root@c7-server ~]# echo $?
1
[root@c7-server ~]# echo "alongdidi" > test.txt 
[root@c7-server ~]# [ -N test.txt ]
[root@c7-server ~]# echo $?
0

從屬關係測試

-O FILE:當前用戶是否爲文件的屬主。

-G FILE:當前用戶是否屬於文件的屬組。

[root@c7-server ~]# ls -ld /home/zwl/
drwx------. 3 zwl zwl 78 Apr 11  2018 /home/zwl/
[root@c7-server ~]# [ -O /home/zwl/ ]
[root@c7-server ~]# echo $?
1
[root@c7-server ~]# [ -G /home/zwl/ ]
[root@c7-server ~]# echo $?
1

雙目測試

FILE1 -ef FILE2:FILE1和FILE2是否指向同一個文件系統的相同inode的硬鏈接。

[root@c7-server ~]# ln test.txt test.hard
[root@c7-server ~]# ls -li test.txt test.hard 
33731123 -rw-r--r-- 2 root root 10 Jan  7 14:31 test.hard
33731123 -rw-r--r-- 2 root root 10 Jan  7 14:31 test.txt
[root@c7-server ~]# [ test.txt -ef test.hard ]
[root@c7-server ~]# echo $?
0

FILE1 -nt FILE2:FILE1是否新於FILE2,根據文件的mtime。

FILE1 -ot FILE2:FILE1是否舊於FILE2,根據文件的mtime。

[root@c7-server ~]# [ test.txt -nt /etc/passwd ]
[root@c7-server ~]# echo $?
0
[root@c7-server ~]# [ test.txt -ot /etc/passwd ]
[root@c7-server ~]# echo $?
1

組合測試條件

即與或非。

[ EXP1 -a EXP2 ]:EXP1和EXP2必須都爲true,結果才爲true。

[ EXP1 -o EXP2 ]:只要EXP1和EXP2當中有一個爲true,結果就爲true。

[ ! EXP ]:當EXP爲true的時候,結果爲false;當EXP爲false的時候,結果爲true。

練習

如果主機名爲空或者包含local字符串,則將主機名設置爲www.alongdidi.com。

hostName=$(hostname)
[ -z "${hostName}" -o ${hostName}=~"local" ] && hostname www.magedu.com

注:實際我在CentOS 7,Bash 4.2.46場景下執行該命令,得到的結果不太對。主要問題出在“=~”的判斷上。暫時未知如何解決,這裏大概知道下思路即可。

 

命令/腳本狀態返回值

上文中我們介紹了特殊變量$?,它存儲了測試表達式的測試結果。true=0,false=1。

命令執行的結果也會有這麼一個返回值(也可以叫退出狀態碼),一般返回值0表示命令執行成功,返回值非0(多數情況下是1)則表示失敗。

PS:這裏也需要注意,大多數編程語言使用1來表示成功/true等。還有大家也要注意和命令執行後的標準輸出或者標準錯誤輸出區別開。一個表示命令執行成功與否的結果,另一個則是命令執行的輸出結果。

這個返回值在我們執行腳本的時候,也會返回。默認腳本執行的返回值使用的是腳本中最後一條命令的返回值。

如果腳本中前幾條命令的執行均成功了,但是最後一條執行失敗了,那麼整個腳本的$?也是非0的。

我們可以通過exit命令來手工配置退出狀態碼。bash遇到exit會立即退出當前的shell並將返回值存入父shell的$?變量中。因此可以用來立即退出bash腳本。

[root@c7-server ~]# bash
[root@c7-server ~]# exit 10
exit
[root@c7-server ~]# echo $?
10

PS:有的時候退出狀態碼會異常,可能和返回值的取值有關係。

[root@c7-server ~]# exit 1000
exit
[root@c7-server ~]# echo $?
232
[root@c7-server ~]# bash
[root@c7-server ~]# exit 1024
exit
[root@c7-server ~]# echo $?
0

自定義返回值一般用於bash腳本中的判斷。比如,當某個文件不存在的時候,立即執行exit 5。

腳本會立刻退出,5這個返回值會被返回。一般程序員會事先定義好不同的返回值表達的不同含義,並將其寫入文檔。

用戶根據返回值和該文檔來判斷腳本爲什麼中斷執行了。

像rsync的man手冊中就有定義。

0      Success
1      Syntax or usage error
2      Protocol incompatibility
3      Errors selecting input/output files, dirs
...

 

腳本中的參數

在學習C語言的時候,我們可以向函數執行傳遞參數的操作。bash腳本編程也是可以的。

可以對腳本進行傳參,也可以對函數。

在引用參數的時候,具體是引用腳本的參數還是函數的參數則取決於引用的位置。

按照馬哥課程的進度,函數還未學習到,因此這裏就不說了。(雖然已經有bash編程的基礎)等到寫bash函數的博文時,會在提及。

向腳本傳參和腳本中引用參數十分簡單,示例如下。

[root@c7-server ~]# cat test.sh 
#!/bin/bash
echo $1
echo $2
[root@c7-server ~]# bash test.sh along didi
along
didi

在執行腳本時,腳本名稱後面的字符串就是參數,多個參數之間以空格分離,根據參數出現在腳本名稱後的位置,在腳本中使用$1、$2、$3...來引用,它們也被稱作位置參數。

shift not shit

如果我們想要改變位置參數的位置,就需要使用到shift內置命令。

shift [n]

shift的本意是移動,我們可以理解爲拿掉位置參數最左邊的n個。默認是1個。

[root@c7-server ~]# cat test.sh 
#!/bin/bash
echo $*
shift 2
echo $*
[root@c7-server ~]# bash test.sh a long di di
a long di di
di di

其他特殊變量

$#:獲取腳本被傳遞的參數的個數。

[root@c7-server ~]# cat test.sh 
#!/bin/bash
echo $#
[root@c7-server ~]# bash test.sh a l on g did i
6

$0:獲取腳本的名稱,這個名稱是執行時的名稱。執行的方式不同,值也不同。

[root@c7-server ~]# cat test.sh 
#!/bin/bash
echo $0
[root@c7-server ~]# bash test.sh 
test.sh
[root@c7-server ~]# /root/test.sh 
/root/test.sh
[root@c7-server ~]# ./test.sh 
./test.sh

只想獲取腳本的名稱的話,可結合basename命令。

[root@c7-server ~]# basename $(bash test.sh)
test.sh
[root@c7-server ~]# basename $(/root/test.sh)
test.sh
[root@c7-server ~]# basename $(./test.sh)
test.sh

$*:引用所有的參數。在雙引號的情況下,所有參數整合作爲一個整體。

$@:引用所有的參數。無論是否有雙引號,每個參數自身都作爲一個整體。

[root@c7-server ~]# cat test.sh 
#!/bin/bash
echo $*
echo $@
[root@c7-server ~]# bash test.sh a long di di
a long di di
a long di di

區別的話,可以看這個例子。

[root@c7-server ~]# cat test.sh
#!/bin/bash
for i in $*; do echo $i; done
for i in $@; do echo $i; done
echo "I am cut-off line"
for i in "$*"; do echo $i; done
for i in "$@"; do echo $i; done
[root@c7-server ~]# bash test.sh a long di di
a
long
di
di
a
long
di
di
I am cut-off line
a long di di
a
long
di
di

更深度的解釋,查閱官方文檔的話,需要對bash的單詞分割(word splitting)和IFS變量有理解才行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章