Shell編程的究極系列(基本語法)

恩,這是由奇技淫巧組成的語言,沒有之一。奇技淫巧到,他的語法出如今你無數個靈光一現時刻~python

我:蔣哥你shell好厲害!mysql

蔣哥:恩git

我:要多久啊sql

蔣哥:(基於Java,python,js等)要一段時間docker

1、理解shell、變量、環境變量

shell,殼,計算機命令終端,每一個unix體系的機器最原始都是使用這個與計算機操做系統進行交互的,相似的,windows操做系統下滿就是bat(批處理文件)。每次Linux系統啓動,登錄以後,都會默認啓動幾個這種命令終端,若是圖形界面有的話,通常會默認進入圖形界面。圖形界面之上,咱們也可使用Terminal這種模擬的命令終端,進行命令操做,這東西相似於windows的cmd。shell

一、變量

對於編程這麼多年,變量很好理解,無非就是一個瞎幾把命名的一個單詞,賦值一個value嘛~真的嗎?奇技淫巧來了,請看下面數據庫

# ①
var1=123
# ②
var2='jicheng${var1}'
# ③
var3="jicheng${var1}"
# ④
var4=`pwd`
# ⑤
var4 = `pwd`
# ⑥
var4=$(pwd)

①shell變量沒類型,弱類型,萬物皆字符串,即便是這個,也是var1值是"123"express

②③單雙引號不同,單引號內部不轉義,寫啥是啥;雙引號會轉義,會替換變量值編程

④這種上引號是執行引用起來的字符串,因此被引用的要能被執行,結果賦值給等號左面的變量ubuntu

⑤這種是錯誤語法。賦值操做的等號兩邊不能有任何空格,這種有空格的,後面會將

⑥和④同樣

完了?遠遠還沒,來點晉級的:

# ①
var4=${var1:-value}
var4=${var1-value}
# ②
var5=${var1:=value}
var5=${var1=value}
# ③
var6=${var1:?value}
var6=${var1?value}
# ④
var7=${var1:+value}
var7=${var1+value}

解說:不帶冒號表明var1是否爲未賦值,帶了表明var1是否爲未賦值或是空值

①var1未賦值(或空值)將"value"賦值給var4變量

②var1未賦值(或空值)將"value"賦值給var4,且一樣賦值給var1

③var1未賦值(或空值)將"value"做爲標準錯誤輸出,用於進行變量判空用的

④var1未賦值(或空值),什麼都不作,不然value代替var1的值,賦值給var7,var1的值不變

二、shell腳本內部變量

一個真正的shell腳本在執行過程當中,會有一些特殊的變量,這個對理解shell程序相當重要,來戰:

#!/bin/bash

# shell_test.sh腳本


echo $0
echo $(basename $0)
echo $1 $2
echo $#
echo $*
echo "$*"
echo $@
echo "$@"
  1. $0:

    獲取執行腳本本身的文件名,執行的輸入全字符串,例如./shell.sh、/etc/profile_test.sh,若是想單純獲取名字,要用:$(basename $0)

  2. $1,$2,$3……..$n這種

    回去執行名稱後面對應的輸入參數,$1表明第一個,$n表明第n個

  3. $#

    獲取輸入參數一共有多少個

  4. $*與$@

    不加引號,兩個同樣:都是將入參使用IFS定義的分隔符進行拆分;加了就有區別:$*會把全部入參當作一個總體的字符串,而$@會將使用雙引號的入參當作一個,不進行IFS分隔符拆分操做。下面代碼是兩個變量的對比試驗:

#!/bin/bash

# 對$*與$@作對比試驗

# 將帶引號的入參進行IFS分割拆分
for i in $*;
do
	echo $i
done


# 恩,這就是一個大的字符串
for i in "$*";
do
	echo $i
done


# 將帶引號的入參進行IFS分割拆分
for i in $@;
do
	echo $i
done

# 不將帶引號的入參進行IFS分割拆分
for i in "$@";
do
	echo $i
done

三、系統+環境變量

每次運行shell腳本,或者咱們開啓一個Terminal,或者咱們遠程登錄一個命令終端,其實都是有預先設置好的變量存在的,這個就叫:環境變量。若是是全局的環境變量,就是說,每一個用戶登陸都能看到的,能夠說是全局變量,相似於windows裏面的系統變量的概念;若是環境變量只是當前用戶可以看到的,那就是局部環境變量,相似於windows裏面的環境變量的概念。內部有幾個很關鍵的腳本文件,專門用來設置全局與局部變量的,大體總結下:

a、全局環境變量設置(系統變量)

  1. /etc/profile:最先啓動,在系統登陸的時候就進行調用,其後都不會調用的設置環境變量的腳本。其中會調用執行下面的下面的每個文件
  2. /etc/profile.d/*.sh:被1文件調用的各個文件,若是要設置全局的系統環境變量,最好分類別分別寫個.sh可執行文件放入這個文件夾,系統啓動之時會被調用執行,設置環境變量

下面是profile的源代碼:

# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).

if [ "`id -u`" -eq 0 ]; then
# id -u 顯示登錄用戶id,0是root用戶
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
else
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
fi
export PATH

# 下面的判斷語法是否是很熟悉呢?
# 這裏主要進行PS1這個環境變量的設置,主要影響交互式命令號行頭標示符
if [ "${PS1-}" ]; then
  if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
    if [ -f /etc/bash.bashrc ]; then
    # 到這裏就是判斷當前是否是不是使用sh且存在bash.bashrc文件
      . /etc/bash.bashrc
    fi
  else
  	# sh模式(不是bash模式),就是所謂的POSIX兼容模式
    if [ "`id -u`" -eq 0 ]; then
      PS1='# '
    else
      PS1='$ '
    fi
  fi
fi

if [ -d /etc/profile.d ]; then
# 第一個判斷文件夾是否存在
  for i in /etc/profile.d/*.sh; do
  # 遍歷文件夾中的每一個文件
    if [ -r $i ]; then
    # 判斷單個文件是否存在,執行
      . $i
    fi
  done
  unset i
fi

b、局部用戶環境變量設置

下面針對用戶環境變量設置是按順序,找到的就會執行,沒找到就不執行:

  1. $HOME/.bash_profile:有可能存在,針對我的用戶設置用戶級別的環境變量
  2. $HOME/.bash_login:有可能存在,針對我的用戶設置用戶級別的環境變
  3. $HOME/.profile:有可能存在,針對我的用戶設置用戶級別的環境變

通常狀況下,會只存在一個。例如,Debian系統下面,用戶目錄下面只有.profile。但是內部又調用執行類另一個同目錄的文件:.bashrc。下面是真是的源碼:

# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
# 若是BASH變量存在就執行這個代碼塊
  if [ -f ~/.bashrc ]; then
  # 若是.bashrc這個文件存在在用戶目錄中,就執行這個代碼塊
    . ~/.bashrc
    # 這種的另外一種寫法是:source ~/.bashrc
  fi
fi

mesg n || true
# ~/.bashrc: executed by bash(1) for non-login shells.

# 這裏都註釋掉了,認真閱讀可見,咱們平時經常使用的命令簡寫都出自這裏,只不過這個系統定製性的給去掉了

# Note: PS1 and umask are already set in /etc/profile. You should not
# need this unless you want different defaults for root.
# PS1='${debian_chroot:+($debian_chroot)}\h:\w\$ '
# umask 022

# You may uncomment the following lines if you want `ls' to be colorized:
# export LS_OPTIONS='--color=auto'
# eval "`dircolors`"
# alias ls='ls $LS_OPTIONS'
# alias ll='ls $LS_OPTIONS -l'
# alias l='ls $LS_OPTIONS -lA'
#
# Some more alias to avoid making mistakes:
# alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'

2、字符串與數字運算

對於平常的使用,最多見的莫過於字符串拼接,與數字的運算,下面分別來講說這兩方面

一、字符串拼接

#!/bin/bash

str1=hello
str2=shell
str3=world
echo $str1$'\t'$str2$'\t'$str3
# 輸出爲:hello	shell	world
  • shell中沒有所謂的「+」鏈接符,直接用啥放啥,直接放!
  • 中間不能有空格,不然字符串會被分割拆分
  • 拆分字符串的環境變量是IFS,默認值爲:IFS=$' \t\n'
  • 奇技淫巧又來了:上面介紹過單引號裏面不轉義,而這裏又轉義了呢?f**c!關鍵點在於這裏的單引號前面多了個$。下面是bash官方文檔針對這一段的解釋,我翻譯了一下
$'string'這種類型的值會被特殊解析。這種值,內部使用反斜槓加一個字符,都會按照標準c的模式進行轉義。若是存在這種反斜槓轉義序列, 映射關係以下:
       /a     警告(鈴聲)
       /b     退格
       /e     一個轉義字符
       /f     換頁
       /n     換行
       /r     回車
       /t     水平製表符
       /v     垂直製表符
       //     反斜槓
       /'     單引號
       /nnn   八進制
       /xHH   十六進制
       /cx    控制x字符(沒弄明白這個何時用)

二、算術運算

shell自己就是萬物皆字符串,因此對於純數學計算支持的不是很好。大致上分兩大類:整數運算與浮點數運算。實踐的方式有三種,前兩種是整數運算,最後一種是浮點數運算

a、使用expr

#!/bin/bash 
# An example of using the expr command 
var1=10
var2=20
var3=$(expr $var2 / $var1)
echo The result1 is $var3
var3=$(expr $var2 \* $var1)
echo The result2 is $var3

這東西很差使用,並且是很是。其中還會支持以下的一些操做:

  • ARG1 | ARG2:兩個參數誰不爲空(被賦了值),結果就是誰,都不爲空,結果爲ARG1,不然結果爲0
  • ARG1 & ARG2:兩個參數都不爲空(被賦值了),結果爲ARG1,不然爲0
  • ARG1 < ARG2
  • ARG1 <= ARG2
  • ARG1 = ARG2
  • ARG1 != ARG2
  • ARG1 >= ARG2
  • ARG1 > ARG2

這東西,若是是取結果值,賦值給其餘變量的話,通過實際的測驗,還有一些貓膩,請仔細看下面代碼與註釋:

#!/bin/bash

# 測試expr取值操做

expr $var \| 3 #若是是沒被賦值參與了運算的,這時候會報錯
$var=
expr $var \| 3 #這種被賦了個空值,也會報錯
$var=1
expr $var \| 3 #這種纔不會報錯,按照正常邏輯返回值:1
$var=
expr "$var" \| 3 #這種也不會報錯,直接取了var的空值,結果爲:3

expr 3 | 2 #不轉義「或」符號報錯,shell會當作管道符號處理,而2並非命令

expr 4\*3 #這種直接返回結果:4*3(字符串)
expr 4\* 3 #這種語法錯誤,操做符的兩邊必需要有空格

可見shell下面的算術運算操做並不容易,很是多的小細節,恩就是所謂的「奇技淫巧」!

b、使用中括號:$[ARG1*ARG2]

中括號主要是爲了取代expr而出現的,主要也是用於整數的算術運算與邏輯運算,而且可以方便的取值操做。畢竟,使用expr有太多奇技淫巧,太多要注意的語法細節了,而中括號,偏偏能很好的解決這些。下面是一些語法舉例:

#!/bin/bash

# 中括號算術運算舉例

[4*4] #單獨這樣會有語法錯誤,由於中括號語法要配合$來使用,報錯信息:[4*4]:未找到命令
[ 4*4 ] #這種其實沒有語法錯誤,中括號裏面兩邊有了空格這種,屬於判斷語句,下面會介紹到
[4 * 4] #這種操做符有了空格,會被命令解釋器拆分紅三個命令:[四、*、4],而[4並非命令,因此報錯
$[4*6] #這種表達式自己是沒問題的,可以求結果,但是結果求出來了shell會運行結果,顯然24不是命令,報錯


var=$[3 *4] #這種是最正確的,操做符左右不用擔憂空格、也不用擔憂轉義問題,求結果,並賦值操做

仔細閱讀上面註釋,就能夠了。是否是感受shell很是死扣一些細節?這個腳本語言就這個德行!

c、浮點數解決方案:bc

恩,最後了,一步步闖關以後,這東西是「公主」!最好控制的東西(就是奇技淫巧少)。好控制不表明好用,來看看bc兩種模式

①交互模式

bash計算器其實是一種編程語言,它容許在命令行中輸入浮點表達式,而後解釋並計算該 表達式,最後返回結果。bash計算器可以識別:

  • 數字(整數和浮點數)
  • 變量(簡單變量和數組)
  • 註釋(以#或C語言中的/* */開始的行)
  • 表達式
  • 編程語句(例如if-then語句)
  • 函數

下面是我一頓瞎幾把搞的代碼:

jicheng:~/Project/shell_test$ bc -q #q是不顯示軟件介紹

34.5
23*34.645645
796.849835
234*3-(3/ 234)#支持混合運算
702
3/ 234
0
3.0/ 234.023
0
scale= 6 #默認是0,不設置,小於1的除法結果顯示0
3.0/ 234.023
.012819
3/ 234
.012820
var1=10 #內部支持變量
var1 * 4
40
var2 = var1 / 5
print var2
2
quit 
jicheng:~/Project/shell_test$
②腳本中使用bc

基本語法爲:variable=$(echo "options; expression" | bc)

#!/bin/bash
var1=100
var2=45
var3=$(echo "scale=4; $var1 / $var2" | bc) # 看到如何設置精確值
echo The answer for this is $var3

3、輸入輸出與重定向

這兩個話題也是一大難。哎~難點在於不少不少的細節,仍是那樣,難在正所謂的"奇技淫巧",咱們分開2小節來說:

  1. 輸入
  2. 輸出與重定向

一、輸入

對於一個計算機語言來講,輸入,無非就是:用戶交互式輸入與文件內容的輸入。Shell裏面也同樣。針對用戶的輸入,能夠從命令傳入,也可實時讀取用戶的外設輸入。而針對這些個輸入,shell會有相應的讀取與保存方式,下面分開來介紹

a、命令傳參式輸入

在執行命令時候,默認以空格分割,緊接着在命令後面進行傳入的連續字符串。分割方式是空格,若是入參自己帶空格,要使用引號或者雙引號,進行引用包裹

#!/bin/bash

# ./shell_test var1 var2 var3

#正確獲取文件名的真實寫法
echo `basename $0`

echo $1
echo $2
echo $3
# 一共有多少個入參
echo $#
# 最後一個入參
echo ${!#}

大部分知識都在上面變量小節介紹過了,上面關鍵的是最後兩行代碼:

  • $#返回當前入參是多少個,若是沒有命令執行後面的入參,那值爲0
  • 雖然目測能夠經過${$#}獲取最後一個參數,但是,問題是不行,要經過使用${!#}獲取

b、"一波帶走":處理輸入的方式

有時候,咱們會動態獲取輸入參數,並循環作處理,例以下面的方式:

#!/bin/bash
# shell_test.sh文件
count=1
for var in "$@"
do
	echo "Parameter #$count: $var"
	count=$[ $count + 1 ]
done

# 運行:./shell_test var1 var2
# 結果:
# Parameter #1: var1
# Parameter #2: var2

很簡單。再高一個難度:若是咱們想對入參進行分類,區分配置選項與輸入參數兩種,例如咱們常用的

ls -al不就是有配置選項al。看下面咱們怎麼用實際的代碼進行實現:

#!/bin/bash
# shell_test.sh文件

while [ -n "$1" ]
do
	case "$1" in
		-a) echo "Found the -a option";;
		-b) echo "Found the -b option";;
		-c) echo "Found the -c option";;
		*) echo "$1 is not an option";;
	esac
	shift
done
<<COMMENT
	輸入 /shell_test.sh -a -c -b -d
    結果:
    Found the -a option
    Found the -c option
    Found the -b option
    -d is not an option
COMMENT

shift是shell內部的一個命令,能夠左移一個輸入參數,被移動的入參,將會消失,例如,入參有var一、var2,shift以後,入參只有var2。shift能夠有參數,後面加上一個數字,表示要左移多少個參數。上面的代碼用到告終構化的語句,後面章節會進行介紹。接下來咱們再高一個級別:我不只僅想要配置選項的入參,我還想要配置參數的入參,那咱們怎麼定義呢?針對輸入方式,咱們能夠用一個特殊字符來隔離選項與參數,例如'—',看下面:

#!/bin/bash

# shell_test.sh

while [ -n "$1" ]
do
	case "$1" in
		-a) echo "Found the -a option";;
		-b) echo "Found the -b option";;
		-c) echo "Found the -c option";;
		# 這裏作了特殊處理:shift掉了--入參,並結束while循環
		--) shift
			break ;;
		*) echo "$1 is not an option";;
	esac
	shift
done

count=1
for var in "$@"
do
	echo "Parameter #$count: $var"
	count=$[ $count + 1 ]
done

<<COMMENT
    輸入:./shell_test.sh -a -b -c -- var1 var2 var3
    輸出:
    Found the -a option
    Found the -b option
    Found the -c option
    Parameter #1: var1
    Parameter #2: var2
    Parameter #3: var3
COMMENT

已經有點規模了,哈哈。接下來咱們還想進一步,ls -al相似於這種,人家成熟的命令但是能夠合併配置選項的,但是上面的代碼並不支持。接下來就是最終的解決方案,要用到一個很好的命令:getopts。下面是代碼,邊寫邊解說:

#!/bin/bash

while getopts :ab:c opt
do
	case "$opt" in
	a) echo "Found this -a option";;
	b) echo "Found this -b option,with value $OPTARG";;
	c) echo "Found this -c option";;
	*) echo "Unknow";;
	esac
done

shift $[ $OPTIND -1 ]
count=1
for param in "$@"
do
	echo "Parameter $count: $param"
	count=$[ $count+1 ]
done

<<COMMENT
    輸入:./shell_test -ab var1 -c var2 var3
    輸出:
    Found this -a option
    Found this -b option,with value var1
    Found this -c option
    Parameter 1: var2
    Parameter 2: var3
COMMENT

一波解釋:

  • getopts可以保存全部的入參,根據指定的選項來對入參進行格式化:getopts optstring variable
  • optstring配置選項的,選項後面加冒號表明選項有參數要解析。最開始加冒號表示去掉錯誤信息
  • getopts每次都保存一個配置選項,咱們要循環處理
  • 若是這個選項入參有參數值的話,$OPTARG就是用來存儲這個的
  • $OPTIND用來保存當前getopts正在處理的參數位置

至此咱們對於命令傳參式輸入就全介紹完了,下面開始說用戶交互式輸入

c、交互式輸入

這種比較簡單一些,就是用read這個命令一頓瞎幾把搞~直接上代碼,直接解釋:

#!/bin/bash

# 最基本的read,阻塞式的輸入,用echo的n(不換行)參數配合打印提示行
echo -n "Enter your name: "
read name 
echo "Hello $name, welcome to my program. "


# 使用read的p參數,直接打印提示航
read -p "Please enter your age: " age 
days=$[ $age * 365 ] 
echo "That makes you over $days days old! "


# 使用read的t參數,來進行超時控制,單位是秒
if read -t 5 -p "Please enter your name: " name 
then
	echo "Hello $name, welcome to my script" 
else
	echo "Sorry, too slow! " 
fi

# 使用read的s參數,能夠隱藏輸入的字符,好比密碼輸入
read -s -p "Enter your password: " pass
echo "Is your password really $pass? "

d、文件中輸入

文件中的輸入,主要仍是運用read這個命令,也是直接代碼:

#!/bin/bash 
# reading data from a file # 
count=1
# test是一個文本文件
cat test | while read line 
do
echo "Line $count: $line"
count=$[ $count + 1]
done
echo "Finished processing the file"

line是記錄了每一行的記錄,咱們能夠根據需求,繼續對每行的數據進行切割處理

二、輸出與重定向

在shell中,輸出永遠和重定向脫不開干係,由於在shell中不管是輸出到屏幕(標準輸出)或是輸出到文件,都是要靠重定向標準輸出來 實現的,因此掌握各類重定向的方式,很關鍵,一樣,這裏也是好幾種「奇技淫巧」的方式進行重定向。下面我分別說說幾個方面。

a、文件描述符

在運行shell腳本的時候,會有一系列的文件描述符,用數字表示,最多有9個,最多見的就是系統設定好的前三個:

  • 0:標準輸入(STDIN):默認從鍵盤讀取數據
  • 1:標準輸出(STDOUT):默認輸出到屏幕
  • 2:標準錯誤輸出(STDERR):默認輸出到屏幕

咱們能夠重定向這幾個,同時也能夠本身建立新的文件描述符:

#!/bin/bash

# 默認狀況的echo,直接輸出到標準輸出
echo "echo 2 STDOUT"

# 咱們能夠進行重定向標準輸出,這樣就會輸出到具體的文件當中去
echo "echo 2 other" >test.out

## 雙大於號的輸出表示追加到一個文件,不是覆蓋
echo "echo 2 other" >>test.out

# 下面的jiad是沒有的目錄,標準錯誤會輸出到屏幕
ls /jiad

# 對標準錯誤輸出進行重定向,直接在大於號前面加數字
ls /jiad 2>test.out

# 全局性永久重定向,使用exec命令原理是啓動一個新的shell而後進行重定向
exec 2>test.out
ls /jiad #這種錯誤會直接輸出到test.out文件,由於標準錯誤全局性的重定向了

# 這種就是自定義文件描述符3,而後重定向到test.out文件
exec 3>test.out
# 引用文件描述符的方式,這樣輸出的結果就覆蓋到了test.out文件裏面了
echo "echo 2 other" >&3

上面主要的語法都已經列出來,仔細閱讀註釋就能夠了。另外,這地方對重定向的箭頭左右是否有空格,要求不是特別的嚴格,有沒有功能都不會受影響,請放心使用。

b、「暫存」文件描述符

當咱們使用exec命令永久性的重定向了標準輸出的話,如何再重定向回來本來的呢?也很簡單,就如咱們最開始學習編程時候學的「三段式」交換數字同樣,先暫存,再操做,最後再反賦值就能夠了,就以下面:

#!/bin/bash

exec 3 >&1
exec 1 >test.out

echo "temp save file description"

# 看到沒?直接把1再重定向會3,3裏面,最開始是重定向到標準輸出的
exec 1 >&3

c、輸入重定向

輸入的文件 標示符是0,重定向使用小於號,簡單的一個重定向代碼以下:

#!/bin/bash

# 0和符合之間不能有空格,其實他們是一體的,表示:0文件描述符輸入重定向
exec 0< out
# 這種狀況,cat並不會進行交互式輸入了,而會從out文件進行輸入
cat

一樣,咱們能夠經過暫存的方式進行恢復重定向的標準輸入

#!/bin/bash
  
# 下面的小於號左右不能有空格存在,不然語法報錯,&符號與前面不能有空格
exec 3<&0
exec 0<out

# 這裏的cat的輸入已經變成了out文件
cat
# 這個命令能夠查詢0,1,2,3幾個文件描述符的指向文件
lsof -a -p $$ -d 0,1,2,3

# 恢復標準輸入的文件描述符
exec 0<&3
lsof -a -p $$ -d 0,1,2,3

# 這個時候會變成交互式輸入
cat

在輸入重定向這裏,有個"奇技淫巧",就是咱們可以腳本中動態設置標準輸入(重定向),而不用非得使用原生的標準輸入或是文件輸入,語法以下:

command << delimiter
    document
delimiter

具體咱們舉一個例子:

#!/bin/bash 
# read file and create INSERT statements for MySQL

outfile='members.sql'
IFS=',' 
while read lname fname address city state zip 
do
	# 注意這個地方
	cat >> $outfile << EOF
        INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('$lname', '$fname', '$address', '$city', '$state', '$zip'); 
    EOF 
done < ${1}

上面的cat,實際上是直接在命令行執行cat這種模式的,只不過在執行的時候,重定向了兩個東西:

  • 輸出:使用雙大於號方式,進行追加式到文件member.sql文件中
  • 輸入:使用了上述的語法,進行了腳本命令式重定向,輸入就是兩個EOF中間的內容

頗有意思,這種方式,很普遍被運用,例如:shell腳本對mysql數據庫執行一條sql語句

4、結構化語句

說到了最後,這一章說完,幾乎shell的語法部門就結束了。剩下的,就是深刻大海般的」命令海「!這些命令,有系統自帶的,也有咱們本身安裝的工具帶的命令,或是咱們安裝的軟件帶來的命令,例如git、docker等。針對結構化的問題,也算是放輕鬆了,由於結構化的語法,是平時咱們最最多見的了。

一、ifelse

這兩個東西,在任何計算機語言體系中,可謂是老生常談了。很少說,下面是基本語法,有兩種模式:

# 第一種模式
if command 
then 
	command1
	command2
	......
fi

# 第二種模式
if command; then
	command1
	command2
	......
fi

# 多級別的ifelse
if command1 
then
	commands 
elif command2 
then
	more commands 
else
	more commands
fi

特別注意點:shell中沒有所謂的判斷真(true)假(false)一說,這裏的if語句中的判斷語句是一個具體的命令(command)。根據命令的執行退出的狀態值來判斷是否要執行if裏面的代碼塊: 若是這個命令正確執行,那退出值爲0,if代碼塊就被執行;若是命令執行過程當中發生錯誤,退出值就不爲0,那if代碼塊就不被執行。這就是shell中的語法。後面的while、for、util等都是這種規則

下面就是基本的shell中if語句的代碼:

#!/bin/bash 
# Testing nested ifs - use elif & else 
# 
testuser=NoSuchUser 

if grep $testuser /etc/passwd
then
	echo "The user $testuser exists on this system." 

elif ls -d /home/$testuser 
then
	echo "The user $testuser does not exist on this system."
	echo "However, $testuser has a directory." 

else
	echo "The user $testuser does not exist on this system."
	echo "And, $testuser does not have a directory."
fi

二、ifelse常規使用

雖然ifelse的判斷語句是運行一個命令,而後根據退出值來判斷的,可是能不能讓咱們仍是像以往那樣,根據語句的真假來判斷是否執行語句塊呢?恩,test命令就是解決這個問題的:

if test condition 
then 
	commands 
fi
#!/bin/bash
my_var='jicheng'
if test $my_var
then
	# 這個會被執行,由於test命令會讓退出值爲0
	echo "myvar"
fi
my_null_var=""
if test 
then 
	# 空的test的退出值不爲0,因此這個地方不會被執行
	echo "1"
elif test my_null_var
then
	# test空串也是會退出值不爲0的,因此這裏也不會執行
	echo "2"
else
	echo "3"
fi

固然,shell中不會傻呵呵讓你一直寫test的,提供了一個替換語法:

# 注意這裏:中括號內部的condition兩邊必需要有空格,不然語法有錯,上面立的flag,在這裏能夠拔下來了
if [ condition ] 
then 
	commands 
fi

針對性的,test的語法,能夠對三種類型進行條件判斷下面分三個小節

a、數值比較

因爲shell中萬物皆字符串,因此對於數值的比較,要使用關鍵字進行,不難,下面就是:

  • n1 -eq n2:相等
  • n1 -ge n2:大於等於
  • n1 -gt n2:大於
  • n1 -le n2:小於等於
  • n1 -lt n2:小於
  • n1 -ne n2:不相等

下面是簡單的例子:

#!/bin/bash

value1=10 
value2=11 
# 
if [ $value1 -gt 5 ] 
then
	echo "The test value $value1 is greater than 5" 
fi 
# 
if [ $value1 -eq $value2 ] 
then
	echo "The values are equal" 
else
	echo "The values are different" 
fi

注意shell中只能處理整數,對於浮點數的test,會報錯。不支持

b、字符串比較

其實也不難,主要注意和sort的區別。下面是基礎的一些字符串比較表達式:

  • str1 = str2
  • str1 != str2
  • str1 < str2
  • str1 > str2
  • -n str1:檢查str1的長度是否非0
  • -z str1:檢查str1的長度是否爲0

注意點有幾個:

  • 注意符號的兩邊要有空格,不然就是賦值操做了!(前面的flag又拔了)
  • 大於和小於要進行轉義,不然就是重定向啦![ "test" \> "Test" ]
  • sort命令對字符排序是根據本地系統的語言設置排序的,小寫出如今大寫前面
  • 這裏的test比較,是根據ASCII順序的,大寫是小於小寫的

下面是簡單的測試代碼:

#!/bin/bash 
# testing string sort order 
val1=Testing 
val2=testing 
#
if [ $val1 \> $val2 ]
then
	echo "$val1 is greater than $val2" 
else
	echo "$val1 is less than $val2" 
fi

c、文件比較

最後一類比較測試頗有多是shell編程中最爲強大、也是用得最多的比較形式。它容許你測 試Linux文件系統上文件和目錄的狀態:

  • -e file:存在
  • -s file:存在且非空
  • -f file:存在且是一個文件
  • -d file:存在且是一個目錄
  • -r file:存在且可讀
  • -w file:存在且可寫
  • -x file:存在且可執行
  • -O file:存在且屬於當前用戶
  • -G file:存在且屬於當前用戶組
  • file1 -nt file2:file1比file2新
  • file1 -ot file2:file1比file2舊

d、可使用符合條件表達式

  • [ condition1 ] && [ condition2 ]
  • [ condition1 ] || [ condition2 ]
#!/bin/bash 

if [ -d $HOME ] && [ -w $HOME/testing ] 
then
	echo "The file exists and you can write to it" 
else
	echo "I cannot write to the file" 
fi

e、雙小括號與雙中括號

雙小括號模式是(用於算術運算比較):

(( expression ))

支持:var++、var--、++var、--var、!(非)、~(位求反)、**(冪運算)、<<(左移)、>>(右移)、&(位與)、|(位或)、&&(邏輯與)、||(邏輯或)。另外這種模式下大於小於號不用轉義!

雙中括號的模式是(用於字符串比較):

[[ expression ]]

雙方括號裏的expression使用了test命令中採用的標準字符串比較。但它提供了test命 令未提供的另外一個特性——模式匹配(pattern matching)。

#!/bin/bash 

if [[ $USER == r* ]] 
then
	echo "Hello $USER" 
else
	echo "Sorry, I do not know you" 
fi

多說兩句:在這裏,對於這兩個東西,能夠玩的花樣有點多,平常使用的也會總去使用這兩個運算符的花樣,這個我在後面單獨開文章細講」奇技淫巧「!

三、case使用

通常高級靜態語言中,都有switch的語法,shell中也有。不過這裏的使用方式,有點"詭異":

case variable in 
pattern1 | pattern2) commands1;; 
pattern3) commands2;; 
*) default commands;; 
esac

和Java中不一樣,這裏每一個匹配了但括號裏面的模式並執行了command以後,就結束case語句了,並且使用兩個;符號結尾。而Java中不使用break的話,會一直匹配下面全部的模式並執行裏面代碼塊。

#!/bin/bash 
# using the case command 
# 
case $USER in 

rich | barbara) 
	echo "Welcome, $USER"
	echo "Please enjoy your visit";;
testing)
	echo "Special testing account";; 
jessica)
	echo "Do not forget to log off when you're done";;
 *)
	echo "Sorry, you are not allowed here";;
esac
<<COMMENT
輸出:
Welcome,
rich Please enjoy your visit
COMMENT

四、for語句

for語句在shell中,有兩種方式,一種是原生的,一種是C語言風格的。可是C風格的for語句,在一些bash中並非很支持(ubuntu16中的dash就不支持),因此我看,大部分仍是使用原生的for語句爲主,咱們這裏就先不講C風格的for。原生的for,在一些小細節上面仍是要注意的,下面是基本的語法:

for var in list 
do 
	commands 
done
注意引號問題
#!/bin/bash
for var in I don't know if this'll work
do
	echo "$var"
done

結果是:

I

dont know if thisll

work

顯然不符合咱們的預期,由於shell把兩個引號中間的部分當作一個字符串了,有兩種解決方法:

#!/bin/bash
# 一種進行轉義,一種使用雙引號括起來
for var in I don \'t know if "this'll" work
do
	echo "$var"
done
注意字符串總體性
#!/bin/bash
for var in Nevada New Hampshire New Mexico New York
do
	echo "Now going to $var"
done

結果是:

Now going to Nevada

Now going to New

Now going to Hampshire

Now going to New

Now going to Mexico

Now going to New

Now going to York

Now going to North

Now going to Carolina

顯然就不符合。咱們可使用雙引號括起來:

#!/bin/bash
for var in Nevada "New Hampshire" "New Mexico" "New York"
do
	echo "Now going to $var"
done
注意使用IFS拆列表
#!/bin/bash 
# reading values from a file

file="states"

IFS=$'\n' 
for state in $(cat $file) 
do 
	echo "Visit beautiful $state" 
done

這樣,對於文件中的分割,只會使用換行符

注意經常使用的讀取目錄文件
#!/bin/bash

for file in /home/rich/test/* 
do
	if [ -d "$file" ] 
	then 
		echo "$file is a directory" 
	elif [ -f "$file" ] 
	then 
		echo "$file is a file" 
	fi 
done

注意使用雙引號括起來了文件名的引用變量,是由於有可能文件名有空格,若是不括起來,語法就會有錯誤了!

五、while循環

經歷了前面的if與for的歷練,對於while這裏的語法相對來講就沒有那麼難理解了:

while test command 
do 
	other commands 
done

簡單的例子:

#!/bin/bash
var1=10
while [ $var1 -gt 0 ] 
do
	echo $var1
	var1=$[ $var1 - 1 ] 
done

六、until循環

這個和while的邏輯相反:while是知道test退出碼爲非零的時候,結束循環,而這個是當test循環退出碼爲零的時候結束循環,語法也是相似:

until test command 
do 
	other commands 
done

例子不舉了

5、結束語

到此幾乎最最基本的shell語法就差很少都講了。剩下的,就是如"汪洋"的命令大軍了。哪怕是這些基礎的語法,我都感受,是由命令組成的,這就是shell編程,瘋狂的奇技淫巧~接下來我會短平快的寫幾個小案例和shell中經常使用的命令,例如awk,sed,find等,如如有具體的語法」靈光一現「,都會update到本篇幅中,恩就這樣。

相關文章
相關標籤/搜索