一、 BashShell腳本初步
之前我們學習了很多Linux命令,但是這些命令都是在命令提示符下執行的,一次只能執行一條命令併產生結果。如創建一個文件或目錄等等。但是有些情況下,我們需要執行多個命令來完成一個完整的任務,我們可以在命令行提示符下也可以同時執行多個命令,但需要用分號分割每個單獨的命令,如下所示,先定位到當前用戶的工作目錄下,然後在目錄下創建一個logs目錄,並且在logs目錄中創建一個空的syslog.log日誌文件並在日誌文件中添加當前的系統日期和時間。
命令:
cd /home/yarn ; mkdir logs ; cd logs ; touch syslog.log ; date > logs.log |
命令執行後,會在/home/yarn目錄下首先創建一個目錄logs,然後切換到logs目錄下,通過touch命令創建一個空的日誌文件syslog.log,然後使用date命令將當前系統日期和時間添加到syslog.log日誌文件中。
syslog.log日誌內容:
2016年 02月 18日 星期四 17:49:47 CST |
雖然我們在命令行提示符同時執行了多條命令,也完成了我們想要的任務,但是這樣的命令集並沒有被保存,不能重複使用,另外,如果任務較複雜,這樣的方式就不是更好的方式了。
9.1 創建一個Bash Shell腳本
創建一個Bash腳本需要使用文件編輯器,將命令集輸入到文本文件中,如下:
命令:
vi bashTest.sh |
bashTest.sh:
#!/bin/bash … |
在腳本的第一行需要輸入#!/bin/bash,作用是指定Shell腳本解釋器,如果不顯示的指定,也可以執行,但是bash提供的函數不能使用。另外的情況是,用戶默認的Shell解釋器是bash,但是如果其他用戶要執行此腳本,這個用戶的默認的Shell解釋器不是bash,腳本執行過程中可能會出現異常,所以,要求在腳本的第一行要顯示的指定Shell腳本解釋器。
在Shell腳本中,#號是用來註釋信息的,解釋器不會執行#號後面的命令,只把他當中一般的註釋信息。註釋可以用來說明腳本的使用場景和腳本的使用說明,方便其他用戶使用腳本。
下面創建一個簡單的腳本,實現的功能和之前的例子相同,創建目錄和日誌文件。首先要使用文本編輯器創建一個文本文件用來編寫腳本命令。
命令:
vi bash01.sh |
例:bash01.sh:
#!/bin/bash # 創建的第一個bash腳本 # 此腳本的作用是在用戶工作目錄下創建一個目錄,在目錄中創建一個日誌文件 # 並且將當前的系統日期和時間輸入到文件中
# 切換到工作目錄 cd /home/yarn # 創建logs目錄 mkdir logs # 切換到logs目錄 cd logs # 創建一個空的日誌文件 touch syslog.log # 將當前系統日期時間添加到syslog.log日誌文件中 date > syslog.log |
當第一次執行腳本時,系統會警告命令找不到,通常解決的辦法有兩種,第一種方法是將腳本所在的目錄添加到環境變量PATH中,這樣系統在執行腳本時會到PATH所指定的目錄下去找,找到後執行。另一種方式是使用絕對路徑或在腳本所在目錄下使用./的方式執行腳本,如下:
/home/yarn/bash02/bash01.sh cd /home/yarn/bash02 ./bash01.sh |
如果在環境變量PATH中指定路徑或使用絕對路徑執行腳本,系統還會發出警告,說用戶沒有執行的權限,這是因爲創建腳本文件時默認情況下是沒有執行權限的,如下:
-rw-rw-r--. 1 yarn yarn 440 2月 19 11:51 bash01.sh |
這和系統默認設置有關,可以重新設置,後面會做介紹。
看到腳本文件的屬主有讀寫權限而沒有執行權限,所以要通過命令chmod爲屬主設置可執行權限。
命令:
chmod u+x bash01.sh |
bash01.sh:
-rwxrw-r--. 1 yarn yarn 440 2月 19 11:52 bash01.sh |
好了,現在就可以執行腳本了。
9.1.1 echo命令
echo命令是將字符串輸出到標準輸出,通常使用echo命令輸出提示信息或檢測結果是否正確。
命令:
echo Test Bash script! |
命令執行後會在控制檯輸出:Test Bash script!。沒問題!因爲echo命令默認後面跟着的是字符串,但是有些情況下,輸出會出現不正確的結果,如下面的情況:
命令:
echo Test 'Bash script'! |
控制檯輸出:
Test Bash script! |
這不是我們想要的結果,單引號被過濾掉了,所以在使用echo命令時,建議將輸出的字符串加上雙引號,如下:
命令:
echo "Test 'Bash script'"! |
控制檯輸出:
Test 'Bash script'! |
這次的輸出是我們想要的結果。
另外,echo命令輸出後會自動添加一個換行符,下一次輸出將在新行輸出,如下:
命令:
echo "Test 'Bash script'"! ; echo "Helle bash" |
控制檯輸出:
Test 'Bash script'! Helle bash |
有時候我們需要將輸出同一行顯示,這就需要使用-n參數,如下:
命令:
echo -n "Test 'Bash script'"! ; echo "Helle bash" |
控制檯輸出:
Test 'Bash script'!Helle bash |
9.2 在Bash腳本中使用變量
與Java或其他語言相似,在Bash腳本中也可以使用變量,將值保存到變量中在腳本中使用,下面通過例子說明在Bash腳本中變量的使用。在之前我們講了Linux系統中的環境變量,那麼對Bash腳本的變量就更容易理解了。
9.2.1 Bash腳本中的環境變量
在Linux系統中維護着一組環境變量,用來記錄系統的各類信息,如當前的用戶、用戶ID和主機名稱等等信息,這些環境變量的值可以在Bash腳本中使用。可以通過set命令看到Linux系統維護的環境變量的值。
命令:
set |
控制檯輸出:
BASH=/bin/bash HADOOP_HOME=/usr/local/hadoop HISTCONTROL=ignoredups HISTFILE=/home/yarn/.bash_history HOME=/home/yarn HOSTNAME=YARN HOSTTYPE=i386 IFS=$' \t\n' JAVA_HOME=/usr/local/jdk1.8.0_51 JRE_HOME=/usr/local/jdk1.8.0_51/jre LANG=zh_CN.utf8 LOGNAME=yarn OLDPWD=/home/yarn PATH=/usr/local/jdk1.8.0_51/bin:/usr/local/jdk1.8.0_51/jre/bin:/usr/lib/qt-3.3/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/yarn/bash01:/usr/local/hadoop/sbin:/usr/local/hadoop/bin:/home/yarn/bin:/home/yarn/bash01 PIPESTATUS=([0]="0") PPID=3054 PWD=/home/yarn/bash02 UID=500 USER=yarn USERNAME=yarn … |
這些環境變量是系統維護的,我們可以在腳本中使用,引用變量需要使用$符號,如下:
例:在腳本中訪問系統環境變量
#!/bin/bash # 使用系統維護的環境變量
echo "當前的用戶:$USER" echo "當前用戶ID:$UID" echo "用戶的工作目錄:$HOME" echo "主機名:$HOSTNAME" echo "系統默認腳本解釋器:$BASH" |
控制檯輸出:
當前的用戶:yarn 當前用戶ID:500 用戶的工作目錄:/home/yarn 主機名:YARN 系統默認腳本解釋器:/bin/bash |
這裏需要注意的是$符,系統會認爲後面跟着的是一般變量,在腳本執行過程中,系統會將變量的值替換顯示。所以如果想顯示$符到控制檯,需要進行轉義:
echo "\$USER:$USER" $USER:yarn |
對變量的引用可以可以採用${varname}的方式,大括號中是變量名稱,修改上面的例子。
例:採用${varname}的方式訪問系統環境變量
#!/bin/bash # 使用系統維護的環境變量
echo "當前的用戶:${USER}" echo "當前用戶ID:${UID}" echo "用戶的工作目錄:${HOME}" echo "主機名:${HOSTNAME}" echo "系統默認腳本解釋器:${BASH}" |
控制檯輸出:
當前的用戶:yarn 當前用戶ID:500 用戶的工作目錄:/home/yarn 主機名:YARN 系統默認腳本解釋器:/bin/bash |
使用效果是相同的,建議使用第二種方式。
9.2.2 Bash腳本中的用戶變量
在用戶腳本中可以定義和使用變量,定義後的變量可以在腳本中引用。這些變量的生命週期同腳本的執行週期相同,腳本執行結束變量同時在內存中被銷燬。爲了區別環境變量,用戶變量建議使用小寫字母,並且變量Var和var是不同的兩個變量,要注意變量名區分大小寫。
爲變量賦值要使用等號,與其他語言的區別是,變量名、等號和變量值之間是不能存在空格。如下:
例:
#!/bin/bash name=張三 age=22 address=山西太原
echo "name:$name" echo "age:$age" echo "address:$address" |
Bash腳本中的變量類型會根據變量的值決定,引用變量需要用到$符。
例子:編寫兩個腳本文件,在第一個腳本中調用第二個腳本,父腳本中設置局部變量,在子腳本中進行訪問,因爲執行子腳本會啓動一個新的子進程,所以父腳本中的局部變量不能訪問到。
#!/bin/bash #這是一個測試局部變量的腳本 name=張三 id=1001 echo "name:$name" echo "id:$id" echo "bash02.sh進程號:$$" #./bash02_1.sh #bash bash02_1.sh #. ./bash02_1.sh source ./bash02_1.sh |
例子:編寫兩個腳本文件,在第一個腳本中調用第二個腳本,父腳本中設置環境變量並使用export命令將用戶環境變量導出,在子腳本中進行訪問父腳本導出的環境變量,因爲執行子腳本會啓動一個新的子進程,在子進程中可以訪問到父腳本中的導出的用戶環境變量。
#!/bin/bash #測試用戶環境變量 export name=zhangsan id=1001 export id echo "name:$name" echo "id:$id" echo "bash03.sh父進程號:$PPID" echo "bash03.sh進程號:$$" bash /home/yarn/b2/bash03_1.sh echo "name:$name" echo "id:$id" |
例子:編寫兩個腳本文件,在第一個腳本中使用”.”的方式調用第二個腳本,父腳本中設置局部變量,再設置用戶環境變量並使用export命令將用戶環境變量導出,在子腳本中進行訪問父腳本導出的環境變量和局部變量,因爲採用.的方式啓動子腳本不會啓動一個新的子進程,還是在父進程中運行,所以,在子進程中可以訪問到父腳本中的導出的用戶環境變量和局部變量。
#!/bin/bash #這是一個測試局部變量的腳本 name=張三 id=1001 echo "name:$name" echo "id:$id" echo "bash02.sh進程號:$$" #./bash02_1.sh #bash bash02_1.sh #. ./bash02_1.sh source ./bash02_1.sh |
9.3 exec命令
當使用exec命令執行一個命令或啓動一個腳本文件時,會啓動一個子進程,但進程ID號還會使用父進程的進程ID號,對於父進程的局部變量exec命令啓動的腳本進程是不可見的,而父進程導出的環境變量還可以引用到。當exec命令啓動的腳本進程執行結束後,不會返回到父進程,而是將父進程終止。也可以理解爲exec啓動的腳本進程替代了當前的父進程,並繼承了父進程的環境變量,當腳本執行結束後啓動腳本進程的當前環境都將會被清除。
語法格式爲:
exec 命令 |
通常情況下命令是一個腳本文件。當這個腳本結束時,相應的進程就結束了。
例子:通常情況下,一個命令或腳本進程結束後,會回到啓動命令或腳本進程的父進程,下面例子說明。
#!/bin/bash #測試exec命令啓動腳本 user=zhangsan export country="中國" echo "bash04.sh進程號:$$" exec ./bash04_1.sh echo "是否執行到這裏" |
例子:通過exec命令啓動的命令或腳本進程執行結束後,不會回到啓動腳本的父進程,而是會終止啓動腳本的父進程,因爲使用的進程ID還是父進程的進程ID,感覺上是沒有啓動新的進程,還在父進程中執行腳本,但是,實際上還是啓動了新的子進程(對父進程的環境複製),只是進程ID(PID)還使用父進程的,當腳本執行結束,父進程也不存在了,不存在返回父進程。
9.3.1 exec命令執行Java程序
使用exec命令執行Java程序,命令格式如下:
exec java classname exec java -jar jarname |
使用exec命令執行Java程序,當程序執行結束後,執行exec命令的父進程也同樣會結束。
例子:
9.3.2 exec命令指定文件描述符
exec命令指定文件描述符在後面的內容中會詳細說明。
例子:重定向輸出
#!/bin/bash #測試exec命令重定向輸出
echo "chongdingxiangqian" exec > log.txt echo "Hello zhangsan" cat bash03.sh ls |
9.4 腳本中的反引號
反引號用於執行命令並且有輸出的情況,如:ls,顯示目錄下的文件信息。並且將輸出賦給一個變量。需要注意的是系統會將反引號中的內容作爲一個命令並執行。
反引號可以將命令的標準輸出值賦值給一個變量,如下:
#!/bin/bash var=`ls -a` echo "var:$var" |
控制檯輸出:
var: . .. .abrt bash01 .bash_history .bash_logout .bash_profile .bashrc .cache .config .dbus .dmrc .esd_auth … |
下面通過一個例子說明反引號的使用方式。如系統服務每天要創建一個日誌文件,每個日誌文件名要帶有日期。
例:使用反引號返回日期字符串
#!/bin/bash # 將日期格式化輸出結果賦值給變量 datestr=`date +%y%m%d` # 切換到保存日誌的目錄 cd /home/yarn/bash01 # 將日誌的名稱保存到一個變量中 logfile=log_${USER}_${datestr}.log # 創建一個空的日誌文件 touch $logfile # 將當天的日期輸入到文件的第一行 date > $logfile |
生成的文件:
log_yarn_160226.log |
log160226.log日誌內容:
2016年 02月 26日 星期五 17:29:43 CST |
9.5 獲取命令輸出結果的另一種方式
像反引號一樣,還可以使用另一種方式獲取命令的輸出結果。命令格式如下:
var=$(命令) |
例子:可以用下面的方法讀取命令序列的輸出,與返引號相似。
filelist=$(ls | cat -n) echo $filelist # 文件列表加行號 filelist=`ls | cat -n` echo $filelist # 計算兩個整數的和並返回結果 result1=`expr 3 + 4` result2=$(expr 5 + 5) echo $result1 echo $result2
# 創建臨時文件,並返回文件名稱 tmpfile1=`mktemp` tmpfile2=$(mktemp) echo $tmpfile1 echo $tmpfile2
# 創建臨時目錄並返回目錄名稱 tmpdir1=`mktemp -d` tmpdir2=$(mktemp -d) echo $tmpdir1 echo $tmpdir2 |
9.6 重定向輸入和輸出
重定向是根據不同需求將標準輸出到控制檯的信息輸出到其他的媒介,如將輸出到控制檯的警告信息重定向輸出到日誌文件便於查詢。或將從標準輸入設備鍵盤讀取數據重定向成從文件中讀取數據。
9.6.1 輸出重定向
輸出重定向常用的方式是將輸出到控制檯的信息重定向輸出到文件中。輸出重定向符是大於號 >,命令格式如下:
命令 > filename |
只要是有輸出信息的命令都可以使用輸出重定向符。
ls -a > filename date > filename echo "login USER:$USER" > filename |
輸出重定向符是大於號,需要注意的是,如果重定向輸出的文件不存在,系統會首先創建文件然後將信息輸入到文件中,如果文件存在,則直接將信息輸入到文件中。通過輸出重定向符每次輸出的信息都會將文件中已經存在的信息覆蓋,所以如果想向文件中追加信息而不覆蓋已存在的信息,需要使用重定向符(>>),下面通過例子說明。
例:將腳本執行過程中的信息重定向輸出到日誌文件。
#!/bin/bash # 輸出重定向 # 日期格式化到變量 datestr=`date +%y%m%d` # 日誌文件命令 filename=/home/yarn/bash01/log_${USER}_${datestr}.log # 將當天日期重定向輸出到日誌文件 date > $filename # 腳本執行信息追加到日誌文件 echo "bash script begin!" >> $filename # 切換到工作目錄 cd /home/yarn/bash01 # 將信息追加到日誌文件 echo "select /home/yarn/bash01" >> $filename # 創建工作目錄 mkdir mapreduce01 # 將信息追加到日誌文件 echo "create log dir:/home/yarn/bash01/mapreduce01" >> $filename # 切換工作目錄 cd mapreduce01 # 將信息追加到日誌文件 echo "select /home/yarn/bash01/mapreduce01" >> $filename # 拷貝文件到當前目錄 cp /home/yarn/bash01/bash04.sh . # 將信息追加到日誌文件 echo "copy /home/yarn/bash01/bash04.sh to /home/yarn/bash01/mapreduce01" >> $filename # 將腳本執行結束信息追加到日誌文件 echo "bash script end!" >> $filename |
可以通過:>輸出定向符清除文件內容,命令格式如下:
:> filename |
:>會把文件filename截斷爲0長度,就是清除文件內容。如果文件不存在, 那麼就創建一個空的文件。:>是一個佔位符和定向符組成。
例:將輸出信息重定向到文件後,將文件信息清除。
#!/bin/bash # :> # 定義變量,文件hello.txt不存在 FILE_DIR=/home/yarn/bash01/dir1/hello.txt # 將標準輸出到文件中 # 將"Hello Hadoop!.Hello Bash!"寫到/home/yarn/bash01/dir1/hello.txt中 # 不會在控制檯輸出,輸出重定向到文件中了 echo "Hello Hadoop!.Hello Bash!" > "$FILE_DIR" # 顯示文件/home/yarn/bash01/dir1/hello.txt內容:"Hello Hadoop!.Hello Bash!" cat "$FILE_DIR" # 清空/home/yarn/bash01/dir1/hello.txt文件的內容 :>"$FILE_DIR" # 顯示文件內容,爲空 cat "$FILE_DIR"
exit 0 |
例:將輸出信息重定向到文件,比較>與>>。
#!/bin/bash # > >> # 定義變量 FILE_DIR=/home/yarn/bash01/dir1 # 將"Hello YARN!"寫入文件yarn1.txt echo "Hello YARN!" > "$FILE_DIR"/yarn1.txt # 顯示文件內容:"Hello YARN!" cat "$FILE_DIR"/yarn1.txt # 將"Hello MapReduce!"寫入文件並覆蓋原內容"Hello YARN!" echo "Hello MapReduce!" > "$FILE_DIR"/yarn1.txt # 顯示文件內容:"Hello MapReduce!" cat "$FILE_DIR"/yarn1.txt
# 向文件中寫入內容:"Hello YARN!" echo "Hello YARN!" > "$FILE_DIR"/yarn2.txt cat "$FILE_DIR"/yarn2.txt # 向文件中追加內容:"Hello MapReduce!",在文件尾部添加,不覆蓋原內容 echo "Hello MapReduce!" >> "$FILE_DIR"/yarn2.txt # 顯示內容: # Hello YARN! # Hello MapReduce! cat "$FILE_DIR"/yarn2.txt
exit 0 |
9.6.2 輸入重定向
輸入標準設備是鍵盤,輸入重定向是將從標準輸入設備讀取數據重定向到其他媒介,通常情況下,最常見的是重定向輸入到文件,從文件中讀取數據到命令,和輸出重定向正好相反。命令格式如下:
命令 < filename |
爲了測試方便,創建一個文本文件file.txt,文件內容如下:
abc efg hello java shell script linux hadoop 100 ok 50 aaa 355 399 25 22 19 99 1000 345 33 |
例子:使用輸入重定向符<將文件內容輸出到命令
more < file.txt more file.txt cat < file.txt cat file.txt sort < file.txt sort -r < file.txt sort file.txt sort -r file.txt |
雙小於號<<是內聯輸入重定向符,使用方式和輸入重定向符<不同,必須要給定結束符,並且不是從文件中直接讀取數據。通常的用法如下面的例子,
例:將控制檯輸入的信息重定向輸入到文件
cat > file << end 將控制檯的輸入 重定向到文件file中 end |
9.7 管道
管道可以將前一個命令的輸出作爲後一個命令的輸入,管道符是豎線|,用法比較簡單,下面通過例子說明。
將ls命令的輸出排序:
ls -a | sort ls | grep f cat file.txt | grep java cat file.txt | sort -r | grep java |
rpm命令管理通過Red Hot包管理系統安裝到當前系統上的軟件包,參數-qa會在控制檯顯示已經安裝的軟件包,顯示在控制檯上的軟件包沒有排序,要想確認某個軟件是否安裝,在衆多的安裝軟件中查找不太方便,如果將rpm命令的輸出再進行排序,顯示到控制檯的安裝軟件會按照字典排序排列,可以通過管道命令實現排序,如下命令:
rpm -qa rpm -qa | sort |
控制檯顯示:(無排序)
iwl5150-firmware-8.24.2.2-1.el6.noarch iwl6000g2a-firmware-17.168.5.3-1.el6.noarch plymouth-system-theme-0.8.3-27.el6.centos.noarch xorg-x11-fonts-Type1-7.2-9.1.el6.noarch m17n-contrib-maithili-1.1.10-4.el6_1.1.noarch wqy-zenhei-fonts-0.9.45-3.el6.noarch ... |
控制檯顯示:(已排序)
usermode-gtk-1.102-3.el6.i686 ustr-1.0.4-9.1.el6.i686 util-linux-ng-2.17.2-12.9.el6.i686 valgrind-3.8.1-3.2.el6.i686 vconfig-1.9-8.1.el6.i686 vim-common-7.2.411-1.8.el6.i686 vim-enhanced-7.2.411-1.8.el6.i686 ... |
安裝軟件很多的情況下,可以一屏一屏的顯示,如下命令:
rpm -qa | sort | more |
如果目錄下文件很多,可以採用下面的命令讓文件一屏一屏的顯示:
ls -l | more |
9.8 數學運算
在Shell腳本中,算術運算和其他語言比較相對複雜,可以通過兩種方式進行算術運算。
9.8.1 expr命令
可以通過expr命令進行算術運算並返回結果,命令格式如下。
expr 5 + 5 expr 5 – 5 expr 5 / 5 expr 5 \* 5 |
控制檯顯示:
10 0 1 25 |
可以通過反引號將結果保存到變量中,在腳本中就可以引用變量了,如下所示:
例:使用返引號或$()方式
var1=`expr 5 + 5` var2=`expr 5 - 5` var3=`expr 5 / 5` var4=`expr 5 \* 5` echo "$var1" echo $var2 echo ${var3} echo $var4
v=$(expr 20 + 2) v=$(expr 20 - 2) v=$(expr 20 / 2) v=$(expr 20 \* 2) echo $var1 echo $var2 echo $var3 echo $var4 |
expr命令支持的運算符說明:
操作符 | 說明 |
OP1 + OP2 | 返回算術運算和 |
OP1 - OP2 | 返回算術運算差 |
OP1 \* OP2 | 返回算術運算乘積 |
OP1 / OP2 | 返回算術運算商 |
OP1 % OP2 | 返回算術運算餘數 |
OP1 > OP2 | 如果OP1大於OP2,返回1,否則返回0 |
OP1 >= OP2 | 如果OP1大於等於OP2,返回1,否則返回0 |
OP1 < OP2 | 如果OP1小於OP2,返回1,否則返回0 |
OP1 <= OP2 | 如果OP1小於等於OP2,返回1,否則返回0 |
OP1 != OP2 | 如果OP1不等於OP2,返回1,否則返回0 |
OP1 = OP2 | 如果OP1等於OP2,返回1,否則返回0 |
substr string pos length | 返回string的從pos開始length長度的字符串 |
index string chars | 返回chars在string中的開始位置,沒有返回0 |
length string | 返回字符串的長度 |
注意:大於號和小於號需要轉義,如下所示:
expr 5 \> 5 expr 5 \>= 5 expr 5 \< 5 expr 5 \<= 5 expr 5 != 5 expr 5 = 5 |
幾個常用的字符串操作,如下所示:
expr substr abcdefg 2 3 # bcd expr index abcdefg cd # 3 expr length abcdefg # 7 |
控制檯輸出:
bcd 3 7 |
9.8.2 使用方括號
另一種方式是採用方括號進行算術運算,如$[算術表達式],通常的方式將結果值賦給一個變量,使用方式如下。
使用方括號進行算術運算:
例子:
#!/bin/bash
var1=$[ 5 + 5 ] var2=$[ 5 - 5 ] var3=$[ 5 * 5 ] var4=$[ 5 / 5 ] var5=$[ 5 % 5 ] var6=$[ 5 + ( 5 * 5 )]
echo "var1: $var1" echo "var2: $var2" echo "var3: $var3" echo "var4: $var4" echo "var5: $var5" echo "var6: $var6" |
控制檯輸出:
var1: 10 var2: 0 var3: 25 var4: 1 var5: 0 var6: 30 |
注意:BashShell只支持整數運算,不支持小數運算。
9.9 腳本退出
在Linux中,每個命令執行完成後都會返回一個退出狀態碼,通知系統命令執行完成。退出狀態碼是0~255的一個正整數,在命令執行結束後由命令傳給解釋程序。通常情況下,可以根據這個值來判斷命令是否執行成功。
Linux提供了變量$?來保存最近執行的命令的退出狀態碼,當一個命令執行完成後,可以通過$?變量查看到命令的退出碼,如下:
例:通過變量$?查看命令執行的退出碼
cd /home/yarn/bash01 echo "$?" mkdir logs echo "$?" |
常見的命令退出狀態碼:
狀態碼 | 說明 |
0 | 命令成功結束 |
1 | 未知錯誤 |
2 | 誤用Shell命令 |
126 | 命令不可執行 |
127 | 命令沒有找到 |
128 | 無效退出參數 |
一個命令成功執行結束返回的狀態碼是0,如果命令執行出現錯誤會返回一個非0的正整數。
9.9.1 exit命令
如果只在命令提示符下鍵入exit,將退出當前進程。如果在腳本中使用exit命令,會將exit命令之前的一個命令退出碼返回。通常情況下:退出狀態爲0表示命令執行成功,而非0退出碼說明命令沒有執行成功。
當然,可以使用exit命令在腳本中指定退出碼,如下所示。
cd /home/yarn/bash01 mkdir logs exit 0 |
命令exit可以在分支語句中使用,根據條件退出腳本執行並返回退出碼,在父腳本中作爲判斷的條件。注意:退出碼可以指定,但不要超過取值範圍。退出碼的取值範圍是0~255,如果超出範圍會返回256的餘數。
if 創建目錄 then cd 目錄 創建文件 else exit 1 fi |
例子:在命令行上測試命令執行後的退出碼
mkdir d1 echo $? mkdir d1 echo $? cd d1 echo "執行結束" |
例子:在腳本中,返回的退出碼是最後一條命令的退出碼
#!/bin/bash #測試exit命令返回退出碼
mkdir d1 echo $? mkdir d1 echo $? cd d1 echo "執行結束" exit 10 |
9.10 命令執行順序控制
通常情況下,一個腳本完成一個特定的任務,如:創建目錄,然後在目錄中創建日誌文件。或者將文件備份後刪除原文件,否則可能會丟失文件。也就是在執行某個命令的時需要依賴前一個命令是否執行成功。
如果希望在成功地執行一個命令之後再執行另一個命令,或者在一個命令失敗後再執行
另一個命令,&&和||可以完成這樣的功能。相應的命令可以是系統命令或腳本。
另外,使用()和{ }還可以在當前腳本中執行一組命令。
9.10.1 使用&&執行命令序列
使用&&的一般形式爲:
命令1 && 命令2 && 命令3 |
當命令1返回真後(返回0,成功被執行),命令2才能夠被執行,否則,&&後面的命令都不會執行。當命令2返回真後纔會執行命令3,否則不會執行命令3。就如Java中的短路與相同。
例子:使用&&執行多個命令序列
[yarn@YARN test]$ mkdir dir1 && echo "創建目錄成功" 創建目錄成功 [yarn@YARN test]$ mkdir dir1 && echo "創建目錄成功" mkdir: 無法創建目錄"dir1": 文件已存在
[yarn@YARN test]$ cd /home/yarn && mkdir logs && cd logs && touch log.log && pwd /home/yarn/logs |
目錄創建成功,後面的命令纔會執行輸出。第二個命令序列也是一樣,前面的命令執行成功,後面的命令纔會繼續執行。
命令序列通常使用在if語句中,如果命令序列執行全部成功則執行分支中的語句。如果全部執行成功退出碼爲0,否則爲非0,if語句根據退出碼判斷執行分支語句。
9.10.2 使用||執行命令序列
使用||的一般形式爲:
命令1 || 命令2 || 命令3 |
當命令1執行失敗(返回非0的值),命令2才能夠被執行,否則,||後面的命令都不會執行。當命令2執行也失敗了纔會執行命令3,否則不會執行命令3。就如Java中的短路或相同。
例子:使用||執行多個命令序列
#!/bin/bash # 錯誤信息不輸出 exec 2>/dev/null
cp file1 file2 || echo "文件拷貝失敗" |
例子:如果目錄存在,移動到臨時目錄,然後繼續創建
mkdir dir1 || mv dir1 /tmp/dir1 && mkdir dir1 |
例子:命令也可以是腳本,首先創建一個腳本。
#!/bin/bash # 腳本中的錯誤信息忽略 exec 2>/dev/null # 切換到目錄 cd /home/yarn # 拷貝不存在的文件 cp file1 file2 |
執行腳本:
[yarn@YARN test]$ t.sh || "文件拷貝失敗" -bash: 文件拷貝失敗: command not found |
9.10.3 使用()和{}執行命令序列
如果想執行一組命令,可以採用兩種方式,將命令組放在()或{}中,如果在一行需要使用命令分隔符;將命令分開,如果不在一行,就不需要了。這兩種方式的區別是,()可以在命令行上使用,而{}方式只能在腳本中使用。另外,()方式執行腳本不會改變當前命令行狀態。
例子:在命令行執行()語句塊
[yarn@YARN test]$ (cd /home/yarn;pwd) /home/yarn [yarn@YARN test]$ |
另外,這兩種方式的區別是,()方式可以引用父腳本中的變量,但不能改變變量的值,而{}方式可以引用父腳本中的變量,也可以改變變量的值。
例子:兩種方式引用父進程中的局部變量,並修改變量的值,比較兩種方式的區別。
#!/bin/bash #測試()和{} name=zhangsan ( echo $name name=lisi echo $name echo "()中的進程號: $$" )
echo $name echo "bash03.sh進程號:$$"
#!/bin/bash name=zhangsan { echo $name name=lisi echo $name echo "{}進程號:$$" } echo $name echo "bash04.sh進程號:$$" |
需要注意的是,這兩種方式不會啓動一個新的子進程,在子進程中執行命令,而是都在父進程中執行。
例子:在腳本中使用兩種方式執行,驗證是否創建了新的子進程。
另外,在()內聲明的變量,在腳本中無法訪問,相當於局部變量,而在{}內聲明的變量在腳本中可以訪問
例子:在()中聲明的變量類似局部變量,腳本中不能訪問。在{}中聲明的變量,腳本中可以訪問。
這兩種方式通常使用的場景是重定向輸入和重定向輸出,從一個文件中讀取或重定向輸出到文件。
例子:使用兩種方式重定向輸入和重定向輸出到文件。
#!/bin/bash echo "腳本開始執行" ( cd /home/yarn/b3 echo "切換到/home/yarn/b3目錄下" mkdir d6 echo "創建目錄d6" cd d6 echo "切換到d6下" touch log.log echo "創建日誌文件log.log"
)>f2 |
將代碼塊{…}中的所有標準輸出內容都重新定向到文件中,注意,只有標準輸出到控制檯的內容纔會寫入到文件中。
例:代碼塊的用法。
#!/bin/bash # {}>file
FILE_DIR=/home/yarn/bash01/dir1
# 代碼塊 { # 判斷變量是否是目錄 if [ -d "$FILE_DIR" ] then # 標準輸出到控制檯的信息,所以會寫到文件中 echo "Add content to the '$FILE_DIR/yarn3.txt'" # 標準輸出已經重定向到文件中了,所以不會顯示到控制檯,也不會寫到文件中 echo "Bash Sheel!" >> "$FILE_DIR"/yarn3.txt # 同上 echo "Linux!" >> "$FILE_DIR"/yarn3.txt fi # 重新定向到文件中,可以作爲日誌 } > "$FILE_DIR"/bashbak1 # 顯示文件內容:Add content to the '/home/yarn/bash01/dir1/yarn3.txt' cat "$FILE_DIR"/bashbak1 |
從文件中輸出內容。
例:read命令的用法例子。
#!/bin/bash # <
FILE_DIR=/home/yarn/bash01/dir1/yarn2.txt # 從文件中讀取第一行到變量line1 read line1 < "$FILE_DIR" # 還是讀取第一行 read line2 < "$FILE_DIR" # 兩個變量的內容都是文件的第一行內容:Hello YARN! echo "$line1" echo "$line2"
# 代碼塊{…}中讀取兩次到兩個變量,變量line3讀取的是第一行 # 變量line4讀取的是文件的第二行 { read line3 read line4 } < "$FILE_DIR"
echo # 輸出:Hello YARN! echo "$line3" # 輸出:Hello MapReduce! echo "$line4" |
9.11 grep命令
grep命令使用廣泛,允許對文本文件中的內容進行模式查找。如果找到匹配模式的信息會輸出到控制檯,打印符合條件的所有行。創建測試文件flowlist.txt。
flowlist.txt文件:
1501010001 71.05 2141618 3 07/11/15 21:46:57 1006 User001 109 4 303 1501010002 865.86 1686831 2 05/11/15 03:52:44 1009 User009 109 1 302 1501010003 652.61 2587675 2 07/14/15 19:17:39 1010 User003 110 1 305 1501010004 905.24 1282788 1 04/17/15 17:14:09 1010 User004 102 3 304 1501010005 444.25 1624680 1 04/05/15 11:40:51 1005 User009 108 1 308 1501010006 48.21 2473714 3 01/18/15 23:45:51 1001 User003 110 2 302 1501010007 396.26 2512994 4 08/07/15 05:55:16 1005 User009 102 2 308 1501010008 690.74 1259159 2 08/06/15 02:48:20 1004 User001 104 4 304 1501010009 122.37 2462139 1 04/27/15 08:19:22 1008 User008 105 2 310 |
grep命令格式:
grep [參數] 查找的內容 [文件] |
例子:查找所有以txt結尾的文件中存在字符串的記錄並顯示到控制檯。
[yarn@YARN test]$ grep "User009" *.txt 1501010002 865.86 1686831 2 05/11/15 03:52:44 1009 User009 109 1302 1501010005 444.25 1624680 1 04/05/15 11:40:51 1005 User009 108 1308 1501010007 396.26 2512994 4 08/07/15 05:55:16 1005 User009 102 2308 |
例子:查找存在字符串的行數。
[yarn@YARN test]$ grep -c "1005" flowlist.txt 2 |
例子:查找存在字符串的記錄及行號。
[yarn@YARN test]$ grep -n "1005" flowlist.txt 5:1501010005 444.25 1624680 1 04/05/15 11:40:51 1005 User009 108 1308 7:1501010007 396.26 2512994 4 08/07/15 05:55:16 1005 User009 102 2308 |
例子:顯示不包含條件的記錄。
[yarn@YARN test]$ grep -v "1005" flowlist.txt 1501010001 71.05 2141618 3 07/11/15 21:46:57 1006 User001 109 4303 1501010002 865.86 1686831 2 05/11/15 03:52:44 1009 User009 109 1302 1501010003 652.61 2587675 2 07/14/15 19:17:39 1010 User003 110 1305 1501010004 905.24 1282788 1 04/17/15 17:14:09 1010 User004 102 3304 1501010006 48.21 2473714 3 01/18/15 23:45:51 1001 User003 110 2302 1501010008 690.74 1259159 2 08/06/15 02:48:20 1004 User001 104 4304 1501010009 122.37 2462139 1 04/27/15 08:19:22 1008 User008 105 2310 |
例子:精確查找符合條件的記錄。
[yarn@YARN test]$ grep "303" flowlist.txt 1501010001 71.05 2141618 3 07/11/15 21:46:57 1006 User001 109 4 303 |
例子:忽略大小寫查詢。
[yarn@YARN test]$ grep -i "user001" flowlist.txt 1501010001 71.05 2141618 3 07/11/15 21:46:57 1006 User001 109 4303 1501010008 690.74 1259159 2 08/06/15 02:48:20 1004 User001 104 4304 |
例子:將查詢結果通過管道繼續查詢符合條件的記錄。
[yarn@YARN test]$ grep "User001" flowlist.txt | grep "304" 1501010008 690.74 1259159 2 08/06/15 02:48:20 1004 User001 104 4304 |
例子:將查詢結果輸出到文件。
[yarn@YARN test]$ grep "304" flowlist.txt > file_304.txt [yarn@YARN test]$ cat file_304.txt 1501010004 905.24 1282788 1 04/17/15 17:14:09 1010 User004 102 3304 1501010008 690.74 1259159 2 08/06/15 02:48:20 1004 User001 104 4304 |
例子:查詢用戶信息。
[yarn@YARN test]$ grep "yarn" /etc/passwd yarn:x:500:500:yarn:/home/yarn:/bin/bash [yarn@YARN test]$ grep yarn /etc/passwd yarn:x:500:500:yarn:/home/yarn:/bin/bash |
例子:grep命令不僅可以在文件查找符合條件的行,也可在字符串中查找符合條件的子串。
[yarn@YARN test]$ echo "Hello Linux" | grep in Hello Linux [yarn@YARN test]$ echo "I Study Hadoop" | grep "doo" I Study Hadoop |
9.12 變量設置模式
9.12.1 檢查變量是否已經設置值
測試變量是否已設置值或被初始化。如果變量未被初始化,則返回value。
命令格式如下:
var=${var:-value} var=${var-value} |
例子:測試變量是否被初始化,如果未被初始化,在返回一個默認的值。
# 變量name未設初值則返回默認值 [yarn@YARN test]$ echo ${name:-zhangsan} zhangsan # 可以看出,雖然返回了默認值,但是變量name並未賦值 [yarn@YARN test]$ echo ${name:-lisi} lisi # 爲變量name賦值 [yarn@YARN test]$ name=wangwu # 因爲變量name已經賦值,所以返回變量的值 [yarn@YARN test]$ echo ${name:-zhangsan} wangwu # 清除變量name [yarn@YARN test]$ unset name # 變量那麼未賦初值,返回默認值 [yarn@YARN test]$ echo ${name:-zhangsan} zhangsan |
測試變量是否已設置值或被初始化。如果變量未被初始化,則使用另一個設定的值。與上面命令不同之處在於,將默認值賦值給變量。
命令格式如下:
var=${var:=value} |
例子:測試變量是否被初始化,如果未被初始化,在返回一個默認的值並將默認值賦值給變量。
# 變量id未被賦值,返回默認值並且將值賦給變量id [yarn@YARN test]$ echo ${id:=1001} 1001 # 因爲變量id已經賦值1001,所以直接返回 [yarn@YARN test]$ echo ${id:=1002} 1001 # 清除變量id [yarn@YARN test]$ unset id # 變量id未賦值,返回默認值1002並賦值給變量id [yarn@YARN test]$ echo ${id:=1002} 1002 # 顯示變量id的值, [yarn@YARN test]$ echo $id 1002 # 因爲變量id已經賦值,所以直接返回已有的值 [yarn@YARN test]$ echo ${id:=1003} 1002 |
測試變量是否已設置值或被初始化。如果變量未被初始化,則返回一個空串。如果已設置初始值,則將新值返回但並不賦值給變量。
命令格式如下:
var=${var:+value} |
例子:測試變量是否被初始化,如果未被初始化,在返回一個空串。否則,返回新值但不會將新值賦值給變量。
# 變量price未被賦初值,返回空串 [yarn@YARN test]$ echo ${price:+100}
# 如果變量price未賦值,將新值200賦值給變量price [yarn@YARN test]$ price=${price:-200} # 打印變量的值,爲200 [yarn@YARN test]$ echo $price 200 # 如果變量已經賦值,則返回新值,但是並不會將新值賦給變量price [yarn@YARN test]$ echo ${price:+100} 100 # 打印變量price,變量的值還是舊值200 [yarn@YARN test]$ echo $price 200 |
9.12.2 設置只讀變量
在聲明變量並賦值後,使用readonly命令修飾,變量就成了只讀變量,在整個生命週期內不能修改了。
例子:將變量聲明爲只讀變量。
# 聲明變量並賦值 [yarn@YARN test]$ name=zhangsan # 將變量設置成只讀變量 [yarn@YARN test]$ readonly name # 打印變量的值 [yarn@YARN test]$ echo $name zhangsan # 爲變量重新賦值,提示爲只讀不能修改 [yarn@YARN test]$ name=lisi -bash: name: readonly variable # 變量聲明時設置爲只讀 [yarn@YARN test]$ readonly id=1001 [yarn@YARN test]$ echo $id 1001 # 爲變量重新賦值,提示爲只讀變量 [yarn@YARN test]$ id=1002 -bash: id: readonly variable |