一、什么是shell?
shell是一個用 C 語言編寫的程序,它是用戶使用 linux 的橋梁。Shell 既是一種命令語言,又是一種程序設計語言。
Shell 是指一種應用程序,這個應用程序提供了一個界面,用戶通過這個界面訪問操作系統內核的服務。
Shell屬于內置的腳本,程序開發的效率非常高,依賴于功能強大的命令可以迅速地完成開發任務(批處理)語法簡單,代碼寫起來比較輕松,簡單易學。
二、shell的分類
cat /etc/shells
在linux中有很多類型的shell,不同的shell具備不同的功能,shell還決定了腳本中函數的語法,Linux中默認的shell是 /bash/bash(重點),流行的shell有ash、bash、ksh、csh、zsh等,不同的shell都有自己的特點以及用途。
編寫規范:
#!/bin/bash [指定告知系統當前這個腳本要使用的shell解釋器]
Shell相關指令
文件命名規范:
*.sh
.sh是linux下bash shell 的默認后綴
Bash 常用快捷鍵
快捷鍵 |
作用 |
ctrl+A |
把光標移動到命令行開頭。如果我們輸入的命令過長,想要把光標移動到命令行開頭時使用。 |
ctrl+E |
把光標移動到命令行結尾。 |
ctrl+C |
強制終止當前的命令。 |
ctrl+L |
清屏,相當于clear命令。 |
ctrl+U |
刪除或剪切光標之前的命令。我輸入了一行很長的命令,不用使用退格鍵一個一個字符的刪除,使用這個快捷鍵會更加方便 |
ctrl+K |
刪除或剪切光標之后的內容。 |
ctrl+Y |
粘貼ctrl+U或ctul+K剪切的內容。 |
ctrl+R |
在歷史命令中搜索,按下ctrl+R之后,就會出現搜索界面,只要輸入搜索內容,就會從歷史命令中搜索。 |
ctrl+D |
退出當前終端。 |
ctrl+Z |
暫停,并放入后臺。這個快捷鍵牽扯工作管理的內容,我們在系統管理章節詳細介紹。 |
ctrl+S |
暫停屏幕輸出。 |
ctrl+Q |
恢復屏幕輸出。 |
輸入輸出重定向
linux 的標準輸入與輸出
設備 |
設備名 |
文件描述符 |
類型 |
鍵盤 |
/dev/stdin |
0 |
標準輸入 |
顯示器 |
/dev/stdout |
1 |
標準輸出 |
顯示器 |
/dev/stderr |
2 |
標準錯誤輸出 |
輸入重定向
輸入重定向:是指不使用系統提供的標準輸入端口,而進行重新的指定。換言之,輸入重定向就是不使用標準輸入端口輸入文件,而是使用指定的文件作為標準輸入設備。(重定向簡單理解就是使用 “<”符來修改標準輸入設備)
類型 |
符號(語法) |
功能 |
標準輸入 |
命令<文件1 |
命令把文件1的內容作為標準輸入設備 |
標識符限定輸入 |
命令<<標識符 |
命令把標準輸入中讀入內容,直到遇到“標識符”分解符為止 |
輸入輸出重定向(同時使用) |
命令< 文件1 >文件2 |
命令把文件1的內容作為標準輸入,把文件2作為標準輸出。 |
輸出重定向
輸出重定向:(通俗的講,重定向輸出就是把要輸出的文件信息寫入到一個文件中去,而不是將要輸出的文件信息輸出到控制臺(顯示屏),在linux中,默認的標準輸出設備是控制臺(或稱為顯示器),用戶輸出的信息默認情況下都會顯示到控制臺.
&表示全部文件,文件不管對錯,1表示標準輸出文件,2表示標準錯誤輸出。
類型 |
符號(語法) |
功能 |
標住輸出重定向 |
命令 > 文件 |
以覆蓋方式,把命令的正確輸出內容輸出到指定的文件或設備當中 |
標住輸出重定向 |
命令 >> 文件 |
以追加方式,把命令的正確輸出內容輸出到指定的文件或設備當中 |
標準錯誤輸出重定向 |
錯誤命令 2> 文件 |
以覆蓋方式,把命令的錯誤輸出輸出到指定的文件或設備當中 |
標準錯誤輸出重定向 |
錯誤命令 2>> 文件 |
以追加方式,把命令的錯誤輸出輸出到指定的文件或設備當中 |
正確輸出和錯誤輸出同時保存 |
命令 > 文件 2>&1 |
以覆蓋的方式,把正確輸出和錯誤輸出都保存到同一個文件當中。 |
正確輸出和錯誤輸出同時保存 |
命令 >> 文件 2>&1 |
以追加的方式,把正確輸出和錯誤輸出都保存到同一個文件當中。 |
正確輸出和錯誤輸出同時保存 |
命令 &> 文件 |
以覆蓋的方式,把正確輸出和錯誤輸出都保存到同一個文件當中。 |
正確輸出和錯誤輸出同時保存 |
命令 &>> 文件 |
以追加的方式,把正確輸出和錯誤輸出都保存到同一個文件當中。 |
正確輸出和錯誤輸出同時保存 |
命令 >> 文件1 2>>文件2 |
把正確的輸出追加到文件1中,把錯誤的輸出追加到文件2中。 |
/dev/null 文件
如果希望執行某個命令,但又不希望在屏幕上顯示輸出結果,那么可以將輸出重定向到 /dev/null中.
[root@localhost ~]$ command > dev/null
多命令順序執行
多命令執行符 |
作用 |
格式 |
; |
命令1 ;命令2 |
多個命令順序執行,命令之間沒有任何邏輯聯系 |
&& |
命令1 && 命令2 |
當命令1正確執行(? = 0 ) , 則命令2才會執行;當命令 1執行不正確(?=0),則命令2不會執行 |
|| |
命令1 || 命令2 |
當命令1執行不正確(?≠0),則命令2才會執行;當命令1正確執行(?≠0),則命令2不會執行. |
shell腳本的執行
[root@localhost ~]$ vim test.sh
#!/bin/bash
echo “hello world”
兩種方式執行shell腳本
第一種:給文件增加執行權限
[root@localhost ~]$ chmod u+x test.sh
[root@localhost ~]$ ./test.sh #絕對路徑或相對路徑執行
第二種(了解):通過Bash調用執行腳本
[root@localhost ~]$ bash test.sh
三、shell變量
什么是變量?
在一個腳本周期內,其值可以發生改變的量就是變量。
1. 變量的命名規則:
在定義變量時,有一些規則需要遵守:
l 命名只能使用英文字母,數字和下劃線,首個字符不能以數字開頭。
l 等號左右兩側不能有空格,可以使用下劃線“_”,變量的值如果有空格,需要使用單引號或雙引號包括。如:“test=“hello world!””。其中雙引號括起來的內容“$”,“(”和反引號都擁有特殊含義,而單引號括起來的內容都是普通字符。
l 不能使用標點符號,不能使用bash里的關鍵字(可用help命令查看保留關鍵字)。
l 環境變量建議大寫,便于區分
l 如果需要增加變量的值,那么可以進行變量值的疊加。不過變量需要用雙引號包含"$變量名"或用${變量名}包含變量名。
<!---->
[root@localhost ~]$ test=123
[root@localhost ~]$ test="$test"456
[root@localhost ~]$ echo $test
123456
#疊加變量test,變量值變成了123456
[root@localhost ~]$ test=${test}789
[root@localhost ~]$ echo $test
123456789
#再疊加變量test,變量值編程了123456789
關于單雙引號的問題:
雙引號能夠識別變量,雙引號能夠實現轉義(類似于“*”)
單引號是不能識別變量,只會原樣輸出,單引號是不能轉義的
shell中特殊符號
符號 |
作用 |
'' |
單引號。在單引號中所有的特殊符號,如“$”和”(反引號)都沒有特殊含義。單引號括起來的都是普通字符,會原樣輸出 |
"" |
雙引號。在雙引號中特殊符號都沒有特殊含義.“$”表示“調用變量的值”,“`”(esc鍵下面)表示“引用命令”,“”表示“轉義符”。 |
`` |
反引號。反引號括起來的內容是系統命令,在Bash中會先執行它。和()作用一 樣, 不過推薦使用(),因為反引號非常容易看錯。 |
$() |
和反引號作用一樣,用來引用系統命令。(推薦使用) |
() |
用于一串命令執行時,()中的命令會在子Shell中運行 |
{} |
用于一串命令執行時,{ }中的命令會在當前Shell中執行。也可以用于變量變形與替換。 |
[ ] |
用于變量的測試。 |
# |
在Shell腳本中,#開頭的行代表注釋。 |
$ |
用于調用變量的值,如需要調用變量name的值時,需要用$name的方式得到變量的值。 |
|
轉義符,跟在之后的特殊符號將失去特殊含義,變為普通字符。如$將輸出“$”符號,而不當做是變量引用。 |
單引號和雙引號
[root@localhost ~]$ name=sc
#定義變量name 的值是sc
[root@localhost ~]$ echo '$name'
$name
#如果輸出時使用單引號,則$name被認為是字符串
[root@localhost ~]$ echo "$name"
sc
#如果輸出時使用雙引號,則會輸出變量name的值 sc
[root@localhost ~]$ echo `date`
2018年10月21日星期一18:16:33 CST
#反引號括起來的命令會正常執行
[root@localhost ~]$ echo '`date`'
`date`
#但是如果反引號命令被單引號括起來,那么這個命令不會執行,―date`會被當成普通字符輸出
[root@localhost ~]$ echo "`date'"
2018年10月21日星期一18:14:21 CST
#如果是雙引號括起來,那么這個命令又會正常執行
反引號
[root@localhost ~]$ echo ls
ls
#如果命令不用反引號包含,命令不會執行,而是直接輸出
[root@localhost ~]$ echo `ls`
anaconda-ks.cfginstall.loginstall.log.syslog sh test testfile
#只有用反引號包括命令,這個命令才會執行
[root@localhost ~]$ echo $(date)
2018年10月21日星期一18:25:09 CST
#使用$(命令)的方式也是可以的
2. 變量的分類
l 用戶自定義變量: 這種變量是最常見的變量,由用戶自由定義變量名和變量的值。
l 環境變量: 這種變量中主要保存的是和系統操作環境相關的數據,比如當前登錄用戶,用戶的家目錄,命令的提示符等。不是太好理解吧,那么大家還記得在windows中,同一臺電腦可以有多個用戶登錄,而且每個用戶都可以定義自己的桌面樣式和分辨率,這些其實就是Windows的操作環境,可以當做是Windows的環境變量來理解。環境變量的變量名可以自由定義,但是一般對系統起作用的環境變量的變量名是系統預先設定好的。
l 位置參數變量: 這種變量主要是用來向腳本當中傳遞參數或數據的,變量名不能自定義,變量作用是固定的。
l 預定義變量: 是Bash中已經定義好的變量,變量名不能自定義,變量作用也是固定的。
變量分類 |
名稱 |
作用 |
內容 |
用戶自定義變量 |
自定義 |
自定義 |
自定義 |
用戶自定義環境變量 |
自定義 |
自定義 |
自定義 |
系統自帶環境變量(/etc/profile) |
確定 |
確定 |
自定義 |
位置參數變量 |
確定 |
自定義 |
自定義 |
預定義變量 |
確定 |
自定義 |
自定義 |
2.1 用戶自定義變量
2.1.1 變量定義
[root@localhost ~]$ 2name="shen chao"
-bash: 2name=shen chao: command not found
#變量名不能用數字開頭
[root@localhost ~]$ name = "shenchao"
-bash: name: command not found
#等號左右兩側不能有空格
[root@localhost ~]$ name=shen chao
-bash: chao: command not found
#變量的值如果有空格,必須用引號包含
2.1.2 變量調用
[root@localhost ~]$ name="shen chao"
#定義變量name
[root@localhost ~]$ echo $name #調用變量使用 $變量名
shen chao
#輸出變量name的值
2.1.3 變量查看
[root@localhost ~]$ set [選項]
選項:
-u:如果設定此選項,調用未聲明變量時會報錯(默認無任何提示)
-x:如果設定此選項,在命令執行之前,會把命令先輸出一次
+<參數> :取消某個set曾啟動的參數。
[root@localhost ~]$ set
BASH=/bin/bash
…省略部分輸出…
name='shen chao'
#直接使用set 命令,會查詢系統中所有的變量,包含用戶自定義變量和環境變量
[root@localhost ~]$ set -u
[root@localhost ~]$ echo $file
-bash: file: unbound variable
#當設置了-u選項后,如果調用沒有設定的變量會有報錯。默認是沒有任何輸出的。
[root@localhost ~]$ set -x
[root@localhost ~]$ ls
+ls --color=auto
anaconda-ks.cfginstall.loginstall.log.syslog sh tdir testtestfile
#如果設定了-x選項,會在每個命令執行之前,先把命令輸出一次
[root@localhost ~]$ set +x
#取消啟動的x參數
2.1.4 變量刪除
[root@localhost ~]$ unset 變量名
2.2 環境變量
2.2.1 環境變量設置
[root@localhost ~]$ export age="18"
#使用export聲明的變量即是環境變量
2.2.2 環境變量查詢和刪除
env命令和set命令的區別:
set命令可以查看所有變量,而env命令只能查看環境變量。
[root@localhost ~]$ unset gender #刪除環境變量gender
[root@localhost ~]$ env | grep gender
2.2.3 系統默認環境變量
[root@localhost ~]$ env
HOSTNAME=localhost.localdomain #主機名
SHELL=/bin/bash #當前的shell
TERM=linux #終端環境
HISTSIZE=1000 #歷史命令條數
SSH_CLIENT=192.168.4.15 22 #當前操作環境是用ssh連接的,這里記錄客戶端ip
SSH_TTY=/dev/pts/1 #ssh連接的終端時pts/1
USER=root #當前登錄的用戶
..........更多參數可以使用set和env命令查看.............
2.3 位置參數變量
位置參數變量 |
作用 |
$n |
n為數字,$0表示當前 Shell腳本程序的名稱,$1-9代表 第一到第九個參數 , 十以上的參數需要用大括號包含,如9代表第一到第九個參數,十以上的參數需要用大括號包含,如9代表第一到第九個參數,十以上的參數需要用大括號包含,如${10} |
$* |
這個變量代表命令行中所有的參數,$把所有的參數看成一個整體 |
$@ |
這個變量也代表命令行中所有的參數,不過$@把每個參數區分對待 |
$# |
這個變量代表命令行中所有參數的個數 |
$1 是你給你寫的shell腳本傳的第一個參數,$2 是你給你寫的shell腳本傳的第二個參數…
[root@localhost sh]$ vim test.sh
#!/bin/sh
echo "shell腳本本身的名字: $0"
echo "傳給shell的第一個參數: $1"
echo "傳給shell的第二個參數: $2"
保存退出后,你在Test.sh所在的目錄下輸入 bash Test.sh 1 2
結果輸出:
shell腳本本身的名字: Test.sh
傳給shell的第一個參數: 1
傳給shell的第二個參數: 2
$*會把接收的所有參數當成一個整體對待,而$@則會區分對待接收到的所有參數。舉個例子:
[root@localhost sh]$ vi parameter2.sh
#!/bin/bash
for i in"$*"
#定義for循環,in后面有幾個值,for會循環多少次,注意“$*”要用雙引號括起來
#每次循環會把in后面的值賦予變量i
#Shell把$*中的所有參數看成是一個整體,所以這個for循環只會循環一次
do
echo "The parameters is: $i"
#打印變量$i的值
done
x=1
#定義變量x的值為1
for y in"$@"
#同樣in后面的有幾個值,for循環幾次,每次都把值賦予變量y
#可是Shel1中把“$@”中的每個參數都看成是獨立的,所以“$@”中有幾個參數,就會循環幾次
do
echo "The parameter$x is: $y"
#輸出變量y的值
x=$(( $x +1 ))
#然變量x每次循環都加1,為了輸出時看的更清楚
done
2.4 預定義變量
預定義變量 |
作用 |
$? |
最后一次執行的命令的返回狀態。如果這個變量的值為0,證明上一個命令正確執行;如果這個變量的值為非О(具體是哪個數,由命令自己來決定),則證明上一個命令執行不正確了。 |
$$ |
當前進程的進程號(PID) |
$! |
后臺運行的最后一個進程的進程號(PID) |
先來看看”$?”這個變量,舉個例子說明
[root@localhost sh]$ ls
count.sh hello.sh parameter2.sh parameter.sh
#ls命令正確執行
[root@localhost sh]$ echo $?
#預定義變量“$?”的值是0,證明上一個命令執行正確
[root@localhost sh]$ ls install.log
ls:無法訪問install.log:沒有那個文件或目錄
#當前目錄中沒有install.log文件,所以ls命令報錯了
[root@localhost sh]$ echo $?
2
#變量“$?”返回一個非0的值,證明上一個命令沒有正確執行
#至于錯誤的返回值到底是多少,是在編寫ls命令時定義好的,如果碰到文件不存在就返回數值2
再來說明下”$$”和”$!”這兩個預定義變量
[root@localhost sh]$ vi variable.sh
#!/bin/bash
echo "The current process is $$"
#輸出當前進程的PID.
#這個PID就是variable.sh這個腳本執行時,生成的進程的PID
find /root -name hello.sh &
#使用find命令在root目錄下查找hello.sh文件
#符號&的意思是把命令放入后臺執行,工作管理我們在系統管理章節會詳細介紹
echo "The last one Daemon process is $!"
#輸出這個后臺執行命令的進程的PID,也就是輸出find命令的PID號
3. 只讀變量
[root@localhost sh]$ vi readonly.sh
#!/bin/bash
a=10
#語法:readonly 變量名
readonly a
a=20 #會報錯readonly variable
echo $a
4. 接受鍵盤輸入
[root@localhost ~]$ read [選項][變量名]
選項:
-a 后跟一個變量,該變量會被認為是個數組,然后給其賦值,默認是以空格為分割符。
-p: “提示信息”:在等待read輸入時,輸出提示信息
-t: 秒數:read命令會一直等待用戶輸入,使用此選項可以指定等待時間
-n: 數字:read命令只接受指定的字符數,就會執行
-s: 隱藏輸入的數據,適用于機密信息的輸入
-d: 后面跟一個標志符,其實只有其后的第一個字符有用,作為結束的標志。
-e: 在輸入的時候可以使用命令補全功能。
變量名:
變量名可以自定義,如果不指定變量名,會把輸入保存入默認變量REPLY.
如果只提供了一個變量名,則整個輸入行賦予該變量.
如果提供了一個以上的變量名,則輸入行分為若干字,一個接一個地賦予各個變量,而命令行上的最后一個變量取得剩余的所有字
寫個例子來解釋下read命令:
[root@localhost sh]$ vi read.sh
#!/bin/bash
read -t 30 -p "Please input your name: " name
#提示“請輸入姓名”并等待30 秒,把用戶的輸入保存入變量name 中
echo "Name is $name"
#看看變量“$name”中是否保存了你的輸入
read -s -t 30 -p "Please enter your age: " age
#提示“請輸入年齡”并等待30秒,把用戶的輸入保存入變量age中
#年齡是隱私,所以我們用“-s”選項隱藏輸入
echo -e "n"
#調整輸出格式,如果不輸出換行,一會的年齡輸出不會換行
echo "Age is $age"
read -n 1 -t 30 -p "Please select your gender[M/F]:" gender
#提示“請選擇性別”并等待30秒,把用戶的輸入保存入變量gender
#使用“-n1”選項只接收一個輸入字符就會執行(都不用輸入回車)
echo -e "n"
echo "Sex is $gender"
四、shell 運算符
在shell中,運算符和其他編程腳本語言一樣,常見的有算數運算符、關系運算符、邏輯運算符、字符串運算符、文件測試運算符等
1. 算數運算符
原生bash不支持簡單的數學運算,但是可以通過其他命令來實現,例如 awk 和 expr,expr 最常用。
expr 是一款表達式計算工具,使用它能完成表達式的求值操作。
例如,兩個數相加(注意使用的是反引號 ` 而不是單引號 '):
[root@localhost ~]$ vi computer.sh
#!/bin/bash
val=`expr 2 + 2`
echo "兩數之和為 : $val"
#注意
#表達式和運算符之間要有空格,例如 2+2 是不對的,必須寫成 2 + 2,這與我們熟悉的大多數編程語言不一樣。
#完整的表達式要被 ` ` 包含,注意這個字符不是常用的單引號,在 Esc 鍵下邊。
下表列出了常用的算術運算符,假定變量 a 為 10,變量 b 為 20
運算符 |
說明 |
舉例 |
+ |
加法 |
expr $a + $b 結果為 30。 |
- |
減法 |
expr $a - $b 結果為 -10。 |
* |
乘法 |
expr $a * $b 結果為 200。 |
/ |
除法 |
expr $b / $a 結果為 2。 |
% |
取余 |
expr $b % $a 結果為 0。 |
= |
賦值 |
a=$b 將把變量 b 的值賦給 a。 |
== |
相等。用于比較兩個數字,相同則返回 true(真)。 |
[ $a == $b ] 返回 false(假)。 |
!= |
不相等。用于比較兩個數字,不相同則返回 true。 |
[ $a != $b ] 返回 true。 |
注意:條件表達式要放在方括號之間,并且要有空格,必須寫成 [ $a == $b ]。
[root@localhost ~]$ vi computers.sh
#!/bin/bash
a=10
b=20
echo ' '
echo 'a+b= ' `expr $a + $b`
echo 'a-b= ' `expr $a - $b`
echo 'a*b= ' `expr $a * $b`
echo 'a/b= ' `expr $a / $b`
echo 'a%b= ' `expr $a % $b`
#判斷是否相等
if [ $a == $b ]
then
echo 'a等于b'
else
echo 'a不等于b'
fi
2. 關系運算符
關系運算符只支持數字,不支持字符串,除非字符串的值是數字。
下表列出了常用的關系運算符,假定變量 a 為 10,變量 b 為 20:
運算符 |
單詞 |
說明 |
舉例 |
-eq |
equal |
檢測兩個數是否相等,相等返回 true。 |
[ $a -eq $b ] 返回 false。 |
-ne |
not equal |
檢測兩個數是否相等,不相等返回 true。 |
[ $a -ne $b ] 返回 true。 |
-gt |
great than |
檢測左邊的數是否大于右邊的,如果是,則返回 true。 |
[ $a -gt $b ] 返回 false。 |
-lt |
less than |
檢測左邊的數是否小于右邊的,如果是,則返回 true。 |
[ $a -lt $b ] 返回 true。 |
-ge |
great than or equal |
檢測左邊的數是否大于等于右邊的,如果是,則返回 true。 |
[ $a -ge $b ] 返回 false。 |
-le |
less than or equal |
檢測左邊的數是否小于等于右邊的,如果是,則返回 true。 |
[ $a -le $b ] 返回 true。 |
[root@localhost ~]$ [ 10 -gt 10 ]
[root@localhost ~]$ echo $?
1
[root@localhost ~]$ [ 10 -eq 10 ]
[root@localhost ~]$ echo $?
0
案例:判斷當前輸入的用戶是否存在。如果存在則提示“用戶存在”否則提示“用戶不存在”。
如果要在shell腳本使用linux命令,可以使用$()包裹命令
例如:disk_size=$(df -h | awk ‘NR==2 {print $5}’)
[root@localhost ~]$ vim demo.sh
#!/bin/bash
#接受用戶的輸入
read -p '請輸入需要查詢的用戶名:' username
#獲取指定用戶名在passwd文件中出現的次數
count=$(cat /etc/passwd | grep $username | wc -l)
#count=`cat /etc/passwd | grep $username | wc -l`
#判斷出現的次數,如果次數=0則用戶不存在,反之存在
if [ $count == 0 ]
then
echo '用戶不存在'
else
echo '用戶存在'
fi
3. 邏輯運算符
下表列出了常用的布爾運算符,假定變量 a 為 10,變量 b 為 20:
運算符 |
說明 |
舉例 |
! |
非運算,表達式為 true 則返回 false,否則返回 true。 |
[ ! false ] 返回 true。 |
-o |
或(或者)運算,有一個表達式為 true 則返回 true。 |
[ $a -lt 20 -o $b -gt 100 ] 返回 true。 |
-a |
與(并且)運算,兩個表達式都為 true 才返回 true。 |
[ $a -lt 20 -a $b -gt 100 ] 返回 false。 |
或運算:一個為真即為真,全部為假才是假
與運算:一個為假即為假,全部為真才是真
4. 字符串運算符
下表列出了常用的字符串運算符,假定變量 a 為 “abc”,變量 b 為 “efg”:
運算符 |
說明 |
舉例 |
= |
檢測兩個字符串是否相等,相等返回 true。 |
[ $a = $b ] 返回 false。 |
!= |
檢測兩個字符串是否相等,不相等返回 true。 |
[ $a != $b ] 返回 true。 |
-z |
檢測字符串長度是否為0,為0返回 true。 |
[ -z $a ] 返回 false。 |
-n |
檢測字符串長度是否為0,不為0返回 true。 |
[ -n $a ] 返回 true。 |
str |
檢測字符串是否為空,不為空返回 true。 |
[ $a ] 返回 true。 |
5. 文件測試運算符(重點)
文件測試運算符用于檢測 Unix/Linux 文件的各種屬性。
操作符 |
說明 |
舉例 |
-b file |
檢測文件是否是塊設備文件,如果是,則返回 true。 |
[ -b $file ] 返回 false。 |
-c file |
檢測文件是否是字符設備文件,如果是,則返回 true。 |
[ -c $file ] 返回 false。 |
-d file |
檢測文件是否是目錄,如果是,則返回 true。 |
[ -d $file ] 返回 false。 |
-f file |
檢測文件是否是普通文件(既不是目錄,也不是設備文件),如果是,則返回 true。 |
[ -f $file ] 返回 true。 |
-g file |
檢測文件是否設置了 SGID 位,如果是,則返回 true。 |
[ -g $file ] 返回 false。 |
-k file |
檢測文件是否設置了粘著位(Sticky Bit),如果是,則返回 true。 |
[ -k $file ] 返回 false。 |
-p file |
檢測文件是否是有名管道,如果是,則返回 true。 |
[ -p $file ] 返回 false。 |
-u file |
檢測文件是否設置了 SUID 位,如果是,則返回 true。 |
[ -u $file ] 返回 false。 |
-r file |
檢測文件是否可讀,如果是,則返回 true。 |
[ -r $file ] 返回 true。 |
-w file |
檢測文件是否可寫,如果是,則返回 true。 |
[ -w $file ] 返回 true。 |
-x file |
檢測文件是否可執行,如果是,則返回 true。 |
[ -x $file ] 返回 true。 |
-s file |
檢測文件是否為空(文件大小是否大于0),不為空返回 true。 |
[ -s $file ] 返回 true。 |
-e file |
檢測文件(包括目錄)是否存在,如果是,則返回 true。 |
[ -e $file ] 返回 true。 |
注意:權限幾個判斷,如果只有一個部分符合,則認為是有權限的。
五、流程控制
1. if條件判斷
1.1 單分支if條件
語法:
if [ 條件判斷式 ]
then
程序
fi
案例:統計根分區使用率
[root@localhost ~]$ vi sh/if1.sh
#!/bin/bash
#統計根分區使用率
rate=$(df -h | grep "/dev/sda2" | awk '{print $5}’| cut -d "%"-f1)
#把根分區使用率作為變量值賦予變量rate
if [ $rate -ge 80 ]
#判斷rate的值如果大于等于80,則執行then程序
then
echo "Warning!/dev/sda3 is fu11!!"
#打印警告信息。在實際工作中,也可以向管理員發送郵件。
fi
案例:創建目錄
[root@localhost ~]$ vi sh/add_dir.sh
#!/bin/bash
#創建目錄,判斷是否存在,存在就結束,反之創建
echo "當前腳本名稱為$0"
DIR="/media/cdrom"
if [ ! -e $DIR ]
then
mkdir -p $DIR
fi
echo "$DIR 創建成功"
1.2 雙分支if條件語句
語法:
if [ 條件判斷式 ]
then
條件成立時,執行的程序
else
條件不成立時,執行的另一個程序
fi
案例1:備份MySQL數據庫
[root@localhost ~]$ vi sh/bakmysql.sh
#!/bin/bash
#備份mysql數據庫。
ntpdate asia.pool.ntp.org &>/dev/null
#同步系統時間
date=$(date +%y%m%d)
#把當前系統時間按照“年月日”格式賦子變量date
size=$(du -sh /var/lib/mysql)
#統計mysql數據庫的大小,并把大小賦予size變量
if [ -d /tmp/dbbak ]
#判斷備份目錄是否存在,是否為目錄
then
#如果判斷為真,執行以下腳本
echo "Date : $date!" > /tmp/dbbak/dbinfo.txt
#把當前日期寫入臨時文件
echo "Data size : $size" >> /tmp/dbbak/dbinfo.txt
#把數據庫大小寫入臨時文件
cd /tmp/dbbak
#進入備份目錄
tar -zcf mysql-lib-$date.tar.gz /var/lib/mysql dbinfo.txt &> /dev/null
#打包壓縮數據庫與臨時文件,把所有輸出丟入垃圾箱(不想看到任何輸出)
rm -rf /tmp/dbbak/dbinfo.txt
#刪除臨時文件
else
mkdir /tmp/dbbak
#如果判斷為假,則建立備份目錄
echo "Date : $date!" > /tmp/dbbak/dbinfo.txt
echo "Data size : $size" >> /tmp/dbbak/dbinfo.txt
#把日期和數據庫大小保存如臨時文件
cd /tmp/dbbak
tar -zcf mysql-lib-$date.tar. gz dbinfo.txt /var/lib/mysql &> /dev/null
#壓縮備份數據庫與臨時文件
rm -rf/tmp/dbbak/dbinfo.txt
#刪除臨時文件
fi
案例2:判斷Apache是否啟動,如果沒有啟動則自動啟動
[root@localhost ~]$ vi sh/autostart.sh
#!/bin/bash
#判斷apache是否啟動,如果沒有啟動則自動啟動
port=$(nmap -sT 192.168.4.210 | grep tcp | grep http | awk '{print $2}')
#使用nmap命令掃描服務器,并截取 apache服務的狀態,賦予變量port
#只要狀態是open,就證明正常啟動
if [ "$port" == "open"]
#如果變量port的值是“open”
then
echo "$(date) httpd is ok!” >> /tmp/autostart-acc.log
#則證明apache 正常啟動,在正常日志中寫入一句話即可
else
/etc/rc.d/init.d/httpd start &>/dev/null
#否則證明apache沒有啟動,自動啟動apache
echo "$(date) restart httpd !!" >> /tmp/autostart-err.log
#并在錯誤日志中記錄自動啟動apche 的時間
fi
nmap端口掃描命令,格式如下:
[root@localhost ~]$ nmap -sT 域名或IP
選項:
-s 掃描
-T 掃描所有開啟的TCP端口
#知道了nmap命令的用法,我們在腳本中使用的命令就是為了截取http的狀態,只要狀態是“or.
#就證明apache啟動正常,否則證明apache啟動錯誤。來看看腳本中命令的結果:
[root@localhost ~]$ nmap -sT 192.168.4.210 | grep tcp | grep http | awk ' fprint $2}’
#掃描指定計算機,提取包含tcp 的行,在提取包含httpd 的行,截取第二列open
#把截取的值賦予變量port
1.3 多分支if條件語句
語法:
if [ 條件判斷式1 ]
then
當條件判斷式1成立時,執行程序1
elif [ 條件判斷式2 ]
then
當條件判斷式2成立時,執行程序2
…省略更多條件…
else
當所有條件都不成立時,最后執行此程序
fi
案例:判斷用戶輸入的是什么文件
[root@localhost ~]$ vi sh/if-elif.sh
#!/bin/bash
#判斷用戶輸入的是什么文件
read -p "Please input a filename: " file
#接收鍵盤的輸入,并賦予變量file
if [ -z "$file” ]
#判斷file變量是否為空
then
echo "Error, please input a filename"
#如果為空,執行程序1,也就是輸出報錯信息
exit 1
#退出程序,并返回值為Ⅰ(把返回值賦予變量$P)
elif [ ! -e "$file” ]
#判斷file的值是否存在
then
echo "Your input is not a file!"
#如1果不存在,則執行程序2
exit 2
#退出程序,把并定義返回值為2
elif [ -f "$file” ]
#判斷file的值是否為普通文件
then
echo "$file is a regulare file!”
#如果是普通文件,則執行程序3
elif [ -d "$file” ]
#到斷file的值是否為目錄文件
then
echo "$file is a directory!"
#如果是目錄文件,網執行程序4
else
echo "$file is an other file!”
#如果以上判斷都不是,則執行程序5
fi
2. 多分支case條件語句
case語句和if…elif…else語句一樣都是多分支條件語句,不過和if多分支條件語句不同的是,case語句只能判斷一種條件關系,而if語句可以判斷多種條件關系。
語法:
case $變量名 in
"值1")
如果變量的值等于值1,則執行程序1
;;
"值2")
如果變量的值等于值2,則執行程序2
::
…省略其他分支…
*)
如果變量的值都不是以上的值,則執行此程序
;;
esac
這個語句需要注意以下內容:
l case語句,會取出變量中的值,然后與語句體中的值逐一比較。如果數值符合,則執行對應的程序,如果數值不符,則依次比較下一個值。如果所有的值都不符合,則執行 “*)” (*代表所有其他值)中的程序。
l case語句以“case”開頭,以“esac”結尾。
每一個分支程序之后要通過“;;”雙分號結尾,代表該程序段結束(千萬不要忘記,每次寫case語句,都不要忘記雙分號)。
案例:
[root@localhost ~]$ vi sh/if-case.sh
#!/bin/bash
read -p "請輸入一個字符,并按Enter確認:" KEY
case "$KEY" in
[a-z]|[A-Z])
echo "您輸入的是字母"
;;
[0-9])
echo "您輸入的是數字"
;;
*)
echo "您輸入的是其他字符"
;;
esac
3. for循環
for循環是固定循環,也就是在循環時已經知道需要進行幾次的循環,有時也把for循環稱為計數循環。for的語法有如下兩種:
語法一:
for 變量 in 值1 值2 值3 …(可以是一個文件等)
do
程序
done
這種語法中for循環的次數,取決于in后面值的個數(空格分隔),有幾個值就循環幾次,并且每次循環都把值賦予變量。
也就是說,假設in后面有三個值,for會循環三次,第一次循環會把值1賦予變量,第二次循環會把值2賦予變量,以此類推。
語法二:
for (( 初始值;循環控制條件;變量變化 ))
do
程序
done
語法二中需要注意:
初始值:在循環開始時,需要給某個變量賦予初始值,如i=1;
循環控制條件:用于指定變量循環的次數,如i<=100,則只要i的值小于等于100,循環就會繼續;
變量變化:每次循環之后,變量該如何變化,如i=i+1。代表每次循環之后,變量i的值都加1。
語法一舉例:打印時間
[root@localhost ~]$ vi sh/for.sh
#!/bin/bash
#打印時間
for time in morning noon afternoon evening
do
echo "This time is $time!"
done
語法一舉例:批量解壓縮腳本
[root@localhost ~]$ vi sh/auto-tar. sh
#!/bin/bash
#批量解壓縮腳本
cd/lamp
#進入壓縮包目錄
ls *.tar.gz > ls.log
#把所有.tar.gz結尾的文件的文件覆蓋到ls.log 臨時文件中
for i in $(cat ls.log) `
#或者這樣寫for i in `cat ls.log`
#讀取ls.log文件的內容,文件中有多少個值,就會循環多少次,每次循環把文件名賦予變量i
do
tar -zxf $i &>/dev/null
#加壓縮,并把所有輸出都丟棄
done
rm -rf /lamp/ls.log
#刪除臨時文件ls.log
語法二舉例:從1加到100
[root@localhost ~]$ vi sh/add. sh
#!/bin/bash
#從1加到100
s=0
for (( i=1;i<=100;i=i+1 ))
#定義循環100 次
do
s=$(( $s+$i ))
#每次循環給變量s賦值
done
echo "The sum of 1+2+...+100 is : $s"
#輸出1加到100的和
語法二舉例:批量添加指定數量的用戶
[root@localhost ~]$ vi useradd.sh
#!/bin/bash
#批量添加指定數量的用戶
read -p "Please input user name: " -t 30 name
#讓用戶輸入用戶名,把輸入保存入變量name
read -p "Please input the number of users: " -t 30 num
#讓用戶輸入添加用戶的數量,把輸入保存入變量num
read -p "Please input the password of users: " -t 30 pass
#讓用戶輸入初始密碼,把輸入保存如變量pass
if [ ! -z "$name" -a ! -z "$num"-a ! -z "$pass"]
#判斷三個變量不為空
then
y=$(echo $num | sed 's/[0-9]//g')
#定義變量的值為后續命令的結果
#后續命令作用是,把變量num 的值替換為空。如果能替換為空,證明num 的值為數字
#如果不能替換為空,證明num的值為非數字。我們使用這種方法判斷變量num 的值為數字
if [ -z "$y"]
#如果變量y的值為空,證明num變量是數字
then
for (( i=1 ; i<=$num; i=i+1 ))
#循環num變量指定的次數
do
/usr/sbin/useradd $name$i &>/dev/null
#添加用戶,用戶名為變量name 的值加變量i的數字
echo $pass | /usr/bin/passwd --stdin $name$i &>/dev/null
#給用戶設定初始密碼為變量pass 的值
done
fi
fi
語法二舉例:批量刪除用戶
[root@localhost ~]$ vi sh/userdel.sh
#!/bin/bash
#批量刪除用戶
user=$(cat /etc/passwd | grep " /bin/bash"|grep -v "root"Icut -d ":" -f 1)
#讀取用戶信息文件,提取可以登錄用戶,取消root用戶,截取第一列用戶名
for i in $user
#循環,有多少個普通用戶,循環多少次
do
userdel -r $i
#每次循環,刪除指定普通用戶
done
4. while循環
語法:
while [ 條件判斷式 ]
do
程序
done
案例:1加到100
[root@localhost ~]$ vi sh/addnum.sh
#!/bin/bash
#從1加到100
i=1
s=0
#給變量i和變量s賦值
while [ $i -le 100 ]
#如果變量i的值小于等于100,則執行循環
do
s=$(( $s+$i ))
i=$(( $i+1 ))
done
echo "The sum is: $s"
案例:輸入的數值進行比較判斷
[root@localhost ~]$ vi sh/addnum.sh
#!/bin/bash
PRICE=$(expr $RANDOM % 1000)
TIMES=0
echo "商品的價格為0-999之間,猜猜看是多少?"
while true
do
read -p "請輸入您猜的價格:" INT
let TIMES++
if [ $INT -eq $PRICE ] ; then
echo "恭喜您猜對了,實際價格是 $PRICE"
echo "您總共猜了 $TIMES 次"
exit 0
elif [ $INT -gt $PRICE ] ; then
echo "太高了"
else
echo "太低了"
fi
done
5. until循環
和while循環相反,until循環時只要條件判斷式不成立則進行循環,并執行循環程序。一旦循環條件成立,則終止循環。
語法:
until [ 條件判斷式 ]
do
程序
done
案例一:1加到100
[root@localhost ~]$ vi sh/until.sh
#!/bin/bash
#從1加到100
i=1
s=0
#t給變量i和變量s賦值
until [ $i -gt 100 ]
#循環直到變量i的值大于100,就停止循環
do
s=$(( $s+$i ))
i=$(( $i+1 ))
done
echo "The sum is: $s"
6. 函數
語法:
function 函數名 () {
程序
}
案例:接收用戶輸入的數字,然后從1加到這個數字
[root@localhost ~]$ vi sh/function.sh
#!/bin/bash
#接收用戶輸入的數字,然后從1加到這個數字
function sum () {
#定義函數sum
s=0
for (( i=0; i<=$num;i=i+1 ))
#循環直到i大于$1為止。$1是函數sum 的第一個參數
#在函數中也可以使用位置參數變量,不過這里的$1指的是函數的第一個參數
do
s=$(( $i+$s ))
done
echo "The sum of 1+2+3...+$1 is :$s"
#輸出1加到$1的和
}
read -p "Please input a number: " -t 30 num
#接收用戶輸入的數字,并把值賦予變量num
y=$(echo $num | sed 's/[0-9]//g')
#把變量num的值替換為空,并賦予變量y
if [ -z "$y"]
#判斷變量y是否為空,以確定變量num中是否為數字
then
sum $num
#調用sum函數,并把變量num的值作為第一個參數傳遞給sum函數
else
echo "Error!! Please input a number!"
#如果變量num 的值不是數字,則輸出報錯信息
fi
7. 特殊流程控制語句
7.1 exit語句
系統是有exit命令的,用于退出當前用戶的登錄狀態。可是在Shell腳本中,exit語句是用來退出當前腳本的。也就是說,在Shell腳本中,只要碰到了exit語句,后續的程序就不再執行,而直接退出腳本。
exit的語法如下:
exit [返回值]
如果exit命令之后定義了返回值,那么這個腳本執行之后的返回值就是我們自己定義的返回值。可以通過查詢$?這個變量,來查看返回值。如果exit之后沒有定義返回值,腳本執行之后的返回值是執行exit 語句之前,最后執行的一條命令的返回值。寫一個exit 的例子:
[root@localhost ~]$ vi sh/exit.sh
#!/bin/bash
#演示exit的作用
read -p "Please input a number: " -t 30 num
#接收用戶的輸入,并把輸入賦予變量num
y=$ (echo $num | sed 's/[0-9]//g')
#如果變量num 的值是數字,則把num的值替換為空,否則不替換
#把替換之后的值賦予變量y
[ -n "$y" ] && echo "Error! Please input a number!" && exit 18
#判斷變量y的值如果不為空,輸出報錯信息,退出腳本,退出返回值為18
echo "The number is: $num"
#如果沒有退出加班,則打印變量num中的數字
7.2 break語句
當程序執行到break語句時,會結束整個當前循環。而continue 語句也是結束循環的語句,不過continue 語句單次當前循環,而下次循環會繼續。
案例:
[root@localhost ~]$ vi sh/break.sh
#!/bin/bash
#演示break 跳出循環
for (( i=1;i<=10; i=i+1 ))
#循環十次
do
if ["$i" -eq 4 ]
#如果變量i的值等于4
then
break
#退出整個循環
fi
echo $i
#輸出變量i的值
done
執行下這個腳本,因為一旦變量i的值等于4,整個循環都會跳出,所以應該只能循環三次:
[root@localhost ~]$ chmod 755 sh/break.sh
[root@localhost ~]#sh/break.sh
1
2
3
7.3 continue語句
continue也是結束流程控制的語句。如果在循環中,continue語句只會結束單次當前循環。
案例:
[root@localhost ~]$ vi sh/break.sh
#!/bin/bash
#演示continue
for (( i=1;i<=10;i=i+1 ))
#循環十次
do
if ["$i" -eq 4 ]
#如果變量i的值等于4
then
continue
#退出換成continue
fi
echo $i
#輸出變量i的值
done
執行下這個腳本:
[root@localhost ~]$ chmod 755 sh/continue.sh
[root@localhost ~]#sh/break.sh
1
2
3
5
6
7
8
9
10
#少了4這個輸出
六、字符截取、替換和處理命令
正則表達式
元字符 |
描述 |
示例 |
|
轉義符,將特殊字符進行轉義,忽略其特殊意義 |
a.b匹配a.b,但不能匹配ajb,.被轉義為特殊意義 |
^ |
匹配行首,awk中,^則是匹配字符串的開始 |
^tux匹配以tux開頭的行 |
$ |
匹配行尾,awk中,$則是匹配字符串的結尾 |
tux$匹配以tux結尾的行 |
. |
匹配除換行符n之外的任意單個字符 |
ab.匹配abc或abd,不可匹配abcd或abde,只能匹配單字符 |
[ ] |
匹配包含在[字符]之中的任意一個字符 |
coo[kl]可以匹配cook或cool |
[^] |
匹配[^字符]之外的任意一個字符 |
123[^45]不可以匹配1234或1235,1236、1237都可以 |
[-] |
匹配[]中指定范圍內的任意一個字符,要寫成遞增 |
[0-9]可以匹配1、2或3等其中任意一個數字 |
? |
匹配之前的項1次或者0次 |
colou?r可以匹配color或者colour,不能匹配colouur |
+ |
匹配之前的項1次或者多次 |
sa-6+匹配sa-6、sa-666,不能匹配sa- |
* |
匹配之前的項0次或者多次 |
co*l匹配cl、col、cool、coool等 |
() |
匹配表達式,創建一個用于匹配的子串 |
ma(tri)?匹配max或maxtrix |
{n} |
匹配之前的項n次,n是可以為0的正整數 |
[0-9]{3}匹配任意一個三位數,可以擴展為[0-9][0-9][0-9] |
{n,} |
之前的項至少需要匹配n次 |
[0-9]{2,}匹配任意一個兩位數或更多位數不支持{n,}{n,}{n,} |
{n,m} |
指定之前的項至少匹配n次,最多匹配m次,n<=m |
[0-9]{2,5}匹配從兩位數到五位數之間的任意一個數字 |
| |
交替匹配|兩邊的任意一項 |
ab(c|d)匹配abc或abd |
1 字符截取、替換命令
1.1 cut 列提取命令
[root@localhost ~]$ cut [選項] 文件名
選項:
-f 列號: 提取第幾列
-d 分隔符: 按照指定分隔符分割列
-n取消分割多字節字符
-c 字符范圍: 不依賴分隔符來區分列,而是通過字符范圍(行首為0)來進行字段提取。“n-”表示從第n個字符到行尾;“n-m”從第n個字符到第m個字符;“一m”表示從第1個字符到第m個字符。
--complement補足被選擇的字節、字符或字段
--out-delimiter指定輸出內容是的字段分割符
cut命令的默認分隔符是制表符,也就是“tab”鍵,不過對空格符可是支持的不怎么好啊。我們先建立一個測試文件,然后看看cut命令的作用吧:
[root@localhost ~]$ vi student.txt
idnamegendermark
1limingm86
2scm67
3tgn90
[root@localhost ~]$ cut -f 2 student.txt
#提取第二列內容
那如果想要提取多列呢?只要列號直接用“,”分開,命令如下:
[root@localhost ~]$ cut -f 2,3 student.txt
如果我想用cut命令截取df命令的第一列和第三列,就會出現這樣的情況:
[root@localhost~]$ df -h | cut -d " " -f 1,3
Filesystem
/dev/sda2
tmpfs
/dev/sda1
1.2 awk 編程
參考:
linux文本處理三劍客:grep,sed及awk
1.2.1 awk 概述
AWK 是一種處理文本文件的語言,是一個強大的文本分析工具。
1.2.2 printf 格式化輸出
[root@localhost ~]$ printf ‘輸出類型輸出格式’ 輸出內容
輸出類型:
%c: ASCII字符.顯示相對應參數的第一個字符
%-ns: 輸出字符串,減號“-”表示左對齊(默認右對齊),n是數字指代輸出幾個字符,幾個參數就寫幾個%-ns
%-ni: 輸出整數,n是數字指代輸出幾個數字
%f: 輸出小數點右邊的位數
%m.nf: 輸出浮點數,m和n是數字,指代輸出的整數位數和小數位數。如%8.2f代表共輸出8位數,其中2位是小數,6位是整數。
輸出格式:
a: 輸出警告聲音
b: 輸出退格鍵,也就是Backspace鍵
f: 清除屏幕
n: 換行
r: 回車,也就是Enter鍵
t: 水平輸出退格鍵,也就是Tab 鍵
v: 垂直輸出退格鍵,也就是Tab 鍵
為了演示printf命令,我們需要修改下剛剛cut命令使用的student.txt文件,文件內容如下:
[root@localhost ~]$ vi student.txt
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
2 BBB 77 77 77 77
3 CCC 88 88 88 88
#printf格式輸出文件
[root@localhost ~]$ printf '%st %st %st %st %st %st n’ $(cat student.txt)
#%s分別對應后面的參數,6列就寫6個
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
2 BBB 77 77 77 77
3 CCC 88 88 88 88
如果不想把成績當成字符串輸出,而是按照整型和浮點型輸出,則要這樣:
[root@localhost ~]$ printf '%it %st %it %it %it %8.2ft n’ $(cat student.txt | grep -v Name)
1.2.3 awk 基本使用
[root@localhost ~]$ awk‘條件1{動作1} 條件2{動作2}…’ 文件名
條件(Pattern):
一般使用關系表達式作為條件。這些關系表達式非常多,例如:
x > 10 判斷變量x是否大于10
x == y 判斷變量x是否等于變量y
A ~ B 判斷字符串A中是否包含能匹配B表達式的子字符串
A !~ B 判斷字符串A中是否不包含能匹配B表達式的子字符串
動作(Action) :
格式化輸出
流程控制語句
常用參數:
-F指定輸入時用到的字段分隔符
-v自定義變量
-f從腳本中讀取awk命令
-m對val值設置內在限制
我們這里先來學習awk基本用法,也就是只看看格式化輸出動作是干什么的。
[root@localhost ~]$ awk '{printf $2 "t" $6 "n"}’ student.txt
#輸出第二列和第六列
比如剛剛截取df命令的結果時,cut命令已經力不從心了,我們來看看awk命令:
[root@localhost ~]$ df -h | awk '{print $1 "t" $3}'
#截取df命令的第一列和第三列
1.2.4 awk 的條件
條件的類型 |
條件 |
說明 |
awk保留字 |
BEGIN |
在awk程序一開始時,尚未讀取任何數據之前執行。BEGIN后的動作只在程序開始時執行一次 |
awk保留字 |
END |
在awk程序處理完所有數據,即將結束時執行。END后的動作只在程序結束時執行一次 |
關系運算符 |
> |
大于 |
關系運算符 |
< |
小于 |
關系運算符 |
>= |
大于等于 |
關系運算符 |
<= |
小于等于 |
關系運算符 |
== |
等于。用于判斷兩個值是否相等,如果是給變量賦值,請使用“”號 |
關系運算符 |
!= |
不等于 |
關系運算符 |
A~B |
判斷字符串A中是否包含能匹配B表達式的子字符串 |
關系運算符 |
A!~B |
判斷字符串A中是否不包含能匹配B表達式的子字符串 |
正則表達式 |
/正則/ |
如果在"//"中可以寫入字符,也可以支持正則表達式 |
BEGIN
BEGIN是awk的保留字,是一種特殊的條件類型。BEGIN的執行時機是“在 awk程序一開始時,尚未讀取任何數據之前執行”。一旦BEGIN后的動作執行一次,當awk開始從文件中讀入數據,BEGIN的條件就不再成立,所以BEGIN定義的動作只能被執行一次。
例如:
[root@localhost ~]$ awk 'BEGIN{printf "This is a transcript n" } {printf $2 "t" $6 "n"}’ student.txt
#awk命令只要檢測不到完整的單引號不會執行,所以這個命令的換行不用加入“|”,就是一行命令
#這里定義了兩個動作
#第一個動作使用BEGIN條件,所以會在讀入文件數據前打印“這是一張成績單”(只會執行一次)
#第二個動作會打印文件的第二字段和第六字段
END
END也是awk保留字,不過剛好和BEGIN相反。END是在awk程序處理完所有數據,即將結束時執行。END后的動作只在程序結束時執行一次。例如:
[root@localhost ~]$ awk 'END{printf "The End n"} {printf $2 "t" $6 "n"}’ student.txt
#在輸出結尾輸入“The End”,這并不是文檔本身的內容,而且只會執行一次
關系運算符
舉幾個例子看看關系運算符。假設我想看看平均成績大于等于87分的學員是誰,就可以這樣輸入命令:
例子1:
[root@localhost ~]$ cat student.txt | grep -v Name | awk '$6 >= 87 {printf $2 "n"}'
#使用cat輸出文件內容,用grep取反包含“Name”的行
#判斷第六字段(平均成績)大于等于87分的行,如果判斷式成立,則打第六列(學員名$2)
加入了條件之后,只有條件成立動作才會執行,如果條件不滿足,則動作則不運行。通過這個實驗,大家可以發現,雖然awk是列提取命令,但是也要按行來讀入的。這個命令的執行過程是這樣的:
1)如果有BEGIN條件,則先執行BEGIN定義的動作。
2)如果沒有BEGIN條件,則讀入第一行,把第一行的數據依次賦予$0、$1、$2等變量。其中$0代表此行的整體數據,$1代表第一字段,$2代表第二字段。
3)依據條件類型判斷動作是否執行。如果條件符合,則執行動作,否則讀入下一行數據。如果沒有條件,則每行都執行動作。
4)讀入下一行數據,重復執行以上步驟。
再舉個例子,如果我想看看Sc用戶的平均成績呢:
例子2:
[root@localhost ~]$ awk '$2 ~ /AAA/ {printf $6 "n"}' student.txt
#如果第二字段中輸入包含有“Sc”字符,則打印第六字段數據
85.66
這里要注意在awk中,使用“//”包含的字符串,awk命令才會查找。也就是說字符串必須用“//”包含,awk命令才能正確識別。
正則表達式
如果要想讓awk 識別字符串,必須使用“//”包含,例如:
例子1:
[root@localhost ~]$ awk '/Liming/ {print}’student.txt
#打印Liming的成績
當使用df命令查看分區使用情況是,如果我只想查看真正的系統分區的使用狀況,而不想查看光盤和臨時分區的使用狀況,則可以:
例子2:
[root@localhost ~]$ df -h | awk '/sda[O-9]/ {printf $1 "t" $5 "n"}’
#查詢包含有sda數字的行,并打印第一字段和第五字段
1.2.5 awk 內置變量
awk內置變量 |
作用 |
$0 |
代表目前awk所讀入的整行數據。我們已知awk是一行一行讀入數據的,$0就代表當前讀入行的整行數據。 |
$n |
代表目前讀入行的第n個字段。比如, $1表示第1個字段(列),$ 2表示第2個字段(列),如此類推 |
NF |
當前行擁有的字段(列)總數。 |
NR |
當前awk所處理的行,是總數據的第幾行。 |
FS |
用戶定義分隔符。awk的默認分隔符是任何空格,如果想要使用其他分隔符(如“:”),就需要FS變量定義。 |
ARGC |
命令行參數個數。 |
ARGV |
命令行參數數組。 |
FNR |
當前文件中的當前記錄數(對輸入文件起始為1)。 |
OFMT |
數值的輸出格式(默認為%.6g)。 |
OFS |
輸出字段的分隔符(默認為空格)。 |
ORS |
輸出記錄分隔符(默認為換行符)。 |
RS |
輸入記錄分隔符(默認為換行符)。 |
awk常用統計實例
1、打印文件的第一列(域) :
awk '{print $1}' filename
2、打印文件的前兩列(域) :
awk '{print $1,$2}' filename
3、打印完第一列,然后打印第二列 :
awk '{print $1 $2}' filename
4、打印文本文件的總行數 :
awk 'END{print NR}' filename
5、打印文本第一行 :
awk 'NR==1{print}' filename
6、打印文本第二行第一列 :
sed -n "2, 1p" filename | awk 'print $1'
1. 獲取第一列
ps -aux | grep watchdog | awk '{print $1}'
2. 獲取第一列,第二列,第三列
ps -aux | grep watchdog | awk '{print $1, $2, $3}'
3. 獲取第一行的第一列,第二列,第三列
ps -aux | grep watchdog | awk 'NR==1{print $1, $2, $3}'
4. 獲取行數NR
df -h | awk 'END{print NR}'
5. 獲取列數NF(這里是獲取最后一行的列數,注意每行的列數可能是不同的)
ps -aux | grep watchdog | awk 'END{print NF}'
6. 獲取最后一列
ps -aux | grep watchdog | awk '{print $NF}'
7. 對文件進行操作
awk '{print $1}' fileName
8. 指定分隔符(這里以:分割)
ps -aux | grep watchdog |awk -F':' '{print $1}'
9. 超出范圍不報錯
ps -aux | grep watchdog | awk '{print $100}'
[root@localhost ~]$ cat /etc/passwd | grep "/bin/bash" | awk '{FS=":"} {printf $1 "t" $3 "n"}’
#查詢可以登錄的用戶的用戶名和UID
這里“:”分隔符生效了,可是第一行卻沒有起作用,原來我們忘記了“BEGIN”條件,那么再來試試;
[root@localhost ~]$ cat /etc/passwd | grep "/bin/bash" | awk 'BEGIN {FS=":"} {printf $1 "t" $3 "n"}’
[root@localhost ~]$ cat /etc/passwd | grep "/bin/bash" | awk 'BEGIN {FS=":"} {printf $1 "t" $3 "t 行號:” NR "t 字段數:" NF "n"}’
#解釋下awk命令
#開始執行{分隔符是“:”}{輸出第一字段和第三字段輸出行號(NR值)字段數(NF值)}
root 0 行號:1 字段數:7
user1 501 行號:2 字段數:7
如果我只想看看sshd這個偽用戶的相關信息,則可以這樣使用:
[root@localhost ~]$ cat /etc/passwd | awk 'BEGIN {FS=":"} $1=="sshd" {printf $1 "t" $3 "t 行號:" NR "t 字段數:" NF "n"}’
#可以看到sshd 偽用戶的UID是74,是/etc/passwd_文件的第28行,此行有7個字段
1.2.6 awk 流程控制
我們再來利用下student.txt文件做個練習,后面的使用比較復雜,我們再看看這個文件的內容:
[root@localhost ~]$ cat student.txt
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
2 BBB 77 77 77 77
3 CCC 88 88 88 88
我們先來看看該如何在awk中定義變量與調用變量的值。假設我想統計PHP成績的總分,那么就應該這樣:
[root@localhost ~]$ awk 'NR==2 {php1=$3}
NR==3 {php2=$3}
NR==4 {php3=$3;totle=phpl+php2+php3;print "totle php is " totle}’ student.txt
#統計PHIP成績的總分
我們解釋下這個命令。
"NR==2 {php1=$3}" (條件是NR==2,動作是php1=$3)這句話是指如果輸入數據是第二行(第一行是標題行),就把第二行的第三字段的值賦予變量"php1"。
"NR==3 {php2=$3}" 這句話是指如果輸入數據是第三行,就把第三行的第三字段的值賦予變量"php2"。
"NR==4 {php3=$3;totle=phpl+php2+php3;print "totle php is " totle}"("NR==4"是條件,后面{}中的都是動作)這句話是指如果輸入數據是第四行﹐就把第四行的第三字段的值賦予變量"php3";然后定義變量totle的值是"php1+php2+php3";然后輸出"totle php is"關鍵字,后面加變量totle的值。
在awk編程中,因為命令語句非常長,在輸入格式時需要注意以下內容:
l 多個條件 {動作} 可以用空格分割,也可以用回車分割。
l 在一個動作中,如果需要執行多個命令,需要用 “;” 分割,或用回車分割。
l 在awk中,變量的賦值與調用都不需要加入“$”符。
l 條件中判斷兩個值是否相同,請使用 “==”,以便和變量賦值進行區分。
在看看該如何實現流程控制,假設如果Linux成績大于90,就是一個好男人(學PHP的表示壓力很大!) :
[root@localhost ~]$ awk '{if (NR>=2) {if ($4>60) printf $2 "is a good man!n"}}’ student.txt
#程序中有兩個if判斷,第一個判斷行號大于2,第二個判斷Linux成績大于90分
Liming is a good man !
Sc is a good man !
1.2.7 awk 函數
awk編程也允許在編程時使用函數,我們講講awk的自定義函數。awk函數的定義方法如下:
function 函數名(參數列表){
函數體
}
我們定義一個簡單的函數,使用函數來打印student.txt的學員姓名和平均成績,應該這樣來寫函數:
[root@localhost ~]$ awk 'function test(a,b) { printf a "t" b "n"}
#定義函數test,包含兩個參數,函數體的內容是輸出這兩個參數的值
{ test($2,$6) } ' student.txt
#調用函數test,并向兩個參數傳遞值。
Name Average
AAA 87.66
BBB 85.66
CCC 91.66
1.2.8 awk 中調用腳本
對于小的單行程序來說,將腳本作為命令行自變量傳遞給awk是非常簡單的,而對于多行程序就比較難處理。當程序是多行的時候,使用外部腳本是很適合的。首先在外部文件中寫好腳本,然后可以使用awk的-f選項,使其讀入腳本并且執行。
例如,我們可以先編寫一個awk腳本:
[root@localhost ~]$ vi pass.awk
BEGIN {FS=":"}
{ print $1 "t" $3}
然后可以使用“一f”選項來調用這個腳本:
[root@localhost ~]$ awk -f pass.awk /etc/passwd
rooto
bin1
daemon2
…省略部分輸出…
1.3 sed 文本選取、替換、刪除、新增的命令
sed主要是用來將數據進行選取、替換、刪除、新增的命令。
語法:
[root@localhost ~]$ sed [選項] ‘[動作]’ 文件名
選項:
-n: 一般sed命令會把所有數據都輸出到屏幕,如果加入此選擇,則只會把經過sed命令處理的行輸出到屏幕。
-e: 允許對輸入數據應用多條sed命令編輯。
-f 腳本文件名: 從sed腳本中讀入sed操作。和awk命令的-f非常類似。
-r: 在sed中支持擴展正則表達式。
-i: 用sed的修改結果直接修改讀取數據的文件,而不是由屏幕輸出
動作:
num a : 追加,在當前行后添加一行或多行。添加多行時,除最后一行外,每行末尾需要用“”代表數據未完結。num表示第幾行
c : 行替換,用c后面的字符串替換原數據行,替換多行時,除最后一行外,每行末尾需用“”代表數據未完結。
num i : 插入,在當期行前插入一行或多行。插入多行時,除最后一行外,每行末尾需要用“”代表數據未完結。num表示第幾行
d ; 刪除,刪除指定的行。
p : 打印,輸出指定的行。
s : 字串替換,用一個字符串替換另外一個字符串。格式為“行范圍s/"舊字串/新字串/g”(和vim中的替換格式類似)。
對sed命令大家要注意,sed所做的修改并不會直接改變文件的內容(如果是用管道符接收的命令的輸出,這種情況連文件都沒有),而是把修改結果只顯示到屏幕上,除非使用“-i”選項才會直接修改文件。
2 字符處理命令
2.1 sort 排序命令
[root@localhost~]$ sort [選項] 文件名
選項:
-f: 忽略大小寫
-b: 忽略每行前面的空白部分
-n: 以數值型進行排序,默認使用字符串型排序
-r: 反向排序
-u: 刪除重復行。就是uniq命令
-t: 指定分隔符,默認是分隔符是制表符
-k n[,m]: ―按照指定的字段范圍排序。從第n字段開始,m字段結束(默認到行尾)
案例:
sort命令默認是用每行開頭第一個字符來進行排序的,比如:
[root@localhost~]$ sort /etc/passwd
#排序用戶信息文件
如果想要反向排序,請使用“-r”選項:
[root@localhost~]$ sort -r/etc/passwd
#反向排序
如果想要指定排序的字段,需要使用“-t”選項指定分隔符,并使用“-k”選項指定字段號。加入我想要按照UID字段排序/etc/passwd文件:
[root@localhost~]$ sort -t ":" -k 3,3 /etc/passwd
#指定分隔符是“:”,用第三字段開頭,第三字段結尾排序,就是只用第三字段排序
因為sort默認是按照字符排序,前面用戶的UID的第一個字符都是1,所以這么排序。要想按照數字排序,請使用“-n”選項:
[root@localhost~]$ sort -n -t ":" -k 3,3 /etc/passwd
當然“-k”選項可以直接使用“-k 3”,代表從第三字段到行尾都排序(第一個字符先排序,如果一致,第二個字符再排序,知道行尾)。
2.2 uniq 取消重復行
uniq [-c/d/D/u/i] [-f Fields] [-s N] [-w N] [InFile] [OutFile]
參數解釋:
-c: 在每列旁邊顯示該行重復出現的次數。
-d: 僅顯示重復出現的行列,顯示一行。
-D: 顯示所有重復出現的行列,有幾行顯示幾行。
-u: 僅顯示出一次的行列
-i: 忽略大小寫字符的不同
-f Fields: 忽略比較指定的列數。
-s N: 忽略比較前面的N個字符。
-w N: 對每行第N個字符以后的內容不作比較。
# uniq.txt
My name is Delav
My name is Delav
My name is Delav
I'm learning JAVA
I'm learning Java
I'm learning Java
who am i
Who am i
Python/ target=_blank class=infotextkey>Python is so simple
My name is Delav
That's good
That's good
And studying Golang
直接去重
uniq uniq.txt
結果為:
My name is Delav
I'm learning Java
who am i
Who am i
Python is so simple
My name is Delav
That's good
And studying Golang
顯示重復出現的次數
uniq -c uniq.txt 、
結果為:
3 My name is Delav
3 I'm learning Java
1 who am i
1 Who am i
1 Python is so simple
1 My name is Delav
2 That's good
1 And studying Golang
你會發現,上面有兩行 ”My name is Delav ” 是相同的。也就是說,當重復的行不相鄰時,uniq 命令是不起作用的。所以,經常需要跟
sort 命令一起使用。
sort uniq.txt | uniq -c
結果為:
1 And studying Golang
3 I'm learning Java
4 My name is Delav
1 Python is so simple
2 That's good
1 who am i
1 Who am i
只顯示重復的行,并顯示重復次數
uniq -cd uniq.txt
結果為:
3 My name is Delav
3 I'm learning Java
2 That's good
顯示所有重復的行,不能與 -c 一起使用
uniq -D uniq.txt
My name is Delav
My name is Delav
My name is Delav
I'm learning Java
I'm learning Java
I'm learning Java
That's good
That's good
忽略第幾列字符
下面這里 -f 1 忽略了第一列字符,所以"who am i" 和 "Who am i" 判定為重復
uniq -c -f 1 uniq.txt
結果為:
3 My name is Delav
3 I'm learning Java
2 who am i
1 Python is so simple
1 My name is Delav
2 That's good
1 And studying Golang
忽略大小寫
下面這里 -i 忽略了大小寫,所以"who am i" 和 "Who am i" 判定為重復
uniq -c -i uniq.txt
結果為:
3 My name is Delav
3 I'm learning Java
2 who am i
1 Python is so simple
1 My name is Delav
2 That's good
1 And studying Golang
忽略前面N個字符
下面這里 -s 4 表示忽略前面四個字符,所以"who am i" 和 "Who am i" 判定為重復
uniq -c -s 4 uniq.txt
結果為:
3 My name is Delav
3 I'm learning Java
2 who am i
1 Python is so simple
1 My name is Delav
2 That's good
1 And studying Golang
忽略第N個字符后的內容
uniq -c -w 2 uniq.txt
2.3 wc 統計命令
[root@localhost~]$ wc [選項] 文件名
選項:
-l:只統計行數
-w:只統計單詞數
-m:只統計字符數