shell基礎知識查缺補漏

最近在看《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後,不輸入任何內容,直接回車鍵,就會報錯

這個問題主要是當我們直接輸入回車鍵後,變數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選項取消設置,對簡寫版本也是一樣的處理方法。

Tags: