bash編程初體驗之正則

bash編程初體驗之正則


  • 認識bash編程

  • 變量與賦值

  • 算術與邏輯運算

  • 條件測試與退出狀態


認識bash編程

Bash(GNU Bourne-Again Shell)是許多Linux發行版的默認Shell,我們要認識的bash中,就是在bash的環境下的一種編程。

衆所周知,程序=指令+數據,由此也決定了兩種不同的編程風格,過程過與對象式;

過程式:以指令爲中心,數據服務於指令
對象式:以數據爲中心,指令服務於數據

而shell也給我們提供了一種編程能力,在shell上編寫的腳本程序,都是解釋執行,而不是通過編譯,因爲我們Bash自身就是解釋器。

所有的編程語言都有三種基本的邏輯處理方式:順序結構、選擇結構、循環結構傳說中,只要你掌握這三種結構,你就可以編織一個屬於自己的星球了,這就是《***帝國》的故事!

編程語言的基本結構:數據+表達式+語句,在Linux中,一切皆文件,而shell腳本是一個包含命令或聲明的文本文件,有一定的格式要求,# 表示註釋,且首行要符合shebang機制:

#!/bin/bash
#!/usr/bin/python 
#!/usr/bin/perl

對於運維來說,shell腳本是一個解放我們生產力的工具,可自動化常用的命令;執行系統管理和故障排除;創建簡單的應用及處理文本與文件。

當寫完一個腳本之後,就需要運行,shell腳本的運行主要有兩種方法:

1.給予權限執行,在命令行上指定腳本的相對或絕對路徑 
2.直接運行bash解釋器,將腳本作爲解釋器程序的參數來運行

在以bash直接運行腳本時,有如下兩種調試方法:

1. bash -n /PATH/TO/SOME_SCRIPT
    檢測腳本有無語法錯誤 
2. bash -x /PATH/TO/SOME_SCRIPT
    調試執行

一個簡單的shell腳本:

#!/bin/bash
#author: liansir
#Version: 1.0
#Description: display a Hello World!
echo "Hello World!"

變量與賦值

變量一詞,小學數學中應該就接觸到了,如常量,變量,常量就是指固定不變的量,在數學中就是一個給定的數值;變量就是指一個變化的量,形如x, y之類的;而編程中的常量與變量與數學中的常量與變量其內含是極其相似的,只是它們存在的環境變了而已,在shell腳本編程中,變量就是一段命名的地址空間,用變量可以爲代表你想要表達的東西,譬如把變量看作“水果”的話,它可代表蘋果、桃子等。

在shell腳本中,變量有兩種類型:

強類型:定義變量時必須指定數據類型
弱類型:定義變量時無需指定數據類型

說到數據類型,就涉及到了數據的存儲格式,數據的存儲主要有字符型與數值型,而數值型主要有整形與浮點型,但是bash是不支持浮點型數據的

再回到我們的變量,強類型的變量參與運算時一定得符合其類型要求;而弱類型的變量在參與運算時會自動進行隱式轉換。既然如此,變量的類型也就會有如下作用:

1.數據存儲格式 
2.參與的運算
3.表示的數據範圍

變量的命名法則:

1.不能程序中的保留字,如if, for
2.只能使用數字、字母及下劃線,且不能以數字開頭
3,駝峯命名法
4.見名知義

在bash中,根據變量的生效範圍主要有如下幾種類型的變量:

本地變量:生效範圍爲當前shell進程,對當前shell的子shell及其它shell均無效
環境變量:生效範圍爲當前shell進程及其子進程 
局部變量:生效範圍爲當前shell進程中的某代碼片斷(通常指函數)
位置變量:$1, $2, $3...來表示,用於讓腳本在代碼中調用通過命令行傳遞給它的參數
特殊變量:$?, $0, $*, $@, $#
只讀變量:只能聲明,不能修改與刪除, 可用readonly name或declare -r name來聲明。

在bash中,變量的賦值有直接賦值與引用賦值兩類,所謂直接賦值,類似於name=value,即直接給定一個變量名對其進行賦值,注意等號=兩邊無空格;引用賦值又分爲變量引用與命令:

變量引用:name="$USER"
命令引用:name=`COMMAND` 或 name=$(COMMAND)

再來看看命令行中的變量引用:$name, ${name}

而引用也有強弱之別:

" ":弱引用,變量會被替換爲變量值 
' ':強引用,變量會被當作原字符串

當我們要查看已經定義的所有變量時,可直接使用 set命令,而刪除變量則使用unset name.

環境變量,在bash中佔有重要的一席之地,其聲明與賦值格式爲:

export name=VALUE
declare -x name=VALUE

通過以下命令來顯示環境變量:

export: 可聲明與顯示環境變量
env:顯示系統中已存在的環境變量
printenv:類似於env

小結下bash中的環境變量:

USER, UID, PATH, SHELL, HOME, HISTSIZE, HISTFILE, HISTFILESIZE,  
HISTCONTROL, HISTTIMEFORMAT, PS1, PWD, OLDPWD

另外,位置變量也不容我們小覷啊!

$0: 命令本身
$1,$2:對應第1與第2個參數,shift [n]變換位置
$*: 傳遞給腳本的所有參數,且全部參數合爲一個字符串
$@: 傳遞給腳本的所有參數,每個參數爲獨立字符串
$#: 傳遞給腳本的參數個數
    注:$@ 與 $* 只在被雙引號引起來的時候有差異

練習

1、編寫腳本/root/bin/systeminfo.sh,顯示當前主機系統信息,包括主機名,IPv4地址,操作系統版本,內核版本,CPU型號,內存大小,硬盤大小。

[root@centos7 ~/bin#]cat systeminfo.sh 
#!/bin/bash
#
echo -e  "The following is systeminfo:\n"
echo "The hostname is `hostname`"
echo "The IP is `ifconfig |sed -n '2p' |sed -n -r 's@.*inet.(.*) net.*@\1@p'`"
echo "The OS version is `cat /etc/redhat-release`"
echo "The kernel version is `uname -r`"
echo "The CPU is `cat /proc/cpuinfo |grep 'model name' |sed -n '1p' |cut -d: -f2 |tr -d ' 'i`"
echo "The memory size is `cat /proc/meminfo |sed -n '1p' |cut -d: -f2 |tr -d ' '`"
echo "The disk size is `fdisk -l |sed -n '2p' |cut -d: -f2 |cut -d, -f1`"
[root@centos7 ~/bin#]

相對路徑執行(要給予其執行權限)

wKioL1euu8iQiLlvAACDRU1ocU0263.png

2、編寫腳本/root/bin/backup.sh,可實現每日將/etc/目錄備份到/root/etcYYYY-mm-dd中.

[root@centos7 ~/bin#]cat baskup.sh 
#!/bin/bash
#
cp -r /etc/ /root/etc`date +%F`
[root@centos7 ~/bin#]

絕對路徑執行(要給予執行權限)

 wKiom1euyZqiSN6mAABZa-DB17s075.png

3、編寫腳本/root/bin/disk.sh,顯示當前硬盤分區中空間利用率最大的值

[root@centos7 ~/bin#]cat checkdisk.sh 
#!/bin/bash
#
NumDisk=`df |grep 'sd' |tr -s ' ' |cut -d' ' -f5 |cut -d% -f1`
[[ $NumDisk -gt 50 ]] && wall disk will be full!;exit 
[root@centos7 ~/bin#]

直接利用bash執行(不需要執行權限)

wKiom1euvXvyK8pPAABUvlBBkKs656.png

4、編寫腳本/root/bin/links.sh,顯示正連接本主機的每個遠程主機的IPv4地址和連接數,並按連接數從大到小排序.

[root@centos7 ~/bin#]cat links.sh 
#!/bin/bash
#
echo -e "遠程主機的IP地址及連接數:\n"netstat -nt | tr -s ' ' |cut -d' ' -f5 |tail -n +3 |cut -d: -f1 |sort |uniq -c
[root@centos7 ~/bin#]

wKioL1euvZqiNHRoAAA-HkeLNnM108.png


算術邏輯運算

算術運算

bash中的運算:命令let

help let:

id++, id--      variable post-increment, post-decrement
++id, --id      variable pre-increment, pre-decrement
-, +            unary minus, plus
!, ~            logical and bitwise negation
**              exponentiation
*, /, %         multiplication, division, remainder
+, -            addition, subtraction
<<, >>          left and right bitwise shifts
<=, >=, <, >    comparison
==, !=          equality, inequality
&               bitwise AND
^               bitwise XOR
|               bitwise OR
&&              logical AND
||              logical OR
        expr ? expr : expr
                        conditional operator
        =, *=, /=, %=,
        +=, -=, <<=, >>=,
        &=, ^=, |=      assignment

實現算術運算的幾種方式:

1.let var=算術表達式
2.var=$[算術表達式]
3.var=$((算術表達式))
4.var=$(expr arg1 arg2 arg3...)
5.declare -i var = 數值6.echo '算術表達式' |bc

注:乘法 * 在有些場合中需要轉義 \*
bash內有內建的隨機數生成器:$RANDOM (1-32767)
    echo $[$RANDOM%50]: 0-49之間的隨機數

除此,還有以下增強型賦值:

+=, -=, *=, %= /=
let var+=3  自加3後自賦值
let var++   自加1
let var-=1  自減1後自賦值
let var--   自減1

邏輯運算

邏輯運算,又稱布爾運算,就是關於一個事件真或假的運算,通常用來測試真假值。

true    false
1       0

與:同真爲真,一假爲假
或:同假爲假,一真爲真
非:非真爲假,非假爲真

短路運算:
    短路與:
    第一個爲0,結果必定爲0
    第一個爲1,第二個必須要參與運算
    
    短路或:
    第一個爲1,結果必定爲1
    第一個爲0,結果必須要參與運算
    
    異或:^
    不同爲真,相同爲假

練習

1:寫一個腳本/root/bin/sumid.sh,計算/etc/passwd文件中的第10個用戶和第20用戶的ID之和。

[root@centos7 ~/bin#]cat sumid.sh 
#!/bin/bash
#
num1=`cat /etc/passwd |sed -n -e '10p' |cut -d: -f3`
num2=`cat /etc/passwd |sed -n -e '20p' |cut -d: -f3`
let var=$num1+$num2echo "第十個用戶與第二十個用戶的UID之和爲:"echo $var
[root@centos7 ~/bin#]

wKiom1euyViiyq1aAAAtuQSkCw8322.png

2:寫一個腳本/root/bin/sumspace.sh,傳遞兩個文件路徑作爲參數給腳本,計算這兩個文件中所有空白行之和.

[root@centos7 ~/bin#]cat sumspace.sh 
#!/bin/bash
#
fSpace1=`grep '^$' $1 |wc -l`
fSpace2=`grep '^$' $2 |wc -l`
sumSpace=$[ $fSpace1+$fSpace2 ]echo "兩個文件的空白行之和爲:$sumSpace"
[root@centos7 ~/bin#]

wKiom1euyTiza2aHAAA2_fRg0UI731.png

3:寫一個腳本/root/bin/sumfile.sh,統計/etc, /var, /usr目錄中共有多少個一級子目錄和文件.

[root@centos7 ~/bin#]cat sumfile.sh 
#!/bin/bash 
#
#統計/etc, /var, /usr目錄中共有多少個一級子目錄和文件
Detc=`tree -L 1 /etc/ |wc -l`
Fetc=`ls -lR /etc/ |wc -l`

Dvar=`tree -L 1 /var/ |wc -l`
Fvar=`ls -lR /var/ |wc -l`

Dusr=`tree -L 1 /usr/ |wc -l`
Fusr=`ls -lR /usr/ |wc -l`
echo "/etc目錄中共有一級子目錄$Detc 個,文件 $Fetc 個"
echo "/var目錄中共有一級子目錄$Dvar 個,文件 $Fvar 個"
echo "/usr目錄中共有一級子目錄$Dusr 個,文件 $Fusr 個"
[root@centos7 ~/bin#]

wKioL1euyPiwsMHxAABWsuw7Yv0959.png


條件測試與退出狀態

退出狀態

每一條命令的執行,要麼成功,要麼失敗,要麼成功一半,失敗一半,(這種情況嚴格地說屬於失敗),對於我們的shell腳本而言,也是一樣的,shell腳本不就是把簡單的命令通過有效的組織,以編程的思想而實現的麼!這也不就是Linux的哲學之一,將單一用途的命令通過組合而完成複雜的任務!

對於我們用戶而言,一個腳本執行成功與否,一般情況下通過肉眼凡胎也能看見,大不了 echo $? 一下,返回值爲0,則表示執行成功,爲1-255之間的數則表示錯誤。至此,腦袋稍微一轉便知Linux系統就是以返回值爲0或爲1而判斷命令是否執行成功的。一個程序的發起就是一個進程,程序的運行有期壽命,進程自然也有其退出的狀態,而進行就是通過退出狀態來報告成功與失敗的

0   代表成功,1-255代表失敗
$?  變量保存最近命令的退出狀態 

另外,有兩種聚焦命令的方法:
    複合式:date; who |wc -l
        命令會一個接一個地運行
    子shell: (date; who |wc -l) >> /tmp/trace
        所有的輸出都被髮送給單個STDOUT和STDERR

既然 echo $? 可以查找最近一個命令的退出狀態碼,靈活的LInux也允許我們自定義退出狀態碼:exit [n]

注意:腳本中一旦遇到exit命令,腳本會立即終止,終止退出狀態取決於exit命令
後面的數字;如果未給腳本指定的退出狀態碼,整個腳本狀態執行碼取決於腳本中
執行的最後一條命令的狀態碼。

條件測試

所謂條件測試,就是判斷某需求是否滿足,需要專門的測試機制來實現,測試機制必然有相應的表達式與測試命令。

測試命令:

test EXPRESSION
[ EXPRESSION ][[ EXPRESSION ]]
注:EXPRESSION前後必須要有空白字符

test

test - check file types and compare values

( EXPRESSION )
 EXPRESSION is true
! EXPRESSION
 EXPRESSION is false

組合條件測試:         
COMMAND1 && COMMAND2
COMMAND1 || COMMAND2
! COMMAND
如:[ -e FILE ] && [-r FILE ]

EXPRESSION1 -a EXPRESSION2          
    both EXPRESSION1 and EXPRESSION2 are true
EXPRESSION1 -o EXPRESSION2
    either EXPRESSION1 or EXPRESSION2 is true
! EXPRESSION

必須使用測試命令進行:
~#] [ -z "$HOSTNAME" -o "$HOSTNAME"=="localhost.localdomain" ] &&
hosname liansir.com 
~#] [ -f /etc/cat -a -x /etc/cat  ] cat /etc/issue

注:&& 與 || 可視爲“條件性”操作符,靈活運行這兩個操作符,可達到簡單if語句
的作用;
    && 代表條件性的 AND THEN 
    || 代表條件性的 OR ELSE 
  ------------------------------------------------------------------------------  
-n STRING
          the length of STRING is nonzero
-z STRING
          the length of STRING is zero

==: 是否等於
>:  是否大於,比較的是ascii碼
<:  是否小於
=~  左側字符串是否能夠被右側的PATTERN所匹配

注:此表達式一般用於[[ ]] 中;
    用於字符串比較時用到的操作數都應該使用引號;
------------------------------------------------------------------------          

數值測試:        
INTEGER1 -eq INTEGER2
          INTEGER1 is equal to INTEGER2
INTEGER1 -ge INTEGER2
          INTEGER1 is greater than or equal to INTEGER2
INTEGER1 -gt INTEGER2
          INTEGER1 is greater than INTEGER2
INTEGER1 -le INTEGER2
          INTEGER1 is less than or equal to INTEGER2
INTEGER1 -lt INTEGER2
          INTEGER1 is less than INTEGER2
INTEGER1 -ne INTEGER2
          INTEGER1 is not equal to INTEGER2    
------------------------------------------------------------------------

文件測試:
    
   雙目測試:
   FILE1 -ef FILE2
          FILE1 and FILE2 have the same device and inode numbers
          # FILE1與FILE2是否指向同一設備上的相同inode號

   FILE1 -nt FILE2
          FILE1 is newer (modification date) than FILE2
          # FILE1是否新於FILE2

   FILE1 -ot FILE2
          FILE1 is older than FILE2
          # FILE1是否舊於FILE2
          
   存在性及類別測試
   -b FILE
          FILE exists and is block special

   -c FILE
          FILE exists and is character special

   -d FILE
          FILE exists and is a directory

   -e FILE
          FILE exists

   -f FILE
          FILE exists and is a regular file
          
   -h FILE
          FILE exists and is a symbolic link (same as -L)
          
   -L FILE
          FILE exists and is a symbolic link (same as -h)
          
   -p FILE
          FILE exists and is a named pipe
          
   -S FILE
          FILE exists and is a socket
  
   文件權限測試
   -r FILE
          FILE exists and read permission is granted
   -w FILE
          FILE exists and write permission is granted

   -x FILE
          FILE exists and execute (or search) permission is granted
          
   文件特殊權限測試
   -u FILE
          FILE exists and its set-user-ID bit is set
   -g FILE
          FILE exists and is set-group-ID
   -k FILE
          FILE exists and has its sticky bit set
          
   文件大小測試 
   -s FILE
          FILE exists and has a size greater than zero
          是否存在且非空
          
   文件是否打開:
   -t FD  file descriptor FD is opened on a terminal
          文件描述符是否已經被打開且與某終端相關
   -O FILE
          FILE exists and is owned by the effective user ID
          當前有效用戶是否爲文件屬主
   -G FILE
          FILE exists and is owned by the effective group ID
          當前有效用戶是否爲文件屬組
   -N FILE 
          文件自上一次被讀取之後是否被修改過

對於test命令,有以下兩種格式:

長格式:
test "$A" == "$B" && echo "String are equal"
test "$A" -eq "$B" && echo "String are equal"

簡寫:
[ "$A" == "$B" ] && echo "String are equal"
[ "$A" -eq "$B" ] && echo "String are equal"

練習

  1. 寫一個腳本/root/bin/argsnum.sh,接受一個文件路徑作爲參數;如果參數個數小於1,則提示用戶“至少應該給一個參數”,並立即退出;如果參數個數不小於1,則顯示第一個參數所指向的文件中的空白行數。

[root@centos7 ~/bin#]cat argsnum.sh 
#!/bin/bash
#
[[ $# -lt 1 ]] && echo "至少應該給一個參數"
[[ $# -ge 1 ]] && grep '^$' $1 |wc -l 
[root@centos7 ~/bin#]

wKioL1euyMawL-JxAABfW_X8atI752.png

  1. 寫一個腳本/root/bin/hostping.sh,接受一個主機的IPv4地址做爲參數,測試是否可連通。如果能ping通,則提示用戶“該IP地址可訪問”;如果不可ping通,則提示用戶“該IP地址不可訪問”.

[root@centos7 ~/bin#]cat hostping.sh 
#!/bin/bash
#
[[ $# -eq 1 ]] && (echo "$1" |grep -q -E '\<((([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\>' && (
ping -c1 -W1 $1 &> /dev/null && echo "該IP地址可訪問!" || echo \ "該IP地址不可訪問!" )) || echo "請給出一個合法IP地址!"; exit 2[root@centos7 ~/bin#]

wKiom1euyJvA-8vlAAC-6lXXAvE615.png

  1. chmod -rw /tmp/file1,編寫腳本/root/bin/per.sh,判斷當前用戶對/tmp/fiile1文件是否不可讀且不可寫。

[root@centos7 ~/bin#]cat per.sh 
#!/bin/bash
#[ ! -r $1 ] && [ ! -w $1 ] && echo "當前用戶對$1 文件不可讀且不可寫!"
[root@centos7 ~/bin#]

wKioL1euyGuS-6tpAABXRURSfeo260.png


  1. 編寫腳本/root/bin/nologin.sh和login.sh,實現禁止和充許普通用戶登錄系統。

[root@centos7 ~/bin#]cat nologin.sh 
#!/bin/bash
#
[ -e /etc/nologin ] && echo "已禁用普通用戶登錄系統" || touch /etc/nologin && echo "已禁用普通用戶登錄系統"
[root@centos7 ~/bin#]

[root@centos7 ~/bin#]cat login.sh 
#!/bin/bash
#
[ -e /etc/nologin ] && rm -f /etc/nologin && echo "已允許普通用戶登錄系統!"
[root@centos7 ~/bin#]cat nologin.sh 
#!/bin/bash
#
[ -e /etc/nologin ] && echo "已禁用普通用戶登錄系統" || touch /etc/nologin && echo "已禁用普通用戶登錄系統"
[root@centos7 ~/bin#]

先禁用liansir用戶:

wKiom1euyDPCQyCpAAAslOHP2r4151.png

wKiom1euyA3RIQqvAAAxLhYtNS4825.png

wKioL1eux8axVxmQAAAKfnu75fY228.png

允許liansir用戶登錄:

wKiom1eux4filXDLAAAsYdft3t4788.png

wKioL1eux2byKnweAABEguo4LdM147.png

  1. 計算1+2+3+...+100的值

[root@centos7 ~/bin#]cat 100sum.sh 
#!/bin/bash
#
# 計算1-100或100-1之間的和
echo "1-100之和爲:"echo {1..100} |tr ' ' '+' |bc
[root@centos7 ~/bin#]

wKiom1euxznCeoaiAAAqX-yvXlk104.png

  1. 計算從腳本第一參數A開始,到第二個參數B的所有數字的總和,判斷B是否大於A,否提示錯誤並退出,是則計算之。

[root@centos7 ~/bin#]cat sumAB.sh 
#!/bin/bash
#
[[ $# -eq 2 ]] && (
[[ $1 -lt $2 ]] && seq $1 $2 |tr '\n' '+' |grep -o '.*[^+]' |bc \
 || echo "$1 大於 $2,無法計算!") || echo "請給出兩個數值!"
[root@centos7 ~/bin#]

wKioL1euxxuiSCxfAABc86rO5jc609.png


小探位置變量:

位置變量:用於讓腳本在代碼中調用通過命令行傳遞給它的參數

  • $1,$2.. $10,$11...MAX

  • $*,$@ 的區別

  • $#

位置變量知多少?

wKiom1euxuyQraX_AAC4hSrE_AI509.png

既然參數的個數與所有參數的顯示都正常,那爲什麼從第十個參數起位置變量就引用出錯呢?

再看:

wKiom1euxsDD2maeAACZQCzNW40629.png

至此,我們需要對比一下weizhi1.sh與weizhi2.sh的腳本了。

wKioL1euxo6injICAAC1J_132As721.png

於是,我們可得出以下結論:$1-$9代表第一到第九個參數,但第十及以上的參數需要用大括號表示,如${10}

$* 與 $@ 都表示所有參數,那二者又有何區別?

[root@centos7 ~/bin#]cat wzhi.sh
#!/bin/bash
#e
cho "命令本身: $0"
echo "1st is $1"
echo "2st is $2"
echo "all agrs are $*"
echo "all agrs are $@"
[root@centos7 ~/bin#]

wKiom1euxk6ga8BMAAA_P3Z_q0U748.png

可見$0表示命令本身,這個並不難理解。但$@ 與$*的區別還是看不出來!

[root@centos7 ~/bin#]cat wzhi2.s
#!/bin/bash#wzhi.sh "$*"
echo ------------------------------
wzhi.sh "$@"
[root@centos7 ~/bin#]

wKioL1euxeKxCzoqAACgEZnn--4871.png

由此可見,$*中在參數會被看成是一個整體,$@中的每個參數是獨立的,注意這是在被雙引號引起的時候

如果把雙引號換作單引號:

wKioL1euxXaylbJ-AABZC8Zgesg644.png

wKiom1euxL3ju8RzAACHmQ_Apzw516.png

最終,我們有了這樣的結論:表示位置變量的$* 與 $@ 在被雙引號引起的時候是有差別的,$*中在參數會被看成是一個整體,$@中的每個參數是獨立的;而如果是被單引號引起來,則二者並無任何差別

bash編程初體驗(一)先到這兒!

2016.8.13

止戰


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章