1. 程式人生 > >Bash程式設計(3) 命令列解析與擴充套件

Bash程式設計(3) 命令列解析與擴充套件

[email protected]表示指令碼輸入的全部引數,在bash指令碼中,若[email protected]增加引號("[email protected]"),則包含空格的引數也會被保留,若不增加引號([email protected]),則包含空格的引數會被拆分。

例:

# sa指令碼內容如下:
pre=:
post=:
printf "$pre%s$post\n" "[email protected]"
#printf "$pre%s$post\n" [email protected]

# 注意[email protected]
增加引號和不加引號的區別 $ bash test.
sh "a b" :a b: #:a: #:b: 

1. 引號

對於單引號、雙引號、轉義字元開頭的空格,命令列解析時將不會被拆分。

$ sa \ this "is a" 'demonstration of' \  quotes\ and\ espaces ## quotes\ and\ espaces中會轉義空格
: this:
:is a:
:demonstration of:
: :
:quotes and espaces:

$ ./sa "a double-quoted single quote, '" "a double-quoted double quote, \"
" # 單引號中的轉義字元將失效,而雙引號中的轉義將有效 :a double-quoted single quote, ': :a double-quoted double quote, ": $ ./sa 'a single-quoted double quotation mark, "' :a single-quoted double quotation mark, ": $ ./sa "First argument "'still the first argument' :First argument still the first argument: $ echo '\'line1\'\n\'line2\'
' # 單引號中直接巢狀單引號,會有問題,即使增加轉義符也無法正常執行 $ echo $'\'line1\'\n\'line2\'' # 增加$符號即可解決單引號中巢狀單引號的問題  

 2. 花括號

花括號作用於不帶引號、以逗號分隔的列表或序列。當作為bash指令碼的輸入引數時,每個元素作為獨立的引數。

$ ./sa {one,two,three}
:one:
:two:
:three:

$ ./sa {1..3} #bash3.0後增加
:1:
:2:
:3:

$ ./sa {a..c}
:a:
:b:
:c:

$ ./sa pre{d,1}date # 花括號前後的字串將包含在括號中的每個引數中
:preddate:
:pre1date:

$ ./sa {{1..3},{a..c}} # 花括號可以巢狀
:1:
:2:
:3:
:a:
:b:
:c:

$ ./sa {1..3}{a..c} # 連續多個的花括號將會逐個遞迴擴充套件
:1a:
:1b:
:1c:
:2a:
:2b:
:2c:
:3a:
:3b:
:3c:

$ ./sa {01..13..3} # 4.0版本的bash具有更高特性:數值序列字首加0、可以指定序列中的步長
:01:
:04:
:07:
:10:
:13:

$ ./sa {a..h..3} # 也可應用於字母
:a:
:d:
:g: 

3. 波浪號

$ ./sa ~ # 不帶引號的~表示當前使用者的家目錄
:/home/music:

$ ./sa ~root # ~後帶使用者名稱,將表示該使用者的家目錄
:/root:

$ ./sa "~" "~root" # 引號中的~將不會擴充套件
:~:
:~root:
$ dir=~root
$ dir2="~root"
$ ./sa "$dir" "$dir2"
:/root:
:~root:

$ ./sa ~ws # ~後的使用者名稱若不存在,則不擴充套件
:~ws:

 

4. 引數和變數擴充套件

$ var=whatever 
$ ./sa "$var"
:whatever:

$ var=qwerty
$ ./sa "${var}"
:qwerty:

# 通常情況下,{}可選,當位置引數大於9或變數明後緊跟字元時,需要增加{}
$ first=Jane
$ last=Johnson
$ ./sa "$first_$last"  #first_是有效變數名
:Johnson:
$ ./sa "${first}_$last"
:Jane_Johnson: 

5. 算術擴充套件

$ ./sa "$(( 1 + 12 ))" "$(( 12 * 13 ))" "$(( 16 / 4 ))" "$(( 6 - 9 ))" "$(( 10 % 3 ))"
:13:
:156:
:4:
:-3:
:1:

$ ./sa "$(( 3 + 4 * 5 ))" "$(( (3 + 4) * 5 ))"
:23:
:35:

# 將秒轉為天、小時、分鐘
secs_in_day=86400
secs_in_hour=3600
mins_in_hour=60
secs_in_min=60

days=$(( $1 / $secs_in_day ))
secs=$(( $1 % $secs_in_day ))

printf "%d:%02d:%02d:%02d\n" "$days" "$(( $secs / $secs_in_hour ))" \
       "$(( ($secs / $mins_in_hour) % $mins_in_hour ))" "$(( $secs % $secs_in_min ))" 

6. 命令替換

$ wc -l $( date +%Y-%m-%d ).log
1 2019-01-02.log

$ wc -l `date +%Y-%m-%d`.log # ``與$()作用相同
1 2019-01-02.log 

7. 分詞

# 單詞拆分基於內部欄位分隔符(IFS)的值,預設IFS的為s' \t\n'
$ var="this is a multi-word value" 
$ ./sa $var "$var"
:this:
:is:
:a:
:multi-word:
:value:
:this is a multi-word value:

# 當IFS具有其預設值或未設定時,任何預設IFS字元序列都將作為單個分隔符讀取
$ var=' spaced
> out '
$ ./sa $var
:spaced:
:out:

# 當IFS包含另一個字元和空格時如" :",該" :"可以拆分欄位,但每個分空白字元也可以拆分欄位,即":"單獨也會進行拆分
$ IFS=' :'
$ var="query : uiop : :: er" 
$ ./sa $var
:query:
:uiop:
::
::
:er:

# 當IFS僅包含非空字元時,則IFS中的每個字元拆分欄位,且空格將保留
$ IFS=:
$ var="qwery : uiop : :: er"
$ ./sa $var
:qwery :
: uiop :
: :
::
: er: 

 8. 路徑擴充套件 

# 命令列中若包含*, ?, [,則將被當做檔案匹配模式
$ ./sa h*  # 匹配當前路徑下以h開頭的檔案
:hw:
$ ./sa *e  # 匹配當前路徑下以k結尾的檔案
:datafile:
:errorfile:

$ ./sa ?a* # ?表示僅匹配一個字元
:datafile:
:sa:

# [aceg]表示匹配a,c,e,g中的任意一個
# [h-o]表示匹配h到o中的任意字元
# [[:lower:]]表示匹配小寫字母 

9. 程序替換

 程序替換將為命令建立一個臨時檔案。<(commond)使命令的輸出像檔名一樣可用;>(commond)表示可以寫入的檔名。

# totalsize在迴圈外不可用
$ ls -l |
> while read perms links owner group size month day time file
> do
>     printf "%10d %s\n" "$size" "$file"
>     totalsize=$(( ${totalsize:=0} + ${size:-0} ))
> done
$ echo ${totalsize-unset} ## print "unset" if variable is not set

# 命令替換可以使totalsize在迴圈外可用
$ while read perms links owner group size month day time file
> do
> printf "%10d %s\n" "$size" "$file"
> totalsize=$(( ${totalsize:=0} + ${size:-0} ))
> done < <(ls -l *)
$ echo ${totalsize-unset}
12879 

10. 解析選項

shell指令碼中以-連線的選項,可以使用內建的getops解析。格式為:getopts OPTSTRING var

例:parseopts指令碼

progname=${0##*/} ## 獲取指令碼名稱

# 預設值
verbose=0
filename=

# 列出程式接收的選項列表,這些選項帶引數,以:連線
optstring=f:v

# while迴圈呼叫getopts,直到命令無更多的選項
# 每個選項儲存在$opt中,對應的選項引數儲存在OPTARG
while getopts $optstring opt
do
    case $opt in
      f) filename=$OPTARG ;; # $OPTARG包含選項對應的引數
      v) verbose=$(( $verbose + 1 )) ;;
      *) exit 1;;
    esac
done

# 命令列中移除選項
## $OPTIND指向下一個,且不解析引數
shift "$(( $OPTIND - 1 ))"

# 檢查是否有檔案傳入
if [ -n "$filename" ]
then
    if [ $verbose -gt 0 ]
    then
        printf "Filename is %s\n" "$filename"
    fi
else
    if [ $verbose -gt 0 ]
    then
        printf "No filename entered\n" >&2
    fi
    exit 1
fi


# 檢查檔案是否存在
if [ -f "$filename" ]
then
    if [ $verbose -gt 0 ]
    then
        printf "Filename %s found\n" "$filename"
    fi
else 
    if [ $verbose -gt 0 ]
    then
       printf "File, %s, does not exist\n" "$filename" >&2
    fi
    exit 2
fi

# 如果選擇了verbose選項,列印保留在命令中的數值引數
if [ $verbose -gt 0 ]
then
    printf "Number of arguments is %d\n" "$#"
fi

 執行命令結果如下:

$ ./parseopts 
$ echo $?
1
$ ./parseopts -v
No filename entered
$ echo $?
1
$ ./parseopts -x
./parseopts: illegal option -- x
$ ./parseopts -vf qwerty
Filename is qwerty
File, qwerty, does not exist
$ ./parseopts -vf qwerty -- -x
Filename is qwerty
File, qwerty, does not exist
$ ./parseopts -vf ~/.bashrc
.bashrc                .bashrc-anaconda3.bak  
$ ./parseopts -vf ~/.bashrc -- -x
Filename is /home/music/.bashrc
Filename /home/music/.bashrc found
Number of arguments is 1