您好,登錄后才能下訂單哦!
說到Shell編程,很多從事Linux運維工作的朋友都不陌生,都對Shell有基本的了解,讀者可能剛開始接觸Shell的時候,有各種想法,感覺編程非常困難,SHELL編程是所有編程語言中最容易上手,最容易學習的編程腳本語言。
本章向讀者介紹Shell編程入門、Shell編程變量、If、While、For、Case、Select基本語句案例演練及Shell編程四劍客Find、Grep、Awk、Sed深度剖析等。
曾經有人說過,學習Linux不知道Shell編程,那就是不懂Linux,現在細細品味確實是這樣。Shell是操作系統的最外層,Shell可以合并編程語言以控制進程和文件,以及啟動和控制其它程序。
Shell 通過提示您輸入,向操作系統解釋該輸入,然后處理來自操作系統的任何結果輸出,簡單來說Shell就是一個用戶跟操作系統之間的一個命令解釋器。
Shell是用戶與Linux操作系統之間溝通的橋梁,用戶可以輸入命令執行,又可以利用 Shell腳本編程去運行,如圖所示:
Shell、用戶及Kernel位置關系
Linux Shell種類非常多,常見的SHELL如下:
Bourne Shell(/usr/bin/sh或/bin/sh)
Bourne Again Shell(/bin/bash)
C Shell(/usr/bin/csh)
K Shell(/usr/bin/ksh)
Shell for Root(/sbin/sh)
不同的Shell語言的語法有所不同,一般不能交換使用,最常用的shell是Bash,也就是Bourne Again Shell。Bash由于易用和免費,在日常工作中被廣泛使用,也是大多數Linux操作系統默認的Shell環境。
Shell、Shell編程、Shell腳本、Shell命令之間都有什么區別呢?
簡單來說Shell是一個整體的概念,Shell編程與Shell腳本統稱為Shell編程,Shell命令是Shell編程底層具體的語句和實現方法。
要熟練掌握Shell編程語言,需要大量的練習,初學者可以用Shell打印“Hello World”字符,寓意著開始新的啟程!
Shell腳本編程需要如下幾個事項:
Shell腳本名稱命名一般為英文、大寫、小寫;
不能使用特殊符號、空格來命名;
Shell腳本后綴以.sh結尾;
不建議Shell命名為純數字,一般以腳本功能命名。
Shell腳本內容首行需以#!/bin/bash開頭;
Shell腳本中變量名稱盡量使用大寫字母,字母間不能使用“-”,可以使用“_”;
Shell腳本變量名稱不能以數字、特殊符號開頭。
如下為第一個Shell編程腳本,腳本名稱為:first_shell.sh,代碼內容如下:
#!/bin/bash
#This is my First shell
#By author test.net
echo “Hello World ”
First_shell.sh腳本內容詳解如下:
#!/bin/bash???????????? #固定格式,定義該腳本所使用的Shell類型;
#This is my First shell???? #號表示注釋,沒有任何的意義,SHELL不會解析它;
#By author test.net #表示腳本創建人,#號表示注解;
echo “Hello World !” #Shell腳本主命令,執行該腳本呈現的內容。
Shell腳本編寫完畢,如果運行該腳本,運行用戶需要有執行權限,可以使用chmod o+x first_shell.sh賦予可執行權限。然后./first_shell.sh執行即可,還可以直接使用命令執行: /bin/sh first_shell.sh直接運行腳本,不需要執行權限,最終腳本執行顯示效果一樣。
初學者學習Shell編程,可以將在Shell終端運行的各種命令依次寫入到腳本內容中,可以把Shell腳本當成是Shell命令的堆積。
再介紹下字符串輸出顏色,有時候關鍵地方需要醒目,顏色是最好的方式:
字體顏色
30:黑
31:紅
32:綠
33:黃
34:藍色
35:紫色
36:深綠
37:白色
字體背景顏色
40:黑
41:深紅
42:綠
43:黃色
44:藍色
45:紫色
46:深綠
47:白色
顯示方式
0:終端默認設置
1:高亮顯示
4:下劃線
5:閃爍
7:反白顯示
8:隱藏
格式:
\033[1;31;40m?? # 1 是顯示方式,可選。31 是字體顏色。40m 是字體背景顏色。
\033[0m???????????? # 恢復終端默認顏色,即取消顏色設置。
示例:
#!/bin/bash
# 字體顏色
for i in {31..37}; do
echo -e "\033[$i;40mHello world!\033[0m"
done
# 背景顏色
for i in {41..47}; do
echo -e "\033[47;${i}mHello world!\033[0m"
done
# 顯示方式
for i in {1..8}; do
echo -e "\033[$i;31;40mHello world!\033[0m"
done
示例如圖:
測試單個案例
[root@localhost ~]# echo -e '\033[31;40mwww.test.net!\033[0m'
www.test.net!
Shell是非類型的解釋型語言,不像C++、JAVA語言編程時需要事先聲明變量,Shell給一個變量賦值,實際上就是定義了變量,在Linux支持的所有shell中,都可以用賦值符號(=)為變量賦值,Shell變量為弱類型,定義變量不需要聲明類型,但在使用時需要明確變量的類型,可以使用Declare指定類型。
Declare常見參數有:
+/- "-"可用來指定變量的屬性,"+"為取消變量所設的屬性;
-f 僅顯示函數;
r 將變量設置為只讀;
x 指定的變量會成為環境變量,可供shell以外的程序來使用;
i 指定類型為數值,字符串或運算式。
Shell編程中變量分為三種,分別是系統變量、環境變量和用戶變量,Shell變量名在定義時,首個字符必須為字母(a-z,A-Z),不能以數字開頭,中間不能有空格,可以使用下劃線(_),不能使用(-),也不能使用標點符號等。當腳本中使用某個字符串較頻繁并且字符串長度很長時就應該使用變量代替。
例如定義變量A=test.net,定義這樣一個變量,A為變量名,test.net是變量的值,變量名有格式規范,變量的值可以隨意指定。變量定義完成,如需要引用變量,可以使用$A。
如下腳本var.sh腳本內容如下:
#!/bin/bash
#By author test.net
A=123
echo “Printf variables is $A.”
執行該Shell腳本,結果將會顯示:Printf variables is 123。
Shell腳本中的變量其他使用還有很多例如:
使用條件語句時,常使用變量 if [ $i -gt 1 ]; then ... ; fi。
引用某個命令的結果時,用變量替代 n=wc -l test.txt。
寫和用戶交互的腳本時,變量也是必不可少的,read -p "Input a number: " i; echo $i 如果沒寫這個i,可以直接使用$REPLY。
內置變量 $0, $1, $2… $0表示腳本本身,$1 第一個參數,$2 第二個 .... $#表示參數個數。
數學運算a=2;b=3; c=$(($a+$b))或者$[$a+$b]。
Shell常見的變量之一系統變量,主要是用于對參數判斷和命令返回值判斷時使用,系統變量詳解如下:
$0 當前腳本的名稱;
$n 當前腳本的第n個參數,n=1,2,…9;
$* 當前腳本的所有參數(不包括程序本身);
$@???????????? 傳遞給腳本或函數的所有參數。被雙引號(" ")包含時,與 $* 稍有不同
$# 當前腳本的參數個數(不包括程序本身);
$? 命令或程序執行完后的狀態,返回0表示執行成功;
$$ 程序本身的PID號。
注意:
$* 和 $@ 的區別是什么?
$* 和 $@ 都表示傳遞給函數或腳本的所有參數,不被雙引號(" ")包含時,都以"$1" "$2" … "$n" 的形式輸出所有參數。
但是當它們被雙引號(" ")包含時,"$*" 會將所有的參數作為一個整體(強調整體),以"$1 $2 … $n"的形式輸出所有參數;即當成一個整體輸出,每一個變量參數之間以空格隔開。
"$@" 會將各個參數分開(強調獨立),以"$1" "$2" … "$n" 的形式輸出所有參數。即每一個變量參數是獨立的 。當然也是全部輸出。
我們可以在for語句中使用雙引號" "看出兩個變量的區別,
Shell腳本如下:
#!/bin/bash
#By author test.net test
for i in "$*";do
echo $i
done
echo "================="
for i in "$@";do
echo $i
done
執行測試:
[root@localhost ~]# bash test_num.sh 1 2 3 4 5
1 2 3 4 5
=================
1
2
3
4
5
Shell常見的變量之二環境變量,主要是在程序運行時需要設置,環境變量詳解如下:
PATH 命令所示路徑,以冒號為分割;
HOME 打印用戶家目錄;
SHELL 顯示當前Shell類型;
USER 打印當前用戶名;
ID?? 打印當前用戶id信息;
PWD?? 顯示當前所在路徑;
TERM 打印當前終端類型;
HOSTNAME???? 顯示當前主機名。
環境變量相關文件:
系統級:
系統級變量文件對所有用戶生效。
/etc/profile # 系統范圍內的環境變量和啟動文件。不建議把要做的事情寫在這里面,最好創建一個自定義的,放在/etc/profile.d 下
/etc/bashrc # 系統范圍內的函數和別名
用戶級:
用戶級變量文件對自己生效,都在自己家目錄下。
~/.bashrc # 用戶指定別名和函數
~/.bash_logout # 用戶退出執行
~/.bash_profile # 用戶指定變量和啟動程序
~/.bash_history # 用戶執行命令歷史文件
開啟啟動腳本順序:/etc/profile -> /etc/profile.d/*.sh -> ~/.bash_profile -> ~/.bashrc ->
/etc/bashrc
因此,我們可以把寫的腳本放到以上文件里執行。
Shell常見的變量之三用戶變量,用戶變量又稱為局部變量,主要用在Shell腳本內部或者臨時局部使用,系統變量詳解如下:
A=test.net???????????????????? 自定義變量A;
N_SOFT=nginx-1.12.0.tar.gz?????? 自定義變量N_SOFT;
BACK_DIR=/data/backup/?????????? 自定義變量BACK_DIR;
IP1=192.168.1.11?????????????? 自定義變量IP1;
IP2=192.168.1.12?????????????? 自定義變量IP2。
創建Echo打印菜單Shell腳本,腳本代碼如下:
#!/bin/bash
#auto install nginx
#By author test.net
echo -e '\033[32m-----------------------------\033[0m'
FILE=nginx-1.16.0.tar.gz
URL=http://nginx.org/download
PREFIX=/usr/local/nginx/
echo -e "\033[36mPlease Select Install Menu:\033[0m"
echo
echo "1)官方下載nginx文件包."
echo "2)解壓nginx源碼包."
echo "3)編譯安裝nginx服務器."
echo "4)啟動nginx服務器."
echo -e '\033[32m-----------------------------\033[0m'
sleep 20
關于文件描述符(fd)的基本概念:
文件描述符是一個非負整數,在打開現存文件或新建文件時,內核會返回一個文件描述符,讀寫文件也需要使用文件描述符來訪問文件。內核為每個進程維護該進程打開的文件記錄表。文件描述符只適于 Unix、Linux 操作系統。
文件描述符列表(標準輸入、輸出和錯誤)
系統中共有12個文件描述符,0、1、2分別是標準輸入、標準輸出、標準錯誤,3到9是可以被任意使用的。
每一個unix進程,都會擁有三個標準的文件描述符,來對應三種不同的身份。
文件
描述符
描述
映射關系
0 標準輸入
鍵盤
/dev/stdin --> /proc/self/fd/0
1 標準輸出
屏幕
/dev/stdin --> /proc/self/fd/1
2 標準錯誤
屏幕
/dev/stderr --> /proc/self/fd/2
每一個文件描述符會對應一個打開文件,同時不同的文件描述符也可以對應同一個打開文件;同一個文件可以被不同的進程打開,也可以被同一個進程多次打開。
在/proc/PID/fd中,列舉了進程PID所擁有的文件描述符,例如
[root@localhost ~]# cat learn_redirect.sh #!/bin/bash source /etc/profile; # $$表示當前進程的PID PID=$$ # 查看當前進程的文件描述符指向 ls -l /proc/$PID/fd echo "-------------------";echo # 文件描述符1與文件tempfd1進行綁定 ( [ -e ./tempfd1 ] || touch ./tempfd1 ) && exec 1<>./tempfd1 # 查看當前進程的文件描述符指向 ls -l /proc/$PID/fd echo "-------------------";echo;
腳本執行的結果如下:
[root@localhost ~]# cat testfd1 total 0 lrwx------ 1 root root 64 Sep 14 20:55 0 -> /dev/pts/0 lrwx------ 1 root root 64 Sep 14 20:55 1 -> /root/testfd1 lrwx------ 1 root root 64 Sep 14 20:55 2 -> /dev/pts/0 lr-x------ 1 root root 64 Sep 14 20:55 255 -> /root/test.sh
[root@localhost ~]# sh test.sh total 0 lrwx------ 1 root root 64 Sep 14 20:55 0 -> /dev/pts/0 lrwx------ 1 root root 64 Sep 14 20:55 1 -> /dev/pts/0 lrwx------ 1 root root 64 Sep 14 20:55 2 -> /dev/pts/0 lr-x------ 1 root root 64 Sep 14 20:55 255 -> /root/test.sh
上述的例子中第9行,將文件描述符1與文件testfile進行了綁定,此后,文件描述符1指向了testfile文件,標準輸出被重定向到了文件testfile中。
符號 描述 > 符號左邊輸出作為右邊輸入(標準輸出) >> 符號左邊輸出追加右邊輸入 < 符號右邊輸出作為左邊輸入(標準輸入) << 符號右邊輸出追加左邊輸入 & 重定向綁定符號 輸入和輸出可以被重定向符號解釋到 shell,shell 命令是從左到右依次執行命令。
1)覆蓋輸出
一般格式:[n] > file,如果 n 沒有指定,默認是 1
示例:
打印結果寫到文件:
echo "test" > a.txt
當沒有安裝 bc 計算器時,錯誤輸出結果寫到文件:
echo "1 + 1" |bc 2 > error.log
2)追加重定向輸出
一般格式:[n] >> file,如果 n 沒有指定,默認是 1
示例:
打印結果追加到文件:
echo "test" >> a.txt
當沒有安裝 bc 計算器時,錯誤輸出結果追加文件:
echo "1 + 1" |bc 2> error.log
一般格式:[n]<word,如果 n 沒有指定,默認是 0
示例:
a.txt 內容作為 grep 輸入:
grep "test" --color < a.txt
1)覆蓋重定向標準輸出和標準錯誤
&>file 和>&file 等價于 >file 2>&1 &將標準輸出和標準輸入綁定到一起,重定向 word 文件。
示例:
當不確定執行對錯時都覆蓋到文件:
echo "1 + 1" |bc &> error.log
當不確定執行對錯時都覆蓋到文件:
echo "1 + 1" |bc > error.log 2>&1
2)追加重定向標準輸出和標準錯誤
&>>file 等價于>>file 2>&1
示例:
當不確定執行對錯時都追加文件:
echo "1 + 1" |bc &>> error.log
將標準輸出和標準輸入追加重定向到 delimiter:
<< delimiter here-document delimiter
從當前 shell 讀取輸入源,直到遇到一行只包含 delimiter 終止,內容作為標準輸入。
將 eof 標準輸入作為 cat 標準輸出再寫到 a.txt:
# cat << eof 123 abc eof 123 abc # cat > a.txt << eof > 123 > abc > eof
/dev/null 是一個空設備,向它寫入的數據都會丟棄,但返回狀態是成功的。與其對應的還有一個/dev/zero 設備,提供無限的 0 數據流。
在寫 Shell 腳本時我們經常會用到/dev/null 設備,將 stdout、stderr 輸出給它,也就是我們不想要這些輸出的數據。
通過重定向到/dev/null 忽略輸出,比如我們沒有安裝 bc 計算器,正常會拋出沒有發現命令:
echo "1 + 1" |bc >/dev/null 2>&1
這就讓標準和錯誤輸出到了空設備。
忽略標準輸出:
echo "test" >/dev/null
忽略錯誤輸出:
echo "1 + 1" |bc 2>/dev/null
注意:上個練習提到的2>&1可以這樣理解
對于&1 更準確的說應該是文件描述符 1,而1標識標準輸出,stdout。
對于2 ,表示標準錯誤,stderr。
2>&1 的意思就是將標準錯誤重定向到標準輸出。這里標準輸出已經重定向到了 /dev/null。那么標準錯誤也會輸出到/dev/null
exec 是 bash 的內置命令,shell 的內置命令exec執行命令時,不啟用新的shell進程。
source 和.
不啟用新的shell,在當前shell中執行,設定的局部變量在執行完命令后仍然有效。
bash 或 sh 或 shell script 執行時,另起一個子shell,其繼承父shell的環境變量,其子shell的變量執行完后不影響父shell。exec是用被執行的命令行替換掉當前的shell進程,且exec命令后的其他命令將不再執行。例如在當前shell中執行 exec ls 表示執行ls這條命令來替換當前的shell ,即為執行完后會退出當前shell。
為了避免這個結果的影響,一般將exec命令放到一個shell腳本中,用主腳本調用這個腳本,調用處可以用bash xx.sh(xx.sh為存放exec命令的腳本),這樣會為xx.sh建立一個子shell去執行,當執行exec后該子腳本進程就被替換成相應的exec的命令。其中有一個例外:當exec命令對文件描述符操作的時候,就不會替換shell,而是操作完成后還會繼續執行后面的命令!
常用格式:exec [-cl] [-a name] [command [arguments]]
如果指定了command,它將用當前的command替換當前的shell, 但是不會產生新的進程,如果有arguments參數,將會作為command的參數。
選項:
-l:將會在傳遞給command命令的第0個參數前面加上一個dash('-'),有點像在用su的時候(su - username) -c:將會使command命令在一個空環境中執行 -a:shell會將name作為第0個參數傳遞給要執行的command命令
exec 語法:
exec命令
作用
exec ls
在shell中執行ls,ls結束后不返回原來的shell中了
exec <>
將file中的內容作為exec的標準輸入
exec >file
將file中的內容作為標準寫出
exec 3<>
將file讀入到fd3中
sort <&3
fd3中讀入的內容被分類
exec 4>file
將寫入fd4中的內容寫入file中
ls >&4
Ls將不會有顯示,直接寫入fd4中了,上面file中
exec 5<&4
創建fd4的拷貝fd5
exec 3<&-
關閉fd3
舉例:
先上我們進如/dev/fd/目錄下看一下:
root@localhost #cd /dev/fd root@localhost #/dev/fd#ls 0 1 2 255
root@localhost #/dev/fd#ls
0 1 2 255
默認會有這四個項:
0是標準輸入,默認是鍵盤。
1是標準輸出,默認是屏幕/dev/tty
2是標準錯誤,默認也是屏幕
255
當我們執行exec 3>/root/test,再去看看/dev/fd,一定多個3,什么意思呢?
也就是又增加了一個設備,這里也可以體會下linux設備即文件的理念。這時候fd3就相當于一個管道了,重定向到fd3中的文件會被寫在test中。關閉這個重定向可以用exec 3>&-
read 命令從標準輸入讀取,并把輸入的內容復制給變量。
命令格式: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-pprompt] [-t timeout] [-u fd] [name ...]
-e 在一個交互 shell 中使用 readline 獲取行 -r 不允許反斜杠轉義任何字符 -s 隱藏輸入 -a array 保存為數組,元素以空格分隔 -d delimiter 持續讀取直到遇到 delimiter 第一個字符退出 -n nchars 讀取 nchars 個字符返回,而不是等到換行符 -p prompt 提示信息 -t timeout 等待超時時間,秒 -u fd 指定文件描述符號碼作為輸入,默認是 0
示例:
獲取用戶輸入保存到變量:
[root@localhost ~]# read -p "Please input your name: " VAR Please input your name: test [root@localhost ~]# echo $VAR test
用戶輸入保存為數組:
[root@localhost ~]# read -p "Please input your name: " -a ARRAY Please input your name: 1 2 3 4 5 [root@localhost ~]# echo ${ARRAY[*]} 1 2 3 4 5 [root@localhost ~]# echo ${ARRAY[1]} 2 [root@localhost ~]# echo ${ARRAY[0]} 1
遇到 e 字符返回:
[root@localhost ~]# read -d e VAR jf666 e [root@localhost ~]# echo $VAR jf666
從文件作為 read 標準輸入:
[root@localhost ~]# cat a.txt test [root@localhost ~]# read VAR < a.txt [root@localhost ~]# echo $VAR test
while 循環讀取每一行作為 read 的標準輸入:
[root@localhost ~]# cat a.txt test test1 test2 [root@localhost ~]# cat a.txt |while read LINE; do echo $LINE; done test test1 test2
分別變量賦值:
[root@localhost ~]# read a b c 1 2 3 [root@localhost ~]# echo $a 1 [root@localhost ~]# echo $b 2 [root@localhost ~]# echo $c 3 [root@localhost ~]# echo 1 2 3 | while read a b c;do echo "$a $b $c"; done 1 2 3
在Unix或類Unix操作系統中,管道是一個由標準輸入輸出鏈接起來的進程集合,因此,每一個進程的輸出將直接作為下一個進程的輸入。
linux管道包含兩種
匿名管道(ps aux | grep nginx)
命名管道(mkfifo /tmp/fd1)
管道有一個特點,如果管道中沒有數據,那么取管道數據的操作就會滯留,直到管道內進入數據,然后讀出后才會終止這一操作;同理,寫入管道的操作如果沒有讀取管道的操作,這一動作就會滯留。
匿名管道
在Unix或類Unix操作系統的命令行中,匿名管道使用ASCII中垂直線|作為匿名管道符,匿名管道的兩端是兩個普通的,匿名的,打開的文件描述符:一個只讀端和一個只寫端,這就讓其它進程無法連接到該匿名管道。例如:cat file | less
為了執行上面的指令,Shell創建了兩個進程來分別執行cat和less。
有一點值得注意的是兩個進程都連接到了管道上,這樣寫入進程cat就將其標準輸出(文件描述符為fd 1)連接到了管道的寫入端,讀取進程less就將其標準輸入(文件描述符為fd 0)連接到了管道的讀入端。實際上,這兩個進程并不知道管道的存在,它們只是從標準文件描述符中讀取數據和寫入數據。shell必須要完成相關的工作。
命名管道
命名管道簡介
命名管道也稱FIFO(FIFO,First In First Out),從語義上來講,FIFO其實與匿名管道類似,但值得注意:
在文件系統中,FIFO擁有名稱,并且是以設備特俗文件的形式存在的;
任何進程都可以通過FIFO共享數據;
除非FIFO兩端同時有讀與寫的進程,否則FIFO的數據流通將會阻塞;
匿名管道是由shell自動創建的,存在于內核中;而FIFO則是由程序創建的(比如mkfifo命令),存在于文件系統中;
匿名管道是單向的字節流,而FIFO則是雙向的字節流;
比如,可以利用FIFO實現單服務器、多客戶端的應用程序:利用FIFO實現單服務器多客戶端的應用程序
有了上面的知識準備,現在可以開始講述,linux多進程并發時,如何控制每次并發的進程數。
命名管道特性
如果管道內容為空,則阻塞
如果沒有讀管道的操作,則阻塞
測試命名管道特性
如果管道內容為空,則阻塞cat /tmp/fd,管道內容為空則阻塞
10.0.0.7終端1:操作命令 [root@localhost fd]# mkfifo /tmp/fd1 [root@localhost fd]# cat /tmp/fd1 10.0.0.7終端2:操作命令 [root@localhost ~]# echo "test" > /tmp/fd1 查看終端1 返回 [root@localhost fd]# cat /tmp/fd1 test
2.如果沒有讀管道的操作,則阻塞echo "test" > /tmp/fd1,沒有讀管道則阻塞
查看10.0.0.7終端1 [root@localhost fd]# echo "test" > /tmp/fd1 查看10.0.0.7終端2 [root@localhost ~]# cat /tmp/fd1 test
總結:
利用有名管道的上述特性就可以實現一個隊列控制了。
舉例:一個女士公共廁所總共就10個蹲位,這個蹲位就是隊列長度,女廁所門口放著10把藥匙,要想上廁所必須拿一把藥匙,上完廁所后歸還藥匙,下一個人就可以拿鑰匙進去上廁所了,這樣同時來了1千位美女上廁所,那前十個人搶到藥匙進去上廁所了,后面的990人需要等一個人出來歸還藥匙才可以拿到藥匙進去上廁所,這樣10把藥匙就實現了控制1000人上廁所的任務(os中稱之為信號量)。
注意:
(1)管道具有存一個讀一個,讀完一個就少一個,沒有則阻塞,放回的可以重復取,這正是隊列特性,但是問題是當往管道文件里面放入一段內容,沒人取則會阻塞,這樣你永遠也沒辦法,往管道里面同時放入10段內容(想當與10把藥匙),解決這個問題的關鍵就是文件描述符了。
(2)mkfifo /tmp/fd1
創建有名管道文件exec 3<>/tmp/fd1,創建文件描述符3關聯管道文件,這時候3這個文件描述符就擁有了管道的所有特性,還具有一個管道不具有的特性:無限存不阻塞,無限取不阻塞,而不用關心管道內是否為空,也不用關心是否有內容寫入引用文件描述符: &3可以執行n次echo >&3 往管道里放入n把鑰匙。
常用配置文件詳解:
# 查看系統信息 /etc/redhat-release 系統版本 /etc/hosts 主機名與 IP 對應關系 /etc/resolv.conf DNS 服務器地址 /etc/hostname 主機名 /etc/sysctl.conf 系統參數配置文件 /etc/sudoers sudo 權限配置 /etc/init.d 服務啟動腳本 /etc/sysconfig/network-scripts 網卡信息配置目錄 /etc/rc.d/rc.local 系統 init 初始化完后執行,不建議將啟動服務寫在這里面,應創建自己的 systemd 或 udev /etc/fstab 硬盤自動掛載配置 /etc/crontab 系統級任務計劃 /var/spool/cron 用戶級任務計劃,此目錄下以用戶名命名對應每個用戶的任務計劃 /etc/cron.d 描述計算機任務計劃 /etc/hosts.allow TCP 包訪問列表 /etc/hosts.deny TCP 包拒絕列表 /usr/share/doc 各軟件的文檔 /etc/sshd_config SSH 服務配置文件 /var/log 系統和應用程序日志目錄 /var/spool/mail 郵件目錄 # /dev 目錄 /dev 目錄下存放的是一些設備文件。 /dev/hd[a-t] IDE 設備 /dev/sd[a-z] SCSI 設備 /dev/dm-[-9] LVM 邏輯磁盤 /dev/null 黑洞 /dev/zero 無限 0 數據流 # /proc 目錄 /proc 是一個虛擬目錄,在 Linux 系統啟動后生成的,數據存儲在內存中,存放內核運行時的參數、網絡信息、進程狀態等等。 /proc主目錄 /proc/[0-9]+ 此目錄下數字命名的目錄是運行進程信息,目錄名為 PID /proc/meminfo 物理內存、交換空間等信息,free /proc/loadavg 系統負載 /proc/uptime 系統運行時間 計算系統啟動和運行時間: cat /proc/uptime| awk -F. '{run_days=$1 / 86400;run_hour=($1 % 86400)/3600;run_minute=($1 % 3600)/60;run_second=$1 % 60;printf("系統已運行:%d天%d時%d分%d秒",run_days,run_hour,run_minute,run_second)}' 或 who –b 查看最后一次系統啟動的時間 /proc/cpuinfo CPU 信息 /proc/modules 系統已加載的模塊或驅動,lsmod /proc/mounts 文件系統掛載信息,mount /proc/swaps swap 分區信息 /proc/partitions 系統分區信息 /proc/version 內核版本 /proc/stat CPU 利用率,磁盤,內存頁 /proc/devices 可用的設備列表 /proc/net 網絡目錄 /proc/net 目錄存放的是一些網絡協議信息。 /proc/net/tcp TCP 狀態連接信息,netstat /proc/net/udp UDP 狀態連接信息 /proc/net/arp arp 信息表 /proc/net/dev 網卡流量 /proc/net/snmp 網絡傳輸協議的收發包信息 /proc/net/sockstat socket 使用情況,比如已使用,正在使用 /proc/net/netstat 網絡統計數據,netstat -s /proc/net/route 路由表 /proc/sys 系統內核目錄 這個目錄下的文件可被讀寫,存了大多數內核參數,可以修改改變內核行為。所以修改這些文件要特別小心,修改錯誤可能導致內核不穩定。 有四個主要的目錄: fs # 文件系統各方面信息,包括配額、文件句柄、inode 和目錄項。 kernel # 內核行為的信息 net # 網絡配置信息,包括以太網、ipx、ipv4 和 ipv6。 vm # Linux 內核的虛擬內存子系統,通常稱為交換空間。 # 內核配置文件 /proc/sys/fs/file-max 內核分配所有進程最大打開文件句柄數量,可適當增加此值 /proc/sys/fs/file-nr 只讀,第一個值已分配的文件句柄數量,第二個值分配沒有使用文件句柄數量,第三個值文件句柄最大數量。 /proc/sys/kernel/ctrl-alt-del 組合鍵重啟計算機,只為 0 同步緩沖區到磁盤,1 為不同步 /proc/sys/kernel/domainname 配置系統域名 /proc/sys/kernel/exec-shield 配置內核執行保護功能,防止某類型緩沖區溢出***。0 為禁用,1 開啟 /proc/sys/kernel/hostname 配置系統主機名 /proc/sys/kernel/osrelease 內核版本號 /proc/sys/kernel/ostype 操作系統類型 /proc/sys/kernel/shmall 設置共享內存的總量,以字節為單位 /proc/sys/kernel/shmmax 設置最大共享內存段 /proc/sys/kernel/shmmni 設置共享內存段最大數量 /proc/sys/kernel/threads-max 設置最大允許線程數量 /proc/sys/kernel/pid_max 設置最大允許創建的 pid 數量 /proc/sys/kernel/version 顯示最后一次編譯內核時間 /proc/sys/kernel/random/uuid 生成 uuid /proc/sys/kernel/core_pattern 控制生成 core dump 文件位置和保存格式 /proc/sys/net/core/netdev_max_backlog 設置數據包隊列允許最大數量 /proc/sys/net/core/optmem_max 設置 socket 允許最大緩沖區大小 /proc/sys/net/core/somaxconn 每個端口最大監聽隊列長度 /proc/sys/net/core/rmem_default 設置 socket 接收默認緩沖區大小,單位字節 /proc/sys/net/core/rmem_max 設置 socket 接收最大緩沖區大小 /proc/sys/net/core/wmem_default 設置 socket 發送默認緩沖區大小 /proc/sys/net/core/wmem_max 設置 socket 發送最大緩沖區大小 /proc/sys/net/ipv4/icmp_echo_ignore_all 和 icmp_echo_ignore_broadcasts 設置是否忽略 icmp 響應包和廣播包,0 為不忽略,1 為忽略 /proc/sys/net/ipv4/ip_default_ttl 設置默認生存時間 /proc/sys/net/ipv4/ip_forward 允許系統接口轉發數據包,默認 0 為關閉,1 為開啟 /proc/sys/net/ipv4/ip_local_port_range 指定使用本地 TCP 或 UDP 端口范圍,第一個值最低,第二個值最高 /proc/sys/net/ipv4/tcp_syn_retries 限制重新發送 syn 嘗試建立連接次數 /proc/sys/net/ipv4/tcp_synack_retries syn ack 確認包嘗試次數/proc/sys/net/ipv4/tcp_syncookies 是否啟用 syn cookie,0 為關閉,默認 1 為開啟 /proc/sys/net/ipv4/tcp_max_tw_buckets 系統保持 TIME_WAIT 最大數量 /proc/sys/net/ipv4/tcp_tw_recycle 是否啟用 TIME_WAIT 快速收回,默認 0 為關閉,1 為開啟 /proc/sys/net/ipv4/tcp_tw_reuse 是否啟用 TIME_WAIT 復用,默認 0 為關閉,1為開啟 /proc/sys/net/ipv4/tcp_keepalive_time TCP 連接保持時間(默認 2 小時),當連接活動,定時器會重新復位。 /proc/sys/vm/swappiness 內核按此值百分比來使用 swap,值越小越不考慮使用物理內存,0 為盡可能不使用 swap /proc/sys/vm/overcommit_memory 控制內存分配,默認 0 為內核先評估可用內存,如果足夠允許申請,否則拒絕,1 為允許分配所有物理內存,2 為允許分配超過物理內存和交換空間總和的內存 /proc/sys/vm/overcommit_ratio 指定物理內存比率,當 overcommit_memory=2時,用戶空間進程可使用的內存不超過物理內存*overcommit_ratio+swap
Shell編程也叫Shell流程控制語句,流程控制主要是改變程序運行順序的指令。
Linux Shell編程中,if、for、while、case等條件流程控制語句用的非常多,熟練掌握以上流程控制語句及語法的實驗,對編寫Shel腳本有非常大的益處。
If條件判斷語句,通常以if開頭,fi結尾。也可加入else或者elif進行多條件的判斷,if表達式如下:
if (表達式) 語句1 else 語句2 Fi 單分支格式1:if 條件 ; then 語句; fi 雙分支格式2:if 條件; then 語句; else 語句; fi 多分支格式3:if …; then … ;elif …; then …; else …; fi
-f 判斷文件是否存在 eg: if [ -f filename ]; -d 判斷目錄是否存在 eg: if [ -d dir ]; -eq 等于,應用于整型比較 equal; -ne 不等于,應用于整型比較 not equal; -lt 小于,應用于整型比較 letter; -gt 大于,應用于整型比較 greater; -le 小于或等于,應用于整型比較; -ge 大于或等于,應用于整型比較; -a 雙方都成立(and) 邏輯表達式 –a 邏輯表達式; -o 單方成立(or) 邏輯表達式 –o 邏輯表達式; -z “字符串”的長度為零則為真 -n “字符串”的長度為非零non-zero則為真 || 單方成立; && 雙方都成立表達式。 判斷符使用技巧: 1.整數比較使用-lt,-gt,ge等比較運算符,詳情參考:整數比較 2.文件測試使用 -d, -f, -x等運算發,詳情參考:文件測試 3.邏輯判斷使用 &&(且)、||(或)、!(取反) 字符串實用的對比: 1.字符串的比較使用以下三個比較運算符:= 或者(==)、!= 、> 、 <。 2.-z表示后面的值是否為空,為空則返回true,否則返回false。 3.-n表示判斷后面的值是否為空,不為空則返回true,為空則返回false。 邏輯判斷表達式: if [ $a -gt $b ]; if [ $a -lt 5 ]; if [ $b -eq 10 ]等 -gt (>); -lt(<); -ge(>=); -le(<=);-eq(==); -ne(!=) 注意到處都是空格 可以使用 && || 結合多個條件 if [ $a -gt 5 ] && [ $a -lt 10 ]; then if [ $b -gt 5 ] || [ $b -lt 3 ]; then 運算工具( let/expr/bc ) 命令 描述 示例 let 賦值并運算,支持++,-- let VAR=(1+2)*3;echo $VAR x=10;y=5 let x++;echo $x 每次執行一次x加1 let y--;echo $y 每執行一次y-1 let x+=2 每執行一次x加2 let x-=2 沒執行一次x減2 expr 乘法符號或者特殊符號需要加分斜杠轉義\* expr 1 \* 2 運算符兩邊需要有空格 expr \( 1 + 2 \) \* 2 使用雙括號時要進行轉義 bc 計算器,支持浮點運算、平方 bc本身就是一個計算器,可以直接輸入命令,進入解釋器。 echo “1 + 2” |bc 將管道符前面標準輸出左右bc的標準輸入 echo ‘scale=2;10/3’|bc 用scale保留2位小數點
(1) 比較兩個整數大小。
#!/bin/bash #By author test.net NUM=100 if (( $NUM > 4 )) ;then echo “The Num $NUM more than 4.” else echo “The Num $NUM less than 4.” fi
(2) 判斷系統目錄是否存在。
#!/bin/bash #judge DIR or Files #By author test.net if [ ! -d /data/20140515 -a ! -d /tmp/test/ ];then mkdir -p /data/20140515 fi
(3) if多個條件測試分數判斷。
#!/bin/bash #By author test.net scores=$1 if [[ $scores -eq 100 ]]; then echo "very good!"; elif [[ $scores -gt 85 ]]; then echo "good!"; elif [[ $scores -gt 60 ]]; then echo "pass!"; elif [[ $scores -lt 60 ]]; then echo "no pass!" fi
(4) 根據Linux不同發行版本使用不同的命令進行安裝軟件
#!/bin/bash if [ -e /etc/redhat-release ]; then yum install wget -y elif [ $(cat /etc/issue |cut -d' ' -f1) == "Ubuntu" ]; then apt-get install wget -y else Operating system cannot be found. exit fi
Shell編程中,尤其是使用if語句時,經常會使用()、(())、[]、[[]]、{}等括號,如下為幾種括號簡單區別對比:
( ) 用于多個命令組、命令替換、初始化數組,多用于SHELL命令組,例如:JF=(jf1 jf2 jf3),其中括號左右不保留空格;定義變量時增加括號,括號內的變量會失效 (( )) 整數擴展、運算符、重定義變量值,算術運算比較,例如:((i++))、((i<=100)),其中括號左右不保留空格; [ ] bash內部命令,[ ]與test是等同的,正則字符范圍、引用數組元素編號,不支持+-*/數學運算符,邏輯測試使用-a、-o,通常用于字符串比較、整數比較以及數組索引,其中括號左右要保留空格; [[ ]] bash程序語言的關鍵字,不是一個命令,[[ ]]結構比[ ]結構更加通用,不支持+-*/數學運算符,邏輯測試使用&&、||,通常用于字符串比較、邏輯運算符等,其中括號左右要保留空格; {} 主要用于命令集合或者范圍,例如mkdir -p /data/201{7,8}/,其中括號左右不保留空格;
Shell編程中,不管是使用變量、編程時,經常會使用$、\、單引號、雙引號、反引號等符號,如下為幾種符號簡單區別對比:
美元符號$,主要是用于引用SHELL編程中變量,例如定義變量JF=www.test.net,引用值,需要用$JF;
\
反斜杠,主要是用于對特定的字符實現轉義,保留原有意義,例如echo “$JF”結果會打印$JF,而不會打印www.test.net;
單引號 (' ') ,單引號,不具有變量置換的功能,所有的任意字符還原為字面意義,實現屏蔽Shell元字符的功能;
雙引號(" ") ,雙引號,具有變量置換的功能,保留$(使用變量前導符), (轉義符), `(反向引號)元字符的功能;
反向引號(),反引號,位于鍵盤Tab鍵上面一行的鍵,用作命令替換(相當于$(...);
通過前面章節對if語句和變量的學習,現基于所學知識,編寫一鍵源碼安裝LAMP腳本, 編寫腳本可以養成先分解腳本的各個功能的習慣,有利于快速寫出腳本,寫出更高效的腳本。
一鍵源碼安裝LAMP腳本,可以拆分為如下功能:
(1) LAMP打印菜單:
安裝apache WEB服務器;
安裝Mysql DB服務器;
安裝PHP 服務器;
整合LAMP架構
啟動LAMP服務;
(2) Apache服務器安裝部署:
Apache官網下載httpd-2.4.37.tar.gz版本,解壓,進入安裝目錄,configure、make 、make install。
(3) Mysql服務器的安裝:
Mysql官網下載mysql-5.5.60.tar.gz版本,解壓,進入安裝目錄,configure、make 、make install。
(4) PHP服務器安裝:
PHP官網下載php-5.4.31.tar.gz版本,解壓,進入安裝目錄,configure、make 、make install。
一鍵源碼安裝LAMP腳本,auto_install_lamp.sh內容如下:
#!/bin/bash ########## function ########## depend_pkg () { yum install gcc gcc-c++ make cmake ncurses-devel libxml2-devel \ perl-devel libcurl-devel libgcrypt libgcrypt-devel libxslt \ libxslt-devel pcre-devel openssl-devel wget -y } cat <<END 1.[install apache2.4] 2.[install mysql5.5] 3.[install php5.4] END read -p "Please input number : " NUM case $NUM in 1) ########## Install Depend Pkg ########## depend_pkg; WorkDIR=/usr/local/src cd $WorkDIR [ -f "httpd-2.4.37.tar.gz" ] || wget http://mirrors.sohu.com/apache/httpd-2.4.37.tar.gz ls *.tar.gz |xargs -I file tar xzf file -C $WorkDIR if [ $? -eq 0 ];then yum install apr apr-devel apr-util apr-util-devel -y else echo "------ apr make failed. ------" exit 1 fi ########## Install Apache ########## HTTPDIR=/usr/local/apache2.4 if [ $? -eq 0 ];then cd $WorkDIR cd httpd-2.4.37 ./configure -prefix=$HTTPDIR -enable-so -enable-rewrite -enable-modules=all make && make install else echo "------ apr-util make failed. ------" exit 1 fi if [ $? -eq 0 ];then CONF=$HTTPDIR/conf/httpd.conf cp $HTTPDIR/bin/apachectl /etc/init.d/httpd chmod +x /etc/init.d/httpd sed -i "s/#ServerName www.example.com:80/ServerName ${IP}:80/g" $CONF sed -i 's/DirectoryIndex index.html/DirectoryIndex index.php index.html/g' $CONF sed -i "391 s/^/AddType application\/x-httpd-php .php/" $CONF /etc/init.d/httpd start IP=`ip address |grep inet|sed -n '3p'|awk '{print $2}'|awk -F/ '{print $1}'` Urlcode=`curl -o /dev/null -s -w "%{http_code}" $IP/index.html` [ $Urlcode -eq 200 ] && echo "Apache install success." || echo "Apache install failed." else echo "------ apache make failed. ------" exit 1 fi ;; 2) ########## Install Depend Pkg ########## depend_pkg; ########## Install Mysql ########## /usr/sbin/groupadd mysql /usr/sbin/useradd -g mysql -s /sbin/nologin mysql WorkDIR=/usr/local/src MYSQLDIR=/usr/local/mysql5.5 cd $WorkDIR [ -f "mysql-5.5.60.tar.gz" ] || wget https://mirrors.tuna.tsinghua.edu.cn/mysql/downloads/MySQL-5.5/mysql-5.5.60.tar.gz tar zxvf mysql-5.5.60.tar.gz cd mysql-5.5.60 cmake -DCMAKE_INSTALL_PREFIX=$MYSQLDIR \ -DSYSCONFDIR=$MYSQLDIR/etc \ -DMYSQL_DATADIR=$MYSQLDIR/data \ -DDEFAULT_CHARSET=utf8 \ -DDEFAULT_COLLATION=utf8_general_ci make && make install if [ $? -eq 0 ];then $MYSQLDIR/scripts/mysql_install_db \ --basedir=$MYSQLDIR --datadir=$MYSQLDIR/data/ --user=mysql 1>/dev/null mkdir $MYSQLDIR/etc cp support-files/my-medium.cnf $MYSQLDIR/etc/my.cnf cp support-files/mysql.server /etc/init.d/mysqld rm -rf /etc/my.cnf # echo "PATH=$PATH:$MYSQLDIR/bin" >> /etc/profile # . /etc/profile chmod +x /etc/init.d/mysqld chown -R root.mysql $MYSQLDIR chown -R mysql.mysql $MYSQLDIR/data/ /usr/local/mysql5.5/bin/mysqld_safe --user=mysql& sleep 10 && /usr/local/mysql5.5/bin/mysqladmin -u root password '123456' /usr/local/mysql5.5/bin/mysql -uroot -p'123456' -e "show databases;" [ $? -eq 0 ] && echo "MySQL install success." || echo "MySQL install failed." else echo "------mysql cmake failed.------" exit 1 fi ;; 3) ########## Install Depend Pkg ########## depend_pkg; ########## Install GD ########## yum install gd freetype freetype-devel libpng libpng-devel zlib zlib-devel libjpeg* -y ########## Install PHP ########## WorkDIR=/usr/local/src PHPDIR=/usr/local/php5.4 PHPCONF=$PHPDIR/etc/php.ini cd $WorkDIR [ -f "php-5.4.31.tar.gz" ] || wget http://mirrors.sohu.com/php/php-5.4.31.tar.gz tar zxvf php-5.4.31.tar.gz cd php-5.4.31 ./configure -prefix=$PHPDIR \ --with-config-file-path=$PHPDIR/etc \ --with-apxs2=/usr/local/apache2.4/bin/apxs \ --with-mysql=/usr/local/mysql5.5 \ --with-mysqli=/usr/local/mysql5.5/bin/mysql_config \ --enable-soap --enable-bcmath --enable-zip --enable-ftp \ --enable-mbstring --with-gd --with-libxml-dir --with-jpeg-dir \ --with-png-dir --with-freetype-dir --with-zlib \ --with-libxml-dir=/usr --with-curl --with-xsl --with-openssl make && make install if [ $? -eq 0 ];then \cp php.ini-production $PHPCONF echo "data.timezone = Asia\Shanghai" >> $PHPCONF sed -i 's/upload_max_filesize = 2M/ upload_max_filesize = 50M/g' $PHPCONF sed -i 's/display_errors = Off/display_errors = On/g' $PHPCONF echo "<?php phpinfo();?>" > /usr/local/apache2.4/htdocs/index.php /etc/init.d/httpd restart /etc/init.d/mysqld restart &>/dev/null IP=`ip address |grep inet|sed -n '3p'|awk '{print $2}'|awk -F/ '{print $1} '` Urlcode=`curl -o /dev/null -s -w "%{http_code}" $IP/index.php` [ $Urlcode -eq 200 ] && echo "PHP install success." || echo "PHP install failed." echo "/usr/local/apache/bin/apachectl start" >> /etc/rc.local chkconfig mysqld on else echo "------ php make failed. ------" fi ;; *) echo "Please input number 1 2 3." esac
Shell 循環中分為當直型循環和直到型循環
當型循環結構:在每次執行循環體前,對條件進行判斷,當條件滿足時,執行循環體,否則終止循環。
直到型循環結構:在執行了一次循環體后,對條件進行判斷,如果條件不滿,就繼續執行,知道條件滿足終止循環。
Shell編程中循環命令用于特定條件下決定某些語句重復執行的控制方式,有三種常用的循環語句:for、while和until。
while循環和for循環屬于“當型循環”,而until屬于“直到型循環”。
循環控制符:break和continue控制流程轉向。
for循環語句主要用于對某個數據域進行循環讀取、對文件進行遍歷,通常用于需要循環某個文件或者列表。其語法格式以for…do開頭,done結尾。
語法格式如下:
For 變量 in (表達式) do 語句1 done
For循環語句Shell腳本編程案例如下:
(1) 循環打印BAT企業官網:
#!/bin/bash #By author test.net for test in www.baidu.com www.taobao.com www.qq.com do echo $test done
(2) 循環打印1至100數字,seq表示列出數據范圍:
#!/bin/bash #By author test.net for i in `seq 1 100` do echo “NUM is $i” done
(3) For循環求1-100的總和:
#!/bin/bash #By author test.net #auto sum 1 100 j=0 for ((i=1;i<=100;i++)) do j=`expr $i + $j` done echo $j
(4) 對系統日志文件進行分組打包:
#!/bin/bash #By author test.net for i in `find /var/log -name "*.log"` do tar -czf `echo $i|sed s#/#_#g`.tgz $i done
(5) For循環批量遠程主機文件傳輸:
#!/bin/bash #auto scp files for client #By author test.net for i in `seq 100 200` do scp -r /tmp/test.txt root@192.168.1.$i:/data/webapps/www done
(6) For循環批量遠程主機執行命令:
#!/bin/bash #auto scp files for client #By author test.net for i in `seq 100 200` do ssh -l root 192.168.1.$i ‘ls /tmp’ done
(7) For循環打印10秒等待提示:
for ((j=0;j<=10;j++)) do echo -ne "\033[32m-\033[0m" sleep 1 done echo
(8) 檢測多個域名是否可以正常訪問
#!/bin/bash URL="www.test.net www.sina.com www.jd.com" for url in $URL; do HTTP_CODE=$(curl -o /dev/null -s -w %{http_code} http://$url) if [ $HTTP_CODE -eq 200 -o $HTTP_CODE -eq 301 -o $HTTP_CODE -eq 302 ]; then echo "$url OK." else echo "$url NO!" fi done [root@localhost scripts]# bash test.sh www.baidu.com OK. www.sina.com OK. www.jd.com OK.
(9) For循環打印99乘法表
for i in `seq 9` do for n in `seq 9` do [ $n -le $i ] && echo -n "$i*$n = `echo $(($i*$n))` " done echo " " done
While循環語句也稱為前測試循環語句,它的循環重復執行次數,是利用一個條件來控制是否繼續重復執行這個語句。
While循環語句與for循環功能類似,主要用于對某個數據域進行循環讀取、對文件進行遍歷,通常用于需要循環某個文件或者列表,滿足循環條件會一直循環,不滿足則退出循環,其語法格式以while…do開頭,done結尾。
語法格式如下:
while (表達式) do 語句1 done
while循環語句之所以命名為前測試循環,是因為它要先判斷此循環的條件是否成立,然后才作重復執行的操作。
while循環語句執行過程是:先判斷表達式的退出狀態,如果退出狀態為0,則執行循環體,并且在執行完循環體后,進行下一次循環,否則退出循環執行done后的命令。
為了避免死循環,必須保證在循環體中包含循環出口條件,即存在表達式的退出狀態為非0的情況。
While循環語句Shell腳本編程案例如下:
(1) 循環打印BAT企業官網,read指令用于讀取行或者讀取變量:
v1 版本 #!/bin/bash #By author test.net while read line do echo $line done <test.txt v2版本讓循環的腳本只運行2遍。 #!/bin/bash #by author test.net n=1 while ((n<=2)); do ((n++)) while read test; do echo $test; sleep 3; done < /root/test.txt done
其中test.txt內容為:
www.baidu.com www.taobao.com www.qq.com
(2) While無限每秒輸出Hello World:
#!/bin/bash #By author test.net while sleep 1 do echo -e "\033[32mHello World.\033[0m" done # while true表示條件永遠為真,因此會一直運行,像死循環一樣,但是我們稱呼為守護進程。
(3) 循環打印1至100數字,expr用于運算邏輯工具:
#!/bin/bash #By author test.net i=0 while ((i<=100)) do echo $i i=`expr $i + 1` done
(4) While循環求1-100的總和:
#!/bin/bash #By author test.net #auto sum 1 100 j=0 i=1 while ((i<=100)) do j=`expr $i + $j` ((i++)) done echo $j ================= #!/bin/bash i=1 j=0 while [ $i -le 100 ];do let j=j+i let i=i+1 done echo $j
(5) 條件表達式為true,產生死循環,不間斷ping主機地址
#!/bin/bash #By author test.net while true; do ping –c 1 www.test.net done
(6) While循環逐行讀取文件:
#!/bin/bash #By author test.net while read line do echo $line; done < /etc/hosts
(7) While循環判斷輸入IP正確性:
#!/bin/bash #By author test.net #Check IP Address read -p "Please enter ip Address,example 192.168.0.11 ip": IPADDR echo $IPADDR|grep -v "[Aa-Zz]"|grep --color -E "([0-9]{1,3}\.){3}[0-9]{1,3}" while [ $? -ne 0 ] do read -p "Please enter ip Address,example 192.168.0.11 ip": IPADDR echo $IPADDR|grep -v "[Aa-Zz]"|grep --color -E "([0-9]{1,3}\.){3}[0-9]{1,3}" done
(8) 每5秒循環判斷/etc/passwd是否被非法修改:
#!/bin/bash #Check File to change. #By author test.net FILES="/etc/passwd" while true do echo "The Time is `date +%F-%T`" OLD=`md5sum $FILES|cut -d" " -f 1` sleep 5 NEW=`md5sum $FILES|cut -d" " -f 1` if [[ $OLD != $NEW ]];then echo "The $FILES has been modified." fi done
(9) 每10秒循環判斷test用戶是否登錄系統:
#!/bin/bash #Check File to change. #By author test.net USERS="test" while true do echo "The Time is `date +%F-%T`" sleep 10 NUM=`who|grep "$USERS"|wc -l` if [[ $NUM -ge 1 ]];then echo "The $USERS is login in system." fi done
Case選擇語句,主要用于對多個選擇條件進行匹配輸出,與if elif語句結構類似,通常用于腳本傳遞輸入參數,打印出輸出結果及內容,其語法格式以Case…in開頭,esac結尾。
語法格式如下:
#!/bin/bash #By author test.net case $1 in Pattern1) 語句1 ;; Pattern2) 語句2 ;; Pattern3) 語句3 ;; esac
Case條件語句Shell腳本編程案例如下:
(1) 打印Monitor及Archive選擇菜單:
#!/bin/bash #By author test.net case $1 in monitor) echo "monitor" ;; archive) echo "archive" ;; help ) echo -e "\033[32mUsage:{$0 monitor | archive |help }\033[0m" ;; *) echo -e "\033[32mUsage:{$0 monitor | archive |help }\033[0m " esac
(2) 自動修改IP腳本菜單:
#!/bin/bash #By author test.net case $i in modify_ip) change_ip ;; modify_hosts) change_hosts ;; exit) exit ;; *) echo -e "1) modify_ip\n2) modify_ip\n3)exit" esac
Select語句一般用于選擇,常用于選擇菜單的創建,可以配合PS3來做打印菜單的輸出信息,其語法格式以select…in do開頭,done結尾:
select i in (表達式) do 語句 done
Select選擇語句Shell腳本編程案例如下:
(1) 打印開源操作系統選擇:
#!/bin/bash #By author test.net PS3="What you like most of the open source system?" select i in CentOS RedHat Ubuntu do echo "Your Select System: "$i done
(2) 打印LAMP選擇菜單
#!/bin/bash #By author test.net PS3="Please enter you select install menu:" select i in http php mysql quit do case $i in http) echo Test Httpd. ;; php) echo Test PHP. ;; mysql) echo Test MySQL. ;; quit) echo The System exit. exit esac done
break 是終止循環。
continue 是跳出當前循環。
示例 1:在死循環中,滿足條件終止循環
#!/bin/bash i=0 while true; do let i++ if [ $i -eq 3 ]; then break fi echo $i done # bash test.sh 1 2
上述腳本中首先是使用了while循環來引用let計算器,計算i的值,后面用了 if 判斷,并用了 break 語句,如果數字等于3就跳出循環。
示例 2:舉例子說明 continue 用法
#!/bin/bash i=0 while [ $i -lt 5 ]; do let i++ if [ $i -eq 3 ]; then continue fi echo $i done # bash test.sh 1 2 4 5
注上述實驗在使用continue 是進行做了一個判斷,如果i++ 大于等于到3的時候進行跳出本次循環,繼續下次循環。所以打印出來之后沒有數字3。
以上2個案例表明continue是用于跳出單此循環而使用,break則是匹配到之后則是跳出整個循環語句,即時沒有循環完,也會進行跳出。
Shell允許將一組命令集或語句形成一個可用塊,這些塊稱為Shell函數,Shell函數的用于在于只需定義一次,后期隨時使用即可,無需在Shell腳本中添加重復的語句塊,其語法格式以function name(){開頭,以}結尾。
格式
Shell編程函數默認不能將參數傳入()內部,Shell函數參數傳遞在調用函數名稱傳遞,例如name args1 args2。
function name (){ command1 command2 ........ } name args1 args2 #function關鍵字可寫,也可不寫。
引用函數
func [OPTION]
引用函數的時候只需要執行函數名稱即可,OPTION是參數,參數可以寫多個,和腳本后面的參數是一樣的
無參函數
[root@localhost scripts]# cat test.sh \#!/bin/bash func() { echo "This is a function." } func [root@localhost scripts]# bash test.sh This is a function.
Shell函數很簡單,函數名后跟雙括號,再跟雙大括號。通過函數名直接調用,不加小括號,函數命令可以自己定義。
有參函數
[root@localhost scripts]# cat test.sh #!/bin/bash func() { echo "Hello $1" } func world [root@localhost scripts]# bash test.sh Hello world
通過Shell位置參數給函數傳參,參照執行腳本時的$1,$2,$3 等示例。
函數返回值
[root@localhost scripts]# cat test.sh #!/bin/bash func() { VAR=$((1+1)) return $VAR echo "This is a function." } func echo $? [root@localhost scripts]# sh test.sh 2
return在函數中定義狀態返回值,返回并終止函數,但返回的只能是0-255的數字,類似于exit。
遞歸函數
函數也支持遞歸調用,也就是自己調用自己。
[root@localhost scripts]# cat test.sh #!/bin/bash test() { echo $1 sleep 1 test hello } test [root@localhost scripts]# bash test.sh hello hello hello hello hello ……………
執行會一直在調用本身打印hello,這就形成了閉環。
fork 炸彈
經典的 fork 炸彈就是函數遞歸調用:
:(){ :|:& };: 或 .(){.|.&};.
這樣看起來不好理解,我們更改下格式:
:() { :|:& }; :
再易讀一點:
test() { test|test& }; test
分析下:
:(){ } 定義一個函數,函數名是冒號。
: 調用自身函數
| 管道符
: 再一次遞歸調用自身函數
:|: 表示每次調用函數":"的時候就會生成兩份拷貝。
& 放到后臺
; 分號是繼續執行下一個命令,可以理解為換行。
: 最后一個冒號是調用函數。
因此不斷生成新進程,直到系統資源崩潰,遞歸函數用的不多。
創建Apache軟件安裝函數,給函數Apache_install傳遞參數1:
#!/bin/bash #auto install LAMP #By author test.net #Httpd define path variable H_FILES=httpd-2.2.31.tar.bz2 H_FILES_DIR=httpd-2.2.31 H_URL=http://mirrors.cnnic.cn/apache/httpd/ H_PREFIX=/usr/local/apache2/ function Apache_install() { #Install httpd web server if [[ "$1" -eq "1" ]];then wget -c $H_URL/$H_FILES && tar -jxvf $H_FILES && cd $H_FILES_DIR &&./configure --prefix=$H_PREFIX if [ $? -eq 0 ];then make && make install echo -e "\n\033[32m-----------------------------------------------\033[0m" echo -e "\033[32mThe $H_FILES_DIR Server Install Success !\033[0m" else echo -e "\033[32mThe $H_FILES_DIR Make or Make install ERROR,Please Check......" exit 0 fi fi } Apache_install 1
創建judge_ip判斷IP函數:
#!/bin/bash #By author test.net judge_ip(){ read -p "Please enter ip Address,example 192.168.0.11 ip": IPADDR echo $IPADDR|grep -v "[Aa-Zz]"|grep --color -E "([0-9]{1,3}\.){3}[0-9]{1,3}" } judge_ip
什么是正則表達式?
正則表達式在每種語言中都會有,功能就是匹配符合你預期要求的字符串。
為什么要學正則表達式?
在企業工作中,我們每天做的linux運維工作中,時刻都會面對大量帶有字符串的文本配置、程序、命令輸出及日志文件等,而我們經常會有迫切的需要,從大量的字符串內容中查找符合工作需要的特定的字符串。這就要靠正則表達式。因此,可以說正則表達式就是為過濾字符的需求而生的! 例如:ifconfig的輸出取IP,例如:cat /var/log/messages輸出等
兩個注意事項:
正則表達式應用非常廣泛,存在于各種語言中,例如:php、python、java等。但是我們今天講的linux系統運維工作中的正則表達式,即linux正則表達式,最常用正則表達式的命令就是grep(egrep)、sed、awk,換句話說linux四劍客劍客要想工作的各高效,那一定離不開正則表達式配合的。
正則表達式和我們常用的通配符特殊字符是用本質去別的,這一點要注意。通配符例子:ls .log**這里的就是通配符(表示所有),不是正則表達式
Shell 正則表達式分為兩種:
基礎正則表達式:BRE(basic regular express)
擴展正則表達式:ERE(extend regular express),擴展的表達式有+、?、|和()
下面是一些常用的正則表達式符號
正則表達式
描述
\
轉義符,將特殊字符進行轉義,忽略其特殊意義
^
匹配行首,awk中,^則是匹配字符串的開始
$
匹配行尾,awk中,$則是匹配字符串的結尾
.
匹配除換行符\n之外的任意單個字符,awk則中可以
[]
匹配包含在[字符]之中的任意一個字符
[^ ]
匹配字符之外的任意一個字符
^[^]
匹配不是中括號內任意一個字符開頭的行
[ - ]
匹配[]中指定范圍內的任意一個字符,要寫成遞增
?
匹配之前的項1次或者0次
+
匹配之前的項1次或者多次
*
匹配之前的項0次或者多次
()
匹配表達式,創建一個用于匹配的子串
{ n }
匹配之前的項n次,n是可以為0的正整數
{n,}
之前的項至少需要匹配n次
{n,m}
指定之前的項至少匹配n次,最多匹配m次,n<=m
|
交替匹配|兩邊的任意一項
<
邊界符,匹配字符串開始
>
邊界符,匹配字符串結束
基本正則表達式實踐
接下來的測試文本如下:
[root@localhost ~]# cat test.log %anaconda pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges –notempty pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges –emptyok pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty %end... =========================
以下實踐通過grep 命令增加正則表達式進行匹配練習
^(尖角號)
功能實踐
# 匹配首字母為%的行 [root@localhost ~]# grep -n "^%" test.log # -n 參數是顯示匹配到的行號。
$
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。