1. 程式人生 > >快速掌握Shell編程

快速掌握Shell編程

必須 輸入 成功 條件 系統 最大的 一行 get 所有

  作者原創作品,轉載請註明出處http://www.cnblogs.com/yangp/p/8511321.html。

(一)Shell編程概述

1.1 shell簡述

  Shell編程和JavaScript非常相似,Shell和JavaScript都是弱類型語言,同時也都是解釋型語言。解釋型語言需要解釋器,JavaScript的解釋器是瀏覽器,Shell腳本的解釋器時bash,是一個shell、一個命令行用戶接口。

1.2 bash簡述

  bash在執行或者解釋腳本的時候,此bash非彼bash。用戶登錄進來的時候就用一個bash。通過敲一個命令來解釋腳本的時候,是在當前bash中打開一個新的bash,這兩個bash是父子關系。其實在空行敲回車也是從當前bash打開新的bash。

  在命令行中輸入$ps -aux |grep bash可以看見當前只有一個bash。這個bash就是系統和用戶進行交互的一個用戶接口。

技術分享圖片

  在命令行中輸入命令bash回車。這就是打開了一個新的bash。再次輸入ps -aux |grep bash可以看見當前有兩個bash。因為敲一個命令就是一個程序的入口,這個命令就被啟動,這個程序就是bash。這兩個bash就是父子關系。這種有層次結構的bash可以使用exit退出。

技術分享圖片

(二)變量

2.1 變量的類型  

  shell腳本的變量實際上也是bash的變量,共有四種類型:環境變量、本地變量(或稱局部變量,但略有差別)、位置變量、特殊變量。環境變量,作用在當前bash和所有子bash,與本地變量的區別在於作用域不同;本地變量(局部變量,當前代碼段),作用在當前bash,所有子bash都不能用;位置變量和特殊變量,是bash內置的用來保存某些特殊數據的變量,也不存在作用域的問題(也叫系統變量)。

2.2 環境變量

  環境變量,$export 變量名=值,其作用域為當前的shell和其子shell。

  註意:腳本在執行時都會啟動一個子shell進程,命令行中啟動的腳本會繼承當前shell環境變量;系統自動啟動腳本(非命令行啟動),則需要自我定義環境變量。

2.3 本地變量(局部變量)

  本地變量是只屬於某一個bash的變量。例如,$var_name=值,其作用域是整個bash進程。

  局部變量類似,例如$local var_name =值,其作用域為當前代碼段。

2.4 位置變量

  位置變量是指用於腳本執行的參數,$1表示第一個參數,以此類推$1,$2…。

2.5 特殊變量

  特殊變量是每一個bash進程中特有的一些變量,無需聲明。總共就幾個,$?和$#j較為常用。

2.5.1 $?

  $? 表示上一個命令的執行狀態返回值。事實上,任何一個程序執行的結果只有兩類,即程序有兩類返回值:

  第一類是命令的執行結果。例如輸入$ls顯示的目錄下的所有目錄和文件就是執行結果,再如輸入$id -u root看見的0就是執行結果。

  第二類是命令的執行狀態。任何命令的執行狀態都被$?這個變量存儲起來。$?: 0表示正確,1-255表示錯誤。例如使用$echo “$?”查看上一個命令的執行狀態,打印上一個命令的執行狀態0表示正確,非0表示錯誤。註意不能查看上上個命令的執行狀態,因為被覆蓋了。

技術分享圖片

2.5.2 $#

  $# 表示傳遞到腳本的參數個數。

2.5.3 $*

  $* 表示傳遞到腳本的參數,與位置變量不同,此選項參數可超過9個。

2.5.4 $$

  $$ 表示腳本運行時當前進程的ID號,常用作臨時變量的後綴。

2.5.5 $!

  $! 表示後臺運行的(&)最後一個進程的ID號 。

2.5.6 $@

  $@ 與$#相同,使用時加引號,並在引號中返回參數個數。

2.5.7 $-

  $- 表示上一個命令的最後一個參數 。

2.6 變量的聲明、撤銷、查看與引用

2.6.1 聲明變量

  環境變量的聲明必須加export;本地變量和局部變量的聲明不需寫export,直接寫變量名後面接值即可。

2.6.2 撤銷變量

  使用unset 變量名,撤銷變量。

2.6.3 查看變量

  查看shell中變量:set

  查看shell中的環境變量:printenv或env

2.6.4 引用變量

a) 引用變量

  引用變量格式為:${變量名},一般可以省略{}。只有特殊情況下不能省略,具體效果見下圖:

技術分享圖片

b) 單引號:強引用

  單引號:強引用,不作變量替換,引用字符串常量(單引號的內容都是字符串)。

c) 雙引號:弱引用

  雙引號:弱引用,做變量替換。

技術分享圖片

d) 反引號:命令替換

  反引號:``命令替換。當字符串表示一個命令,需要執行時,則要用反引號。

技術分享圖片

(三)輸出重定向

3.1 命令執行結果保存在一個文件中

3.1.1 >覆蓋重定向

  $ls >/path/file。不會重定向錯誤結果

技術分享圖片

3.1.2 >> 追加重定向

  $cat file1 file2 >> file3 //將file1和file2的文件內容追加重定向到file3後面。

3.1.3 2> 錯誤覆蓋重定向

  程序執行出錯的結果放到文件中。

3.1.4 2>>錯誤追加重定向

  程序執行出錯的結果放到文件中。

3.1.5 &> 全部覆蓋重定向

  無論命令執行對錯,都會覆蓋重定向到文件。

3.1.6 &>> 全部追加重定向

  無論命令執行對錯,都會追加重定向到文件。

3.2 命令執行結果直接丟棄

  /dev/null文件,dev是設備,null是一個設備文件,稱之為數據黑洞,所有數據放到這裏都無法恢復。

  $ls >> /dev/null

(四) 腳本

  通過組織命令及變量來完成具有某種業務邏輯的功能稱之為腳本。

4.1 簡單腳本案例

4.1.1 案例一(添加用戶)

a) 業務描述

  添加6個用戶,每個用戶的密碼同用戶名,不顯示添加密碼的信息,並給顯示添加用戶成功信息。

b) 編寫腳本

  首先,創建腳本。

mkdir /opt/shell
cd /opt/shell
vim test1.sh 

  其次,編寫腳本主體。

#!/bin/bash#腳本的第一行一定是這個腳本的聲明,這裏聲明的是腳本的解釋器。
# 用戶可以有參數傳過來,也可以直接定義一個變量。
U=’user1’ #聲明變量,本地變量的聲明直接變量名=值,無需export。這裏選擇單引號,因為user1是個字
符串,只需強引就行,雙引號也行。
useradd $U #引用變量
#設置密碼和用戶名相同,但是設置完之後不顯示passwd的執行結果。但是這個passwd會在控制臺出現信息
,所以需要使用管道傳入數據,並且重定向。
echo "$U" | passwd --stdin $U &>/dev/null #前一個命令的輸出傳給後一個命令的輸入。
echo "success." #打印成功信息。 

  以上是添加1個給定名稱的用戶,可以稍作修改,變為參數傳入的方式動態添加用戶:  

cp test1.sh test2.sh
vim test2.sh
#!/bin/bash
#
useradd $1 #這裏是使用傳遞參數的方式,1代表1個參數,實際上$1是特殊變量$*的特例
echo "$1" |  passwd --stdin  $1 &>/dev/null
echo "Add user $1 success."

c) 執行腳本

  這個兩個腳本當前沒有執行權限。有兩種方法使該腳本有執行權限:一是添加執行權限;二是,使用命令sh /path/腳本名(sh是一個執行腳本的命令)。

  腳本1的執行:

  $sh test1.sh

  腳本2的執行:

  $sh test2.sh username

  查看是否添加成功:

  $cat /etc/passwd

d) 查看腳本執行程度

  $bash -x /opt/shell/test2.sh

4.1.2 案例二(刪除用戶)

a) 業務描述

  寫一個腳本,完成以下任務:第一,使用一個變量保存一個用戶名;第二,刪除此變量中的用戶,且一並刪除其家目錄;第三,顯示“用戶刪除成功”信息。

b) 編寫腳本

  首先,創建腳本。

vim /opt/shell/test3.sh 

  其次,編寫腳本主體。

#!/bin/bash
U=$* #使用一個變量存儲用戶名
userdel $* #刪除這個用戶
rm -rf /home/$* #刪除該用戶的家目錄
echo "delete user and $*home success." #打印成功信息

c) 執行腳本

sh test3.sh username #註意一定要有參數(要刪除的用戶),不然會刪除整個/home目錄
cat /etc/passwd #查看用戶是否刪除成功。
ls /home #查看用戶的家目錄是否刪除成功

4.2 條件判斷

  條件判斷是布爾類型,而shell是弱類型的語言,也即沒有類型,所以表達式只有真或假。

4.2.1 條件表達式

  條件表達式有兩種形式:一是[ expression ],註意中括號和表達式之間一定要加空格隔開;二是,test expression,test+空格+表達式。

4.2.2 整數的比較

  =:-eq  等於

  !=:-ne  不等於

  >:-gt  大於

  <:-lt  小於

  >=:-ge  大於或等於

  <=:-le  小於或等於

4.2.3 邏輯運算

a) 命令的邏輯關系

  在linux中,命令執行狀態:0為真,其他為假。

b) 邏輯與或非

  可以使用離散數學的邏輯與或非來進行邏輯判斷。

  邏輯與:&&。當第一個條件為假時,第二條件不用再判斷,最終結果已經有;當第一個條件為真時,第二條件必須得判斷。

  邏輯或:||。

  邏輯非:!。

c) 案例(命令邏輯關系的條件判斷) 

  業務描述:添加用戶前先判斷是否存在,如果存在就打印該用戶已存在,並且退出。

  編寫腳本: 

vim /opt/shell/test4.sh
#!/bin/bash
#
id $1 &>/dev/null && echo "User $1 exist" && exit 3 #使用命令的執行狀態作為邏輯判斷。id $1 如果用戶$1存在,則為0(真),需要繼續判斷後面的,所以執行echo,並且退出腳本,不再
創建該用戶。退出,3是非0,當前腳本執行不成功。
#上一行可以使用邏輯或的代替,見下兩行
#!id $1 &>/dev/null || echo "User $1 exist" #如果用戶存在id $1則為0,前面加了!就是非0,就為假。前面為假,對於邏輯或來說需要繼續判斷後面的,所以執行echo 打印用戶存在。
#id $1 &>/dev/null && exit 3 #用戶存在就退出 useradd $
1 #添加用戶 id $1 &>/dev/null && echo "$1" | passwd --stdin $1 &>/dev/null #如果用戶創建成功了>,就執行後面的echo passwd添加密碼。 echo "Add user $1 success."

  執行腳本:

sh test4.sh user1

4.2.4 if條件判斷

a) 語法結構

  if條件表達式的語法結構為:

if [ 條件 ]; then
    語句
elif [ 條件 ]; then
    語句
else
    語句
fi

b) if的邏輯與或

  -a :並且

  -o :或者

  例如:

if [ $# -gt 1 -a $# -lt 3 -o $# -eq 2 ] ; then

  如果,上一個命令傳入的參數個數大於1,並且小於3,或者等於2。&&是離散數學中的邏輯與,-a是一個邏輯運算符,並且的意思,不同於&&。

c) 案例(if語句)

  業務描述:給定一個用戶,如果他的UID為0則顯示為管理員,否則顯示其他普通用戶。

  編寫腳本:

vim /opt/shell/test5.sh 
#!/bin/bash
USER_ID=`id -u $1`&> /dev/null || exit 3 #註意這裏需要用反引號,這裏也不能將變量名取為UID,會和環境變量沖突,這樣聲明的變量是只讀的。
if [ $USER_ID -eq 0 ] ; then
  echo "admin."
else
  echo "other."
fi 

  執行腳本:

sh test5.sh root

4.3 算術運算符

4.3.1 算術運算符的類型

a) let 算術運算表達式

let C=$A + $B

b) $[算術表達式]

C = $[$A+$B] 

c) $((算術表達式))

C=$(($A+$B))

d) expr 算術表達式

  註意:表達式中各操作數及運算符之間要有空格。而且要使用命令引用。

C=`expr $A + $B`

4.3.2 案例(算術運算符的使用)

a) 業務描述

  給定一個用戶,獲取其密碼警告期限,然後判斷用戶密碼使用期限是否已經小於警告期限,如果小於,則是顯示“WARN”,否則顯示密碼還有多少天到期。

  密碼警告期限:距離警告的天數;密碼使用期限:密碼有效期減去使用的時間。

b) 編寫腳本

  首先,需要知道:

  $date +%s 可以獲得今天的秒數

  $cat /etc/shadow 可以獲得密碼使用時間。

cat /etc/shadow | grep root

技術分享圖片

  觀察上圖,這一行數據是以冒號隔開的。其中的17021是指,創建密碼的那天距離1970年1月1日的天數;0表示密碼有效期最短0天;99999表示密碼最多99999天有效;7表示有效期還剩7天時警告。

  然後,編輯腳本如下:

vim /opt/shell/test6.sh
#!/bin/bash
#
UN=$1 #根據傳入的數據作為變量UN的值
C_D=`grep "^$UN" /etc/shadow | awk -F: {print $3} ` #grep "^$UN" /etc/shadow是從shadow中找到到以$UN這個用戶名開頭的,print $3是密碼的創建時間,這裏使用-F來指定:分隔符。加反引號是一個命令。
M_D=`grep "^$UN" /etc/shadow | awk -F: {print $5} ` #{print $5}是找到最長有效期
W_D=`grep "^$UN" /etc/shadow | awk -F: {print $6}` #{print $6}是警告時間
NOW_D=$[`date +%s`/86400] #`date +%s`獲得當前系統時間(秒),使用數學運算符[]相當於數>學的括號,除以一天的秒數
U_D=$[$NOW_D-$C_D] #算出了使用的天數
L_D=$[$M_D-$U_D] #算出剩余的天數
if [ $L_D -le $W_D ];then #判斷註意空格,剩余時間小於等於警告時間輸出warn.
  echo "warn."
else
  echo "left day is $L_D" #其他情況輸出剩余天數
fi

c) 執行腳本

sh test6.sh root

技術分享圖片

4.4 文件與字符串測試

4.4.1 文件與字符串測試語法

a) 文件測試

  文件測試需要中括號 [ ]

  -e FILE:測試文件是否存在

  -f FILE:測試文件是否為普通文件

  -d FILE:測試文件是否為目錄

  -r 當前用戶有沒有讀的權限

  -w 當前用戶有沒有寫的權限

  -x 當前用戶有沒有執行的權限

b) 字符串測試

  == 等號兩端需要空格

  !=

  -n string : 判斷字符串是否為空

  -s string : 判斷字符串是否不空

4.4.2 案例(文件與字符串測試)

a) 業務描述

  指定一個用戶名,判斷此用戶的用戶名和它的基本組,組名是否相同。

b) 編寫腳本

vim /opt/shell/test7.sh
#!/bin/bash
#
if !id $1 &>/dev/null ; then
  echo "No such user."
  exit 12
fi
if [ $1 == `id -n -g $1` ] ;then #`id -n -g $1`是指輸入的用戶的組的名稱(-g是組id)
  echo "相同"
else
  echo "不相同"
fi

c) 執行腳本

sh test7.sh root

4.5 循環語句

4.5.1 for循環

a) for循環的語法結構

for 變量 in 列表; do
  語句
done

b) for循環中的列表  

  for循環中的列表尤為重要,列表可以是以空格隔開的一組數,可以理解為數組;也可以動態生成列表。

  靜態數組列表

  以下是個靜態列表的實例:

for I in 1 2 3 4 5 ; do 
    語句
done

  動態生成列表

  動態生成列表有三種方式,分別為:

  第一,{1..100}。中間兩個點,1-10可以成{1..10}

  第二,seq [起始數] [跨度數] 結束數。seq相當於oracle中sequence,遞增的序列,[起始數]帶有中括號意為可以不給,默認為1;[跨度數]帶有中括號也可以不給,默認值為1;結束數必須給,不然會造成死循環。

  第三,ls /PATH/ 文件列表。這個實際上是通過for循環遞歸目錄下的文件。

c) for循環案例一

  業務描述:

  將那些可以登錄的用戶查詢出來,並且將用戶的帳號信息提取出來,後放入/tmp/test.txt文件中,並在行首給定行號。

  編寫腳本:

vim /opt/shell/test8.sh
#!/bin/bash
#
J=1
count=`wc -l /etc/passwd | cut -d  -f1` #wc -l /etc/passwd 可以獲得passwd文件的行數;cut -d  -f1中-d是指定切割符,-f1是取第一個。
#count=`wc -l /etc/passwd | awk -F " " {print $1}` #也可以使用awk
for I in `seq $count`;do #這裏使用seq生成列表,以1開始,跨度為1,到$count結束。
   u_shell=`head -$I /etc/passwd | tail -1 | cut -d: -f7` #head取前I行,然後管道到tail -1拿到最後一行,實際上就是第I行,然後管道到cut按:切割,取第七個(shell)。
   u_n=`head -$I /etc/passwd | tail -1 | cut -d: -f1` #取用戶
   if [ $u_shell == /bin/bash ];then #判斷字符串是否相等,相等表示可以登錄。可以登錄>就將/tmp/users.txt
      echo "$J $u_n " >> /tmp/users.txt #輸出時加上編號,這裏不能使用I,因為I是行號,業務要求是新的序號,需要連續的。
      J=$[$J+1] #相當於J++
   fi
done

  執行腳本:

sh test8.sh root

d) for循環案例二

  業務描述:

  計算100以內所有能被3整除的整數的和。

  編寫腳本:

vim test9.sh
#!/bin/bash
#
sum=0
for I in {1..100};do
  S=$[$I%3] #對3取模(求余數)
  if [ $S -eq 0 ];then #數字比較只能用-eq,不能使用等號
    sum=$[$sum+$I]
  fi
done
echo "$sum"

  執行腳本:

sh test9.sh

e) for循環案例三

  業務描述:

  傳給腳本一個參數:目錄,輸出該目錄中文件最大的,文件名和文件大小。

  編寫腳本:

vim test10.sh
#!/bin/bash
#
#首先判斷參數是否正確
if [ -f $1 ];then #-f 判斷傳過來的目錄是否是文件
  echo "Arg is error."
  exit 2
fi

if [ -d $1 ];then #-d 是判斷$1是目錄
  c=`du -a $1 | sort -nr | wc -l`  #du -a $1是列出$1目錄下的所有文件和目錄; sort -nr是按數值降序排列; wc -l獲取行數。但是這樣把目錄頁選出來了
  for I in `seq $c`;do
    f_size=`du -a $1 | sort -nr | head -$I | tail -1 | awk {print $1}` #取到第I行,拿到文件大小
    f_path=`du -a $1 | sort -nr | head -$I | tail -1 | awk {print $2}` #取到第I行,拿到文件路徑
    if [ -f $f_path ];then #判$f_path是否是文件,是文件就直接輸出,並退出循環
       echo -e "the biggest file is $f_path \t $f_size"
       break
    fi
  done
fi

  執行腳本:

sh test10.sh /path/

4.5.2 while循環

a) 格式一

while 條件;do
    語句
    [break]
done

b) 格式二(死循環)

while true
do
  語句
done 

c) 格式三(死循環)

while :
do
    語句
done

d) 格式四(死循環)

while [ 1 ]
do
    語句
done 

e) 格式五(死循環)

while [ 0 ]
do
    語句
done

f) while循環案例

  業務描述:

  使用echo輸出10個隨機數。

  編寫腳本:

vim /opt/shell/test11.sh
#!/bin/bash
#
I=1
while [ $I -le 10 ] ; do
        echo -n "$RANDOM"
        I=$[$I+1]
done

  執行腳本:

sh test11.sh

  作者原創作品,轉載請註明出處http://www.cnblogs.com/yangp/p/8511321.html。

快速掌握Shell編程