shell基礎知識查缺補漏

wang03發表於2022-01-14

最近在看《Linux程式設計(第4版)》,其中有一個章節主要講了shell指令碼方面的,內容不細,但是利用較短的篇幅講的也不少了。對我們自己來說也是一個查缺補漏的過程,所以就寫下這篇讀書筆記,方便自己隨時翻看。

本文的主要內容是來自《Linux程式設計(第4版)》,另外一小部分內容來自《Linux Shell指令碼攻略(第3版)》。

本篇文章,內容是比較粗,很多內容也都沒有寫,比如重要的awk,sed之內的命令本篇文章壓根就沒有提及到。如果這篇文章能讓大家對自己掌握的shell有一個查缺補漏,加深記憶的作用的話,我覺的這篇文章的目的也就達到了。

指令碼註釋以#符號開始,一直持續到該行的結束。

請注意第一行#! /bin/sh,它是一種特殊形式的註釋,#!字元告訴系統同一行上緊跟在它後面的那個引數是用來執行本檔案的程式。本文章中,/bin/sh是預設的shell程式。

1.變數

變數之前通常並不需要事先宣告。

變數區分大小寫。

在預設情況下,所有變數都被看作字串並以字串來儲存,即使被賦值為數值時也是如此。

在shell中,可以通過在變數名前加一個$符號來訪問它的內容。

當為變數賦值時,只需要使用變數名,該變數會根據需要被自動建立。

注意,如果字串裡包含空格,就必須用引號把它們括起來。此外,等號兩邊不能有空格。


read命令將使用者的輸入賦值給一個變數

1.1 使用引號

一般情況下,指令碼檔案中的引數以空白字元分隔(例如,一個空格、一個製表符或者一個換行符)。如果你想在一個引數中包含一個或多個空白字元,你就必須給引數加上引號

例項指令碼:

#!/bin/sh

myvar="Hi there"

echo $myvar
echo "$myvar"
echo '$myvar' # ''內的內容會當做原樣輸出
echo \$myvar  # \會對$進行轉移,也會原樣輸出

echo Enter some text
read myvar

echo '$myvar'  no equals $myvar

exit 0

輸出結果

1.2 環境變數

當一個shell指令碼程式開始執行時,一些變數會根據環境設定中的值進行初始化。這些變數通常用大寫字母做名字。

3.引數變數

$@和$*之間的區別:

雙引號裡面的$@把各個引數擴充套件為彼此分開的域,而不受IFS值的影響。

訪問指令碼程式的引數,使用$@是明智的選擇。

1.3 declare、typeset

這兩個命令也可以用來宣告變數,作用完全相同。

它的主要引數有以下幾個:

​ -i 宣告整型變數

​ -r 宣告變數為只讀

​ -a 宣告一個陣列變數

​ -f 顯示指令碼中定義的函式體

​ -F 顯示指令碼中定義的函式

示例

#!/bin/sh

declare -i num=10
echo $num

declare -i n1="hello"
echo $n1

declare -ri readony=200
#readony=201   只讀變數,不能修改 。這句執行會報錯

declare -a arr='([0]="a" [1]="b" [2]="c")'
echo ${arr[0]}
echo ${arr[1]}
echo ${arr[2]}


func_1(){
  echo "Function 1"
}
func_2(){
  echo "Function 2"
}
echo "=========== declare -F ==========="
declare -F

echo "=========== declare -f ==========="
declare -f

exit 0

2.條件

2.1 test或[命令

這兩個命令都是在shell的布林判斷。當使用[命令時,我們還使用符號]來結尾。

[符號和被檢查的條件之間留出空格。

檢查一個檔案是否存在,命令是test -f

可以這樣寫

if test -f fred.c
then
...
fi

或者

if [ -f fred.c ]
then
...
fi

test命令的退出碼(表明條件是否被滿足)決定是否需要執行後面的條件程式碼。


如果把then和if放在同一行上,就必須要用一個分號把test語句和then分隔開。如下所示:

if [ -f fred.c ]; then
...
fi

test命令可以使用的條件型別可以歸為3類:字串比較、算術比較和與檔案有關的條件測試。

  • 字串比較

  • 算術比較

  • 檔案有關的條件測試

3 控制結構

3.1 if語句

if語句非常簡單:它對某個命令的執行結果進行測試,然後根據測試結果有條件地執行一組語句。

可以把then和if放在同一行上,就必須要用一個分號把test語句和then分隔開

模版:

if condition
then
  statements
elif condition; then    #then和if放在同一行上,用;分隔開
  statements
else
  statements
fi

示例:

#!/bin/sh

read name

if [ $name = "zhangsan" ]
then
  echo "zhang 1"
elif [ $name = "wangwu" ]; then
  echo "wang 2"
else
  echo "sorry name error"
  exit 1
fi
exit 0

執行結果:

如果在執行sh ifdemo.sh後,不輸入任何內容,直接Enter鍵,就會報錯

這個問題主要是當我們直接輸入Enter鍵後,變數name就是一個空字串,使得上面的條件變數變成了if [ = "zhangsan" ]elif [ $name = ];這就不是一個合法的條件。

這時,可以通過給變數加上引號,if [ "$name" = "zhangsan" ],如果是個空字串,就會變成if [ "$name" = "zhangsan" ]

所以正確的示例指令碼應該是這樣:

#!/bin/sh

read name

if [ "$name" = "zhangsan" ]
then
  echo "zhang 1"
elif [ "$name" = "wangwu" ]; then
  echo "wang 2"
else
  echo "sorry name error"
  exit 1
fi
exit 0

3.2 for語句

for結構來迴圈處理一組值,這組值可以是任意字串的集合。

模版:

for variable in value
do 
  statements
done

如果把do和for放在同一行上,就必須要用一個分號把value語句和do分隔開。如下所示:

for variable in value; do
  statements
done

示例程式碼

#!/bin/sh

for foo in bar fud 43 #如果給bar fud 43加上引號,變成"bar fud 43" 就會當做一個變數
do
  echo $foo
done
exit 0

示例程式碼:

列出當前目錄下以.sh結尾的檔案

#!/bin/sh

for file in $(ls *.sh);do
  echo $file
done
exit 0

3.3 while語句

模版:

while condition
do
  statements
done

如果把do和while放在同一行上,就必須要用一個分號把condition語句和do分隔開。如下所示:

while condition; do
  statements
done

示例程式碼:

#!/bin/sh
read name
while [ "$name" != "zhangsan" ]; do
  echo "name err"
  read name
done
echo "input correct"        

3.4 until語句

模版:

until condition
do
  statements
done

如果把do和until放在同一行上,就必須要用一個分號把condition語句和do分隔開。如下所

until condition; do
 statements
done

示例程式碼:

#!/bin/sh
#當指定使用者登陸後,輸出登陸 並退出
until who |grep "$1" >/dev/null
do
  sleep 1
done
echo  "$1" has just logged in

exit 0

3.5 case語句

模版:

case variable in
  pattern [ | pattern] ...) statements;;
  pattern [ | pattern] ...) statements;;
  ...
esac
~     

示例程式碼:

#!/bin/sh

read timeofday

case "$timeofday" in
  yes) echo "Good Morning";;
  no ) echo "Good Afternoon";;
  #這裡用*來匹配前面條件都不匹配的場景,類似 default分支
  *  ) echo "Sorry, answer not recongnized";; 

esac
exit 0

case命令會對用來做比較的字串進行正常的萬用字元擴充套件,因此你可以指定字串的一部分並在其後加上一個*萬用字元。


示例程式碼:

  • 合併匹配模式
#!/bin/sh

read timeofday

case "$timeofday" in
  yes | y | Yes | YES ) echo "Good Morning";;
  n* | N* ) echo "Good Afternoon";;
  *  ) echo "Sorry, answer not recongnized";;
esac
exit 0

3.5 命令列表

shell提供了一對特殊的結構,專門用於處理命令列表,它們是AND列表和OR列表

  • AND列表

    AND列表結構允許你按照這樣的方式執行一系列命令:只有在前面所有的命令都執行成功的情況下才執行後一條命令。

    模版:

    statement1 && statement2 && statement3 && ...
    

    從左開始順序執行每條命令,只有一條命令返回的是true,它右邊的下一條命令才能夠執行。&& 的作用是檢查前一條命令的返回值。

    示例程式碼:

    #!/bin/sh
    
    touch file_one
    rm -f file_two
    
    if [ -f file_one ] && echo "hello" && [ -f file_two ] && echo " there"
    then
      echo "in if"
    else
      echo "in else"
    fi
    
    exit 0
    
  • OR列表

    OR列表結構允許我們持續執行一系列命令直到有一條命令成功為止,其後的命令將不再被執行。

    模版:

    statement1 || statement2 || statement3 || ...
    

    從左開始順序執行每條命令,只有一條命令返回的是false,它右邊的下一條命令才能夠執行。||的作用是檢查前一條命令的返回值。

當前,AND列表也可以和OR列表結合在一起。

3.6語句塊

在某些只允許使用單個語句的地方(比如在AND或OR列表中)使用多條語句,你可以把它們括在花括號{}中來構造一個語句塊。

4.函式

要定義一個shell函式,你只需寫出它的名字,然後是一對空括號,再把函式中的語句放在一對花括號中。

模版:

function_name(){
}

示例程式碼:

#!/bin/sh

#定義函式
foo(){
    echo "Function foo is executing"
}

echo "script starting"
foo   #呼叫函式
echo "script ended"

exit 0

當一個函式被呼叫時,指令碼程式的位置引數($*、$@、$#、$1、$2等)會被替換為函式的引數。


可以通過return命令讓函式返回數字值。讓函式返回字串值的常用方法是讓函式將字串儲存在一個變數中,該變數然後可以在函式結束之後被使用。此外,還可以echo一個字串並捕獲其結果。

#!/bin/sh

foo(){
    echo "Hello World"
}

result="$(foo)"

echo "result = "$result
fa(){
    return 123
}
result1="$(fa)"  #對於有返回值的函式,不能通過這種方式獲取返回值
result2=fa
echo "result1 = "$result1
echo "result2 = "$result2
exit 0

可以使用local關鍵字在shell函式中宣告區域性變數,區域性變數將僅在函式的作用範圍內有效。

如果一個區域性變數和一個全域性變數的名字相同,前者就會覆蓋後者,但僅限於函式的作用範圍之內。

示例程式碼:

#!/bin/sh

sample_text="global variable"

foo(){
    local sample_text="local variable"
    echo "Function foo is executing"
    echo $sample_text
}

echo "script starting"
echo $sample_text

foo

echo "script ended"
echo $sample_text

exit 0

5.命令

可以在shell指令碼程式內部執行兩類命令。一類是可以在命令提示符中執行的“普通”命令,也稱為外部命令(externalcommand),一類是我們前面提到的“內建”命令,也稱為內部命令(internal command)。內建命令是在shell內部實現的,它們不能作為外部程式被呼叫。

通常情況下,命令是內部的還是外部的並不重要,只是內部命令的執行效率更高。

5.1 :命令

冒號(:)命令是一個空命令。它偶爾會被用於簡化條件邏輯,相當於true的一個別名。

由於它是內建命令,所以它執行的比true快。

5.2 .命令

點(.)命令用於在當前shell中執行命令:

. ./shell_script

比較常用的地方就是我們修改環境變數時使用。比如. /etc/profile等等

通常,當一個指令碼執行一條外部命令或指令碼程式時,它會建立一個新的環境(一個子shell),命令將在這個新環境中執行,在命令執行完畢後,這個環境被丟棄,留下退出碼返回給父shell。

但外部的source命令和點命令(這兩個命令差不多是同義詞)在執行指令碼程式中列出的命令時,使用的是呼叫該指令碼程式的同一個shell。

因為在預設情況下,shell指令碼程式會在一個新建立的環境中執行,所以指令碼程式對環境變數所作的任何修改都會丟失。而點命令允許執行的指令碼程式改變當前環境。

5.3 eval命令

eval命令允許你對引數進行求值。它是shell的內建命令,通常不會以單獨命令的形式存在。

示例程式碼:

#!/bin/sh

foo=10
x=foo
eval y='$'$x
echo $y

5.4 exec命令

exec命令有兩種不同的用法。

  • 將當前shell替換為一個不同的程式。

    exec ls ifdemo.sh 
    

    指令碼中的這個命令會用ls命令替換當前的shell。指令碼程式中exec命令後面的程式碼都不會執行,因為執行這個指令碼的shell已經不存在了。

  • 修改當前檔案描述符

    exec 3< afile
    

    檔案描述符3被開啟以便從檔案afile中讀取資料。

5.5 exit n命令

exit命令使指令碼程式以退出碼n結束執行。

在shell指令碼程式設計中,退出碼0表示成功,退出碼1~125是指令碼程式可以使用的錯誤程式碼。其餘數字具有保留含義。

5.6 export命令

export命令將作為它引數的變數匯出到子shell中,並使之在子shell中有效。

在預設情況下,在一個shell中被建立的變數在這個shell呼叫的下級(子)shell中是不可用的。export命令把自己的引數建立為一個環境變數,而這個環境變數可以被當前程式呼叫的其他指令碼和程式看見。

被匯出的變數構成從該shell衍生的任何子程式的環境變數

set -a或set -o allexport命令將匯出它之後宣告的所有變數

5.7 expr命令

expr命令將它的引數當作一個表示式來求值。它的最常見用法就是進行如下形式的簡單數學運算:

x=	`expr $x + 1`

反引號(``)字元使x取值為命令expr $x+1的執行結果,也可以用語法$( )替換反引號``

主要的一些求值計算

在較新的指令碼程式中,expr命令通常被替換為更有效的$((...))語法

5.8 set命令

set命令的作用是為shell設定引數變數。許多命令的輸出結果是以空格分隔的值,如果需要使用輸出結果中的某個域,這個命令就非常有用。

例如,我們可以通過set命令從date命令的結果中得到當前月份

#!/bin/sh



#把date命令的輸出設定為引數列表,然後通過位置引數$2獲得月份。
echo the date is $(date)

set $(date)
echo The month is $2

exit 0

可以通過set命令和它的引數來控制shell的執行方式。其中最常用的命令格式是set -x,它讓一個指令碼程式跟蹤顯示它當前執行的命令。

5.9 shift命令

shift命令把所有引數變數左移一個位置,使$2變成$1, $3變成$2,以此類推。原來$1的值將被丟棄,而$0仍將保持不變。

$*、$@和$#等其他變數也將根據引數變數的新安排做相應的變動。

在掃描處理指令碼程式的引數時,經常要用到shift命令。如果你的指令碼程式需要10個或10個以上的引數,你就需要用shift命令來訪問第十個及其後面的引數。

不能通過$10來獲取第10個元素(可以通過${10}這種方式來獲取),這樣獲取到的相當於"$1"+"0",所以需要通過shift前移後獲取

示例程式碼:

#!/bin/sh

echo "$10"
echo "${10}"
while [ "$1" != "" ];do
  echo "$1"
  shift
done

exit 0

5.10 trap命令

trap命令用於指定在接收到訊號後將要採取的行動。trap命令的一種常見用途是在指令碼程式被中斷時完成清理工作。

訊號的名字,定義在標頭檔案signal.h中,在使用訊號名時需要省略SIG字首。可以在命令提示符下輸入命令trap -l來檢視訊號編號及其關聯的名稱。

rap命令有兩個引數,第一個引數是接收到指定訊號時將要採取的行動,第二個引數是要處理的訊號名。

trap command signal

如果要重置某個訊號的處理方式到其預設值,只需將command設定為-。如果要忽略某個訊號,就把command設定為空字串’'。一個不帶引數的trap命令將列出當前設定的訊號及其行動的清單。

5.11 unset命令

unset命令的作用是從環境中刪除變數或函式。這個命令不能刪除shell本身定義的只讀變數(如IFS)。

5.12 find命令

find是個用於搜尋檔案的命令。

模版:

find [path] [options] [tests] [actions]
  • path部分可以使用絕對路徑,如/bin;也可以使用相對路徑,如.;也可以指定多個路徑。如find /var /home

  • options有很多選項,一些主要的選項

  • tests是測試的部分。每種測試返回結果有兩種可能:true或者false。find命令開始工作時,它按照順序將定義的每種測試依次應用到它搜尋到的每個檔案上。如果一個測試返回false, find命令就停止處理它當前找到的這個檔案,並繼續搜尋。如果一個測試返回true, find命令將繼續下一個測試或對當前檔案採取行動。

    常用的測試:


以用操作符來組合測試。大多數操作符有兩種格式:短格式和長格式

可以通過使用圓括號來強制測試和操作符的優先順序。由於圓括號對shell來說有其特殊的含義,所以還必須使用反斜線來引用圓括號。

如果你在檔名處使用的是匹配模式,你就必須在模式上使用引號以確保模式沒有被shell擴充套件,而是直接傳遞給find命令。

#find . -name *    這個命令執行會報錯
 find . -name "*"  #這個是正確命令
 
 #搜尋比檔案aaa新、且名字shift開頭的檔案
 #   -type f   搜尋檔案型別是檔案的檔案(不包括資料夾)
 find . -newer aaa  -name "shift*" -type f -print

-exec和-ok命令將命令列上後續的引數作為它們引數的一部分,直到被\;序列終止。實際上,-exec和-ok命令執行的是一個嵌入式命令,所以嵌入式命令必須以一個轉義的分號結束,使得find命令可以決定什麼時候它可以繼續查詢用於它自己的命令列選項。魔術字串{}是-exec或-ok命令的一個特殊型別的引數,它將被當前檔案的完整路徑取代。

find . -name "a*"  -type f -exec ls -l {} \; 

5.13 grep命令

grep是通用正規表示式解析器(General RegularExpression Parser)的簡寫。

grep命令使用一個選項、一個要匹配的模式和要搜尋的檔案。

它的語法如下:

grep [options] PATTERN [FILES]

如果沒有提供檔名,則grep命令將搜尋標準輸入

它的一些主要選項:

5.14 sort命令

sort命令能夠對文字檔案和stdin進行排序。

它的主要引數:

​ -n 採取數字排序

​ -t 指定分隔符

​ -k 指定第幾列

​ -r 反向排序

​ -u 對輸出的結果去重

-k後的整數指定了文字檔案中的某一列。列與列之間由空格分隔。如果需要將特定範圍內的一組字元(例如,第2列中的第4~5個字元)作為鍵,應該使用由點號分隔的兩個整數來定義一個字元位置,然後將該範圍內的第一個字元和最後一個字元用逗號連線起來

示例:

sort -nrk  1 data.txt     #對檔案data.txt 依據第1列,以數字逆序形式排序
sort -k 2 data.txt        #對檔案data.txt 依據第2列排序
sort -k 1.2,1.3 data.txt  #對檔案data.txt 依據第1列的第2-3個字元排序
sort -k 1.3  data.txt     #對檔案data.txt 依據第1列的第3個字元排序


uniq只能作用於排過序的資料,因此,uniq通常都與sort命令結合使用。

sort data.txt |uniq -u #顯示去重後的結果
sort data.txt |uniq -c #統計各行在檔案中出現的次數
sort data.txt |uniq -d #找出重複的航

# -s指定跳過前N個字元;
# -w指定用於比較的最大字元數

#使用-s 2跳過前兩個字元,使用-w 2選項指定後續的兩個字元作為判斷重複的依據
sort data.txt |uniq -s 2 -w 2 

5.15 xargs命令

xargs命令應該緊跟在管道操作符之後。它使用標準輸入作為主要的資料來源,將從stdin中讀取的資料作為指定命令的引數並執行該命令。

  • args命令重新格式化stdin接收到的資料,再將其作為引數提供給指定命令。xargs預設會執行echo命令

    示例:

    • 將多行輸入轉換成單行輸出。

      xargs預設的echo命令可以用來將多行輸入轉換成單行輸出:

    • 將單行輸入轉換成多行輸出。

      xargs的-n選項可以限制每次呼叫命令時用到的引數個數。下面的命令將輸入分割成多行,每行N個元素:

    xargs命令接受來自stdin的輸入,將資料解析成單個元素,然後呼叫指定命令並將這些元素作為該命令的引數。xargs預設使用空白字元分割輸入並執行/bin/echo。

    • 用-d選項將X定義為輸入分隔符。

      echo "splitXsplit2Xsplit3Xsplit4Xslipt5" | xargs -d X
      #用X對字串進行拆分。 注意,這個在MAC上提示沒有-d這個引數
      

    xargs命令可以同find命令很好地結合在一起。find的輸出可以通過管道傳給xargs,由後者執行-exec選項所無法處理的複雜操作。如果檔案系統的有些檔名中包含空格,find命令的-print0選項可以使用0(NULL)來分隔查詢到的元素,然後再用xargs對應的-0選項進行解析

    #統計當前目錄下所有檔案的行數
    find . -type f  -name "*" -print0 | xargs -0  wc -l
    

    xargs有一個選項-I,可以用於指定替換字串,這個字串會在xargs解析輸入時被引數替換掉。如果將-I與xargs結合使用,對於每一個引數,指定命令只會執行一次。

    #等價於上面的指令碼,統計當前目錄下所有檔案的行數
    find . -type f  -name "*"  | xargs  -I {} wc -l {}
    
    • 結合stdin,巧妙運用while語句和子shell

      xargs會將引數放置在指定命令的尾部,因此無法為多組命令提供引數。我們可以通過建立子shell來處理這種複雜情況。子shell利用while迴圈讀取引數並執行命令,就像這樣

      cat data.txt | ( while read arg; do echo  $arg; done)
      #等價於 cat data.txt |xargs -I {} echo {}
      
      #查詢當前目錄下.c結尾的檔案,
      find . -name '*.c' |xargs -I ^ sh -c "echo -ne '\n ^: '; grep main ^"
      

5.16 tr命令

tr可以對來自標準輸入的內容進行字元替換、字元刪除以及重複字元壓縮。tr是translate(轉換)的簡寫,因為它可以將一組字元轉換成另一組字元。

tr只能通過stdin(標準輸入)接收輸入(無法通過命令列引數接收)

它的語法如下:

tr [options]  set1 set2

來自stdin的輸入字元會按照位置從set1對映到set2(set1中的第一個字元對映到set2中的第一個字元,以此類推),然後將輸出寫入stdout(標準輸出)。set1和set2是字元類或字元組。如果兩個字元組的長度不相等,那麼set2會不斷複製其最後一個字元,直到長度與set1相同。如果set2的長度大於set1,那麼在set2中超出set1長度的那部分字元則全部被忽略。

  • 字元替換

  • 字元刪除

    tr有一個選項-d,可以通過指定需要被刪除的字符集合,將出現在stdin中的特定字元清除掉:

  • 字元組補集

    可以利用選項-c來使用set1的補集,set2是可選的:

    如果只給出了set1,那麼tr會刪除所有不在set1中的字元。如果也給出了set2,tr會將不在set1中的字元轉換成set2中的字元。如果使用了-c選項,set1和set2必須都給出。如果-c與-d選項同時出現,你只能使用set1,其他所有的字元都會被刪除。

    • 從輸入文字中刪除不在補集中的所有字元:

    • 將不在set1中的字元替換成空格:

  • 壓縮字元

    tr命令能夠完成很多文字處理任務。例如,它可以刪除字串中重複出現的字元。基本實現形式如下:

    tr -s '[需要被壓縮的一組字元]'
    
    • 去掉多餘空格

    • 其他處理

    上面是我們的資料


    我們可以通過上面的方法對它的內容求和。

    下面分析下具體的邏輯,$[ ]也可以進行算術運算。 $(tr '\n' '+')是把sum.txt的內容中的回車換行替換成+,替換完應該就是1+2+3+4+5+。所以最後補個0,變成1+2+3+4+5+0。對它進行算術運算,結果就是15

  • 字元類

    tr可以將不同的字元類作為集合使用,所支援的字元類如下所示。

5.17 cut命令

cut命令可以按列,而不是按行來切分檔案。該命令可用於處理使用固定寬度欄位的檔案、CSV檔案或是由空格分隔的檔案(例如標準日誌檔案)。

它的語法如下:

​ -f 指定定要提取的欄位,預設的分隔符是製表符;可以使用-d引數指定分隔符

​ -s 對於沒有使用分隔符的行,會將該行照原樣列印出來。-s可以禁止列印出這種行

​ -b 表示位元組提取

​ -c 表示字元提取

​ 示例:

cut -f1 -s -d" "  data.txt #用空格拆分,顯示第一個欄位。禁止顯示沒有空格拆分的行
cut -c2-5  data.txt  #列印每行的第2個到第5個字元
cut -c2-  data.txt  #列印每行的第2個到結束的字元
cut -c -2  data.txt  #列印每行的第0個到第5個的字元

6 命令的執行

要執行一條命令,並把該命令的輸出放到一個變數中。$(command)語法來實現,也可以用一種比較老的語法形式`command`

這裡是反引號(`),而不是單引號(')(單引號的作用是防止變數擴充套件)

應該使用$(...)形式,引入這一形式的目的是為了避免在使用反引號執行命令時,處理其內部的$、`、\等字元所需要應用的相當複雜的規則。如果在反引號...結構中需要用到反引號,它就必須通過\字元進行轉義。

6.1 算術擴充套件

expr命令,可以處理一些簡單的算術命令,但這個命令執行起來相當慢,因為它需要呼叫一個新的shell來處理expr命令。

一種更新更好的辦法是使用$((...))擴充套件。把你準備求值的表示式括在$((...))中能夠更有效地完成簡單的算術運算。

如下所示:

#!/bin/sh

x=0
while [ "$x" -ne 10 ]; do
  echo $x
  x=$(($x+1))   #   變數x +1 
  #let x=x+1     let 命令也可以進行算術運算 
done

exit 0

$[...]也可以進行算術運算

6.2 引數擴充套件

比如我們要訪問兩個檔案,1_tmp,2_tmp。我們是不能通過下面的方式來實現的。

#!/bin/sh

#錯誤案例

for i in 1 2 ; do
  echo $i_tmp   #  不能通過。$i_tmp 的方式訪問到 1_tmp,2_tmp
done

exit 0

正確的做法是我們需要將i放到花括號內,如下所示:

#!/bin/sh

for i in 1 2 ; do
  echo ${i}_tmp   #  通過${i}_tmp 的方式訪問 1_tmp,2_tmp
done

exit 0

每次迴圈中,變數i的值替換了${i}


shell中有多種引數替換方法,一些常見的引數擴充套件方法如下圖:

示例指令碼:

#!/bin/sh

unset foo
echo ${foo:-bar}
echo "foo =  ${foo}" #這裡可以看到 ${foo} 依舊是未設定狀態

foo=fud
echo ${foo:-bar}

foo=/usr/bin/x11/startx
echo ${foo#*}
echo ${foo##*/}

bar=/usr/local/etc/local/networks
echo ${bar%local*}
echo ${bar%%local*}

exit 0

第一條語句${foo:-bar}給出的值是bar,這是因為在這條語句執行時foo沒有值。這條語句執行後,變數foo未發生變化,它還停留在未設定狀態。

如果這條語句是${foo:=bar},那麼變數$foo就會被賦值。這個字串操作符的作用是,檢查變數foo是否存在且不為空。如果它不為空,就返回它的值,否則就把變數foo賦值為bar並返回這個值。

${foo:? bar}語句將在變數foo不存在或它設定為空的情況下,輸出foo:bar並異常終止指令碼程式。最後,${foo:+bar}語句將在變數foo存在且不為空的情況下返回bar。

{foo#*/}語句僅僅匹配並刪除最左邊的/(記住,*匹配零個或多個字元)。{foo##*/}語句匹配並刪除儘可能多的字元,所以它刪除最右邊的/及其前面的所有字元。

{bar%local*}語句匹配從右邊起直到第一次出現local(及跟在它後面的所有字元),而{bar%%local*}語句則從右邊起儘可能多地匹配字元,直到遇到最靠左邊的local。

7 除錯指令碼程式

要除錯指令碼,可以在執行shell時加上命令列選項,或是使用set命令。

下面列出來各種選項:

例如可以用-o選項啟用set命令的選項標誌,用+o選項取消設定,對簡寫版本也是一樣的處理方法。

相關文章