天天看點

跟老男孩學Linux運維:Shell程式設計實戰3.3 普通變量

3.3 普通變量

3.3.1 定義本地變量

本地變量在使用者目前shell生存期的腳本中使用。例如,本地變量oldboy的取值為bingbing,這個值隻在使用者目前shell生存期中有意義。如果在shell中啟動另一個程序或退出,那麼變量oldboy的值将會無效。

1.?普通變量定義

為普通變量的定義指派,一般有以下3種寫法:

變量名=value #<==指派時不加引号

變量名='value' #<==指派時加單引号

變量名="value" #<==指派時加雙引号

2.?在shell中定義變量名及為變量内容指派的要求

變量名一般是由字母、數字、下劃線組成的,可以以字母或下劃線開頭,例如:oldboy、oldboy123、oldboy_training。

變量的内容可以用單引号或雙引号引起來,也可不加引号,但是這三者的含義是不同的,具體參見後文說明。

3.?普通變量的定義及輸出的示例

範例3-4:采用不同的方式對普通變量進行定義,并一一列印輸出。

a=192.168.1.2

b='192.168.1.2'

c="192.168.1.2"

echo "a=$a"

echo "b=$b"

echo "c=${c}"

提示:

1)$變量名表示輸出變量,可以用$c和${c}兩種用法。

2)請在指令行實踐以上内容,然後看一看傳回的結果有何不同。

思考:在指令行shell下輸入以上内容後會輸出什麼結果呢?請在看答案之前,先想一想上面a、b、c變量值的輸出各是什麼,最好自己實踐一下。

答案:

b=192.168.1.2

c=192.168.1.2

可見,将連續的普通字元串的内容指派給變量,不管用不用引号,或者不管用什麼引号,它的内容是什麼,列印變量時就會輸出什麼。

範例3-5:接着上述範例的結果,再在linux指令行下繼續輸入如下内容,想一想a、b、c的輸出又各是什麼結果?

a=192.168.1.2-$a

b='192.168.1.2-$a'

c="192.168.1.2-$a"

提示: 建議先思考結果是什麼,然後再在指令行實踐以上内容,看看和思考的結果有何不同。

參考答案:

a=192.168.1.2-192.168.1.2

b=192.168.1.2-$a

c=192.168.1.2-192.168.1.2-192.168.1.2

4.?變量定義的基本技巧總結

這裡以範例3-5為例:

第一種定義a變量的方式是不加任何引号直接定義變量的内容,當内容為簡單連續的數字、字元串、路徑名時,可以這樣用,例如:a=1,b=oldboy等。不加引号時,值裡有變量的會被解析後再輸出,上述變量定義中因為$a的值被解析為192.168.1.2(範例3-3執行的影響),是以新的a值就是192.168.1.2-192.168.1.2。

第二種定義b變量的方式是通過單引号定義。這種定義方式的特點是:輸出變量内容時單引号裡是什麼就輸出什麼,即使内容中有變量和指令(指令需要反引起來)也會把它們原樣輸出。這種方式比較适合于定義顯示純字元串的情況,即不希望解析變量、指令等的場景,是以,對于這裡的b的值,定義時看到的是什麼就輸出什麼,即192.168.1.2-$a。

第三種定義c變量的方式是通過雙引号定義變量。這種定義方式的特點是:輸出變量内容時引号裡的變量及指令會經過解析後再輸出内容,而不是把雙引号中的變量名及指令(指令需要反引起來)原樣輸出。這種方式比較适合于字元串中附帶有變量及指令且想将其解析後再輸出的變量定義。

老男孩經驗:

數字内容的變量定義可以不加引号,其他沒有特别要求的字元串等定義最好都加上雙引号,如果真的需要原樣輸出就加單引号,定義變量加雙引号是最常見的使用場景。

5.?把一個指令的結果作為變量的内容指派的方法

對需要擷取指令結果的變量内容指派的常見方法有兩種:

變量名=`ls` #<==把指令用反引号引起來,不推薦使用這種方法,因為容易和單引号混淆

變量名=$(ls) #<==把指令用$()括起來,推薦使用這種方法

範例3-6:用兩種方法把指令的結果指派給變量。

[oldboy@oldboy ~]$ ls

test.sh

[oldboy@oldboy ~]$ cmd=`ls` #<==其中``為鍵盤上tab鍵上面的那個鍵輸出的字元

[oldboy@oldboy ~]$ echo $cmd

[oldboy@oldboy ~]$ cmd1=$(pwd)

[oldboy@oldboy ~]$ echo $cmd1

/home/oldboy #<==列印目前使用者所在的目錄

提示: 生産場景中把指令的結果作為變量的内容進行指派的方法在腳本開發時很

常見。

範例3-7:按天打包網站的站點目錄程式,生成不同的檔案名(此為企業實戰案例)。

[root@oldboy scripts]# cmd=$(date +%f) #<==将目前日期(格式為2016-09-10)指派

   ??給cmd變量

[root@oldboy scripts]# echo $cmd #<==輸出變量的值

2016-09-10

[root@oldboy scripts]# echo $(date +%f).tar.gz #<==直接輸出時間指令的結果

2016-09-10.tar.gz

[root@oldboy scripts]# echo `date +%f`.tar.gz

[root@oldboy scripts]# tar zcf etc_$(date +%f).tar.gz /etc

#<==将時間作為壓縮包名打包

tar: 從成員名中删除開頭的“/”

tar: 從硬連接配接目标中删除開頭的“/”

[root@oldboy scripts]# ls -l etc_2016-09-10.tar.gz  #<==打包結果,包名中包含

                                                              ?有目前日期

-rw-r--r-- 1 root root 9700163 9月  10 18:39 etc_2016-09-10.tar.gz

[root@oldboy scripts]# h=$(uname -n) #<==擷取主機名并指派給h變量

[root@oldboy scripts]# echo $h

oldboy

[root@oldboy scripts]# tar zcf $h.tar.gz /etc/services  ?#<==将主機名作為壓縮包名

                                                          ?打封包件

[root@oldboy scripts]# ls -l oldboy.tar.gz #<==打包結果,包名中包含有主機名

-rw-r--r-- 1 root root 127303 9月  10 18:40 oldboy.tar.gz

局部(普通)變量定義及指派的經驗小結

正常普通變量定義:

若變量内容為連續的數字或字元串,指派時,變量内容兩邊可以不加引号,例如a=123。

變量的内容很多時,如果有空格且希望解析内容中的變量,就加雙引号,例如a="/etc/rc.local $user" ,此時輸出變量會對内容中的$user進行解析然後再輸出。

希望原樣輸出變量中的内容時就用單引号引起内容進行指派,例如:a='$user'。

希望變量的内容是指令的解析結果的定義及指派如下:

要使用反引号将指派的指令括起來,例如:a=`ls`;或者用$()括起來,例如:a=$(ls)。

變量的輸出方法如下:

使用“$變量名”即可輸出變量的内容,常用“echo $變量名”的方式,也可用printf代替echo輸出更複雜的格式内容。

變量定義的技巧及注意事項:

注意指令變量内容前後的字元``(此字元為鍵盤tab鍵上面的那個反引号,不是單引号),例如:“cmd=`ls`”。

在變量名前加$可以取得該變量的值,使用echo或printf指令可以顯示變量的值,$a和${a}的寫法不同,但效果是一樣的。

用echo等指令輸出變量的時候,也可用單引号、雙引号、反引号,例如:echo $a、echo "$a"、echo '$a',它們的用法和前面變量内容定義的總結是一緻的。

$dbname_tname,當變量後面連接配接有其他字元的時候,必須給變量加上大括号{},例如:$dbname_tname就要改成${dbname}_tname。

有關上述變量問題輸出的小故事

故事1:老男孩正在給面授班講課,發了一段内容,結果引起群裡網絡班學員的強烈反應,下面是對話内容。

老男孩(31333741)  12:42:54

金庸新著

xxx-學員?12:43:39

老師,金庸又寫啥小說了? #<==看到了吧,這引起了誤解

老男孩(31333741)?12:42:54

這是一本小說,作者為金庸新,而非金庸,但是給讀者造成的感覺是{金庸}

新著。

$dbname_tname變量就類似于這個金庸新著,會引起歧義,是以要改成${dbname}_tname,shell就會認為隻有dbname是變量了。

老男孩(31333741)?12:44:45

如果真的是金庸新著,就要像這樣用大括号分隔開,${金庸}新著。

故事2:老男孩運維班20期的李同學在他媳婦看電視劇時發現了這個金庸新著,于是他将電視劇停下來,還截圖發給了我。

可見形象的比喻學習對學生的影響非常深遠!養成将所有字元串變量用大括号括起來的習慣,在程式設計時将會減少很多問題。不過老男孩也并不是一直都這麼做,因為多輸入内容會造成效率不高,但是金庸新著的問題确實要多注意。

3.3.2 變量定義及變量輸出說明

有關shell變量定義、指派及變量輸出加單引号、雙引号、反引号與不加引号的簡要說明見表3-2。

表3-2 單引号、雙引号、反引号與不加引号的知識說明

名 稱 解 釋

單引号 所見即所得,即輸出時會将單引号内的所有内容都原樣輸出,或者描述為單引号裡面看到的是什麼就會輸出什麼,這稱為強引用

雙引号

(預設) 輸出雙引号内的所有内容;如果内容中有指令(要反引下)、變量、特殊轉義符等,會先把變量、指令、轉義字元解析出結果,然後再輸出最終内容,推薦使用,這稱為弱引用

無引号 指派時,如果變量内容中有空格,則會造成指派不完整。而在輸出内容時,會将含有空格的字元串視為一個整體來輸出;如果内容中有指令(要反引下)、變量等,則會先把變量、指令解析出結果,然後輸出最終内容;如果字元串中帶有空格等特殊字元,則有可能無法完整地輸出,是以需要改加雙引号。一般連續的字元串、數字、路徑等可以不加任何引号進行指派和輸出,不過最好是用雙引号替代無引号的情況,特别是對變量指派時

反引号 ``一般用于引用指令,執行的時候指令會被執行,相當于$(),指派和輸出都要用``将指令引起來

提示: 這裡僅為linux shell下的結論,對于awk語言會有點特别,有關情況下文會有測試講解。

老男孩的建議:

在腳本中定義普通字元串變量時,應盡量把變量的内容用雙引号括起來。

單純數字的變量内容可以不加引号。

希望變量的内容原樣輸出時需要加單引号。

希望變量值引用指令并擷取指令的結果時就用反引号或$()。

以下是單引号、雙引号與不加引号的實戰示範。

範例3-8:對由反引号引起來的`date`指令或$(date)進行測試。

[root@oldboy ~]# echo 'today is date'

today is date #<==單引号引起内容時,你看到什麼就會顯示什麼

[root@oldboy ~]# echo 'today is `date`'

today is `date` #<==單引号引起内容時,你看到什麼就會顯示什麼,内容中有指令時即使通過

    ?反引号引起來也沒有用

[root@oldboy ~]# echo "today is date"

today is date

[root@oldboy ~]# echo "today is `date`"

today is sun sep 11 15:12:30 cst 2016

#<==對輸出内容加雙引号時,如果裡面是變量或用反引号引起來的指令,則會先把變量或指令解析成

?具體内容再顯示

[root@oldboy ~]# echo "today is $(date)"  #<==?$()的功能和反引号``相同

[root@oldboy ~]# echo today is $(date)  ??#<==帶空格的内容不加引号,同樣可以正确

 ??     地輸出,但不建議這麼做

#<==對于連續的字元串等内容輸出一般可以不加引号,但加雙引号比較保險,是以推薦使用。

範例3-9:變量定義後,在調用變量輸出列印時加引号測試。

[root@oldboy ~]# oldboy=testchars  ??#<==建立一個不帶引号的變量并指派。

[root@oldboy ~]# echo $oldboy  ??#<==不加引号,顯示變量解析後的内容。

testchars

[root@oldboy ~]# echo '$oldboy'  ??#<==加單引号,顯示變量本身。

$oldboy

[root@oldboy ~]# echo "$oldboy"  ??#<==加雙引号,顯示變量内容,引号内可以

?是變量、字元串等。

範例3-10:使用三劍客指令中的grep過濾字元串時給過濾的内容加引号。

[root@oldboy ~]# cat grep.log  ??#<==待測試的内容。

[root@oldboy ~]# grep "$oldboy" grep.log  #<==将$oldboy解析為結果後進行過濾。

[root@oldboy ~]# grep '$oldboy' grep.log  #<==将$oldboy本身作為結果進行過濾。

[root@oldboy ~]# grep $oldboy grep.log    #<==将$oldboy解析為結果後進行過濾,同雙引号的情況,但不建議這樣使用,沒有特殊需要時應一律加雙引号。

範例3-11:使用awk調用shell中的變量,分别針對加引号、不加引号等情況進行測試。

首先在給shell中的變量指派時不加任何引号,這裡使用awk輸出測試結果。

[root@oldboy ~]# ett=123  ??#<==定義變量ett并指派123,沒加引号。

[root@oldboy ~]# awk 'begin {print "$ett"}'

#<==加雙引号引用$ett,卻隻輸出了本身,這個就不符合前文的結論了。

$ett

[root@oldboy ~]# awk 'begin {print $ett}'

         #<==不加引号的$ett,又輸出了空的結果,這個就不符合前文的結論了。

[root@oldboy ~]# awk 'begin {print '$ett'}'

#<==加單引号引用$ett,又輸出了解析後的結果,這個就不符合前文的結論了。

123

 [root@oldboy ~]# awk 'begin {print "'$ett'"}'

以上的結果正好與前面的結論相反,這是awk調用shell變量的特殊用法。

然後在給shell中的變量指派時加單引号,同樣使用awk輸出測試結果。

[root@oldboy ~]# ett='oldgirl' #<==定義變量ett并指派oldgirl,加單引号。

$ett #<==加雙引号引用$ett,則輸出本身。

#<==對$ett不加引号,則輸出空的結果。

#<==加單引号引用$ett,也是輸出空的結果,這個和前文的不加引号定義、指派的結果又不一樣。

[root@oldboy ~]# awk 'begin {print "'$ett'"}'

oldgirl #<==在單引号外再加一層雙引号引用$ett,則輸出解析後的結果。

以下在給shell中的變量指派時加雙引号,也使用awk輸出測試結果。

[root@oldboy ~]# ett="tingting" #<==定義變量ett并指派tingting,加雙引号,這個

?測試結果同單引号的情況。

[root@oldboy ~]# awk 'begin {print "$ett"}' #<==加雙引号引用$ett,會輸出本身。

[root@oldboy ~]# awk 'begin {print $ett}' #<==不加引号的$ett,會輸出空的結果。

[root@oldboy ~]# awk 'begin {print '$ett'}' #<==加單引号的$ett,會輸出空的結果。

[root@oldboy ~]# awk 'begin {print "'$ett'"}' #<==在單引号外部再加雙引号引用$ett,

  ?????會輸出正确結果。

tingting

以下在給shell中的變量指派時加反引号引用指令,同樣使用awk輸出測試結果。

[root@oldboy ~]# ett=`pwd` #<==定義變量ett并指派pwd指令,加反引号,這個測試結果更特殊。

[root@oldboy ~]# echo $ett

/root

[root@oldboy ~]# awk 'begin {print '$ett'}' #<==單引号引用$ett,看起來輸出了結

  ???果,卻是報錯,和外層單引号沖突了。

awk: begin {print /root}

awk:               ^ unterminated regexp

awk: cmd. line:1: begin {print /root}

awk: cmd. line:1:                    ^ unexpected newline or end of string

  ???會輸出正确結果。

根據上述範例整理的測試結果見表3-3,供讀者參考。

表3-3 測試結果

ett

   awk ett=123 ett='oldgirl' ett="tingting" ett=`pwd`

awk加雙引号 本身 本身 本身 本身

awk不加引号 空 空 空 空

awk加單引号 正确輸出 空 空 報文法錯

awk加單引号後再同時加雙引号 正确輸出 正确輸出 正确輸出 正确輸出

結論:不管變量如何定義、指派,除了加單引号以外,利用awk直接擷取變量的輸出,結果都是一樣的,是以,在awk取用shell變量時,我們更多地還是喜歡先用echo加符号輸出變量,然後通過管道給awk,進而控制變量的輸出結果。舉例如下:

[root@oldboy ~]# ett="oldgirl" #<==最正常的指派文法

[root@oldboy ~]# echo "$ett"|awk '{print $0}' #<==用雙引号引用$ett

oldgirl

[root@oldboy ~]# echo '$ett'|awk '{print $0}' #<==用單引号引用$ett

[root@oldboy ~]# echo $ett|awk '{print $0}' #<==不加引号引用$ett

[root@oldboy ~]# ett=`pwd` #<==指令指派的文法

[root@oldboy ~]# echo "$ett"|awk '{print $0}'

[root@oldboy ~]# echo '$ett'|awk '{print $0}'

[root@oldboy ~]# echo $ett|awk '{print $0}'

這就符合前面給出的普通情況的結論了。不過,這個例子特殊了一點,有關awk調用shell變量的詳情,還可以參考老男孩的部落格“一道實用linux運維問題的9種shell解答http://oldboy.blog.51cto.com/2561410/760192”。

範例3-12:通過sed指定變量關鍵字過濾。

[root@oldboy ~]# cat sed.log

[root@oldboy ~]# sed -n /"$oldboy"/p sed.log #<==加雙引号測試

[root@oldboy ~]# sed -n /$oldboy/p sed.log #<==不加引号測試

[root@oldboy ~]# sed -n /'$oldboy'/p sed.log #<==加單引号測試

  #<==輸出本身,但是檔案裡沒有本身比對的字元串,是以輸出為空

注意:sed和grep的測試和前面結論是相符的,唯有awk有些特殊。

提示: 上述内容不需要特意去記,在使用時測試一下就會明白。

關于自定義普通字元串變量的建議

1)内容是純數字、簡單的連續字元(内容中不帶任何空格)時,定義時可以不加任何引号,例如:

a.oldboyage=33

b.networking=yes

2)沒有特殊情況時,字元串一律用雙引号定義指派,特别是多個字元串中間有空格時,例如:

a.nfsd_module="no load"

b.myname="oldboy is a handsome boy."

3)當變量裡的内容需要原樣輸出時,要用單引号(' '),這樣的需求極少,例如:

a.oldboy_name='oldboy'