寫在前面
如果你問我bash的這麼多擴展哪個功能最強大,那我會毫不猶豫地告訴你,當然是參數擴展啦~
爲什麼說參數擴展功能強大呢?那是因爲通過參數擴展功能,我們可以完成很多意想不到的功能,例如可以完成參數值的刪除、截取以及替換等功能~
SHELL參數以及參數的分類
開始講述參數擴展之前,我們先要了解什麼是shell的參數,以及引用參數的不同方法。
其實在shell編程中,參數(parameter)是個大概念,也是個籠統的概念,在bash手冊中對參數的定義只是一句話:
A parameter is an entity that stores values.
意思是說在shell中參數是個實體(entity),這個實體中存儲着各式各樣的值(values)。
緊接着提到:
It can be a name, a number, or one of the special characters listed below under Special Parameters.
這句話其實是告訴我們,可以通過三類方式來引用參數,從而得到參數中存儲的值。根據引用方式的不同,可以將參數分爲三類,歸納如下:
(1)通過名稱(name)來引用參數,這樣的參數我們稱之爲變量(variables )。一個變量擁有自己的值和諸多屬性,屬性可以通過declare來設定,可以通過unset來取消一個變量。
(2)通過數字(number)來引用參數,這樣的參數我們稱之爲位置參數(Positional Parameters)。位置參數在腳本被調用時自動初始化爲傳遞給腳本的參數。腳本中調用函數時,位置參數會暫時替換成傳遞給函數的參數。我們可以使用set命令來改變位置參數的值,但是不能試圖通過賦值語句來改變位置參數的值。(參考《Handling positional parameters》)
(3)最後還有一類參數,被稱之爲特殊參數(Special Parameters)。特殊在哪裏? 特殊在我們只能通過shell內部預定義的特殊符號來引用它們,並且我們只能引用,不能試圖通過賦值語句來重新賦值。預定義的特殊符號包括:* @ $ ? ! - $ 0 (參考《Special parameters and shell variables》)
(參考鏈接:《Parameter》《Parameters》)
什麼是參數擴展呢?
講白了,所謂參數擴展就是通過符號$獲得參數中存儲的值。只不過呢,在獲得最終的結果之前,允許我們對參數以及參數值做很多操作,例如本文一開始就提到的對參數值進行刪除、截取以及替換等操作~
本篇博文就是詳細討論參數擴展過程中我們可以進行的諸多操作。
最簡單的形式
參數擴展最簡單直接的形式如下:
$parameter
或者
${parameter}
個人傾向於後者,第一有花括號一看就知道是參數擴展,其次可以根據需要在右花括號後頭追加字符(串),否則shell會認爲是參數的一部分。舉個小例子就知道了,如下:
[09:49:23@astrol:~]$ WORD=car
[09:49:25@astrol:~]$ echo "The plural of $WORD is most likely $WORDs"
The plural of car is most likely
[09:49:27@astrol:~]$ echo "The plural of $WORD is most likely ${WORD}s"
The plural of car is most likely cars
可以看到不加花括號的話,shell認爲WORDs是參數,然而我們並沒有設置過這個參數,因此擴展結果爲空。
需要注意的是,在bash中引用位置參數時,大於第9個參數時,兩位的數字要求必須要在花括號內。例如:${10}
另外,後文介紹的各種操作都是需要在花括號內進行的。
使用默認值(Use Default Values)
test
賦值默認值(Assign Default Values)
test
間接擴展(indirect expansion)
間接擴展也被很多人稱爲間接引用。如果熟悉C/C++的話,可以簡單的把間接擴展理解成指針變量。
子串擴展(Substring Expansion)
${parameter:offset}
${parameter:offset:length}
子串擴展的意思是從offset位置開始截取長度爲length的子串,如果沒有提供length,則是從offset開始到結尾。需要注意的幾點是:
(1)如果offset是個負值,開始位置是從字符串末尾開始算起,然後取長度爲length的子串。例如,-1代表是從最後一個字符開始。
(2)如果length是個負值,那麼length的含義不再代表字符串長度,而是代表另一個offset,位置從字符串末尾開始,擴展的結果是offset ~ length之間的子串。
(3)如果parameter是@,也就是所有的位置參數時,offset必須從1開始。
來看幾個例子:
[17:58:36@astrol:~]$ MYSTRING="Be liberal in what you accept, and conservative in what you send"
[17:59:19@astrol:~]$ echo ${MYSTRING}
Be liberal in what you accept, and conservative in what you send
[17:59:30@astrol:~]$ echo ${MYSTRING:34}
conservative in what you send
[17:59:42@astrol:~]$ echo ${MYSTRING:34:13}
conservative
[17:59:56@astrol:~]$ echo ${MYSTRING: -10:5}
t you
[18:00:18@astrol:~]$ echo ${MYSTRING:(-10):5}
t you
[18:00:23@astrol:~]$ echo ${MYSTRING:11:-17}
in what you accept, and conservative
可以看到當offset是負值時,負號(-)必須與冒號(:)有間隔,這是爲了避免與上文提到的${parameter:-word} 混淆。
查找和替換(Pattern substitution)
${parameter/pattern/string}
${parameter//pattern/string}
${parameter/pattern}
${parameter//pattern}
pattern和路徑擴展(pathname expansion)中的模式匹配(pattern matching)一樣(詳情可參考文章《bash之通配符》),匹配後的子串會用string替換掉。需要注意的有以下幾點:
(1)parameter之後如果是/,則只替換匹配到的第一個子串;parameter之後如果是//,則替換所有匹配到的子串。
(2)當string爲空時,則相當於將匹配的子串刪除。
(3)特殊符號#和%在這種情況下分別錨定(Anchoring )字符串的開始和結尾。
(4)如果bash的nocasematch選項參數是打開的(shopt -s nocasematch),則匹配的過程大小寫是不敏感的。
例子如下:
[19:26:39@astrol:~]$ MYSTRING="Be liberal in what you accept, and conservative in what you send"
[19:26:40@astrol:~]$ echo ${MYSTRING}
Be liberal in what you accept, and conservative in what you send
[19:26:46@astrol:~]$ echo ${MYSTRING//conservative/happy}
Be liberal in what you accept, and happy in what you send
[19:27:04@astrol:~]$ echo ${MYSTRING/in/by}
Be liberal by what you accept, and conservative in what you send
[19:27:21@astrol:~]$ echo ${MYSTRING//in/by}
Be liberal by what you accept, and conservative by what you send
[19:27:24@astrol:~]$ echo ${MYSTRING/conservative/}
Be liberal in what you accept, and in what you send
[19:27:50@astrol:~]$ MYSTRING=xxxxxxxxxxxxxxx
[19:28:09@astrol:~]$ echo ${MYSTRING}
xxxxxxxxxxxxxxx
[19:28:17@astrol:~]$ echo ${MYSTRING/#x/y}
yxxxxxxxxxxxxxx
[19:28:26@astrol:~]$ echo ${MYSTRING/%x/y}
xxxxxxxxxxxxxxy
bash的這個查找替換功能跟sed的很像,不同的是這裏的pattern不是正則表達式。
查找並刪除(Remove matching prefix/suffix pattern)
${parameter#pattern}
${parameter##pattern}
${parameter%pattern}
${parameter%%pattern}
刪除匹配到的子串。直接來看例子吧,假設我們定義了一個變量爲:
file=/dir1/dir2/dir3/my.file.txt
那麼:
${file#*/}:刪除第一個 / 及其左邊的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:刪除最後一個 / 及其左邊的字符串:my.file.txt , 相當於basename ${file}
${file#*.}:刪除第一個 . 及其左邊的字符串:file.txt
${file##*.}:刪除最後一個 . 及其左邊的字符串:txt
${file%/*}:刪除最後一個 / 及其右邊的字符串:/dir1/dir2/dir3,相當於dirname ${file}
${file%%/*}:刪除第一個 / 及其右邊的字符串:(空值)
${file%.*}:刪除最後一個 . 及其右邊的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:刪除第一個 . 及其右邊的字符串:/dir1/dir2/dir3/my
記憶的方法爲:
# 是去掉左邊(在鍵盤上 # 在 $ 之左邊)
% 是去掉右邊(在鍵盤上 % 在 $ 之右邊)
單一符號是最小匹配﹔兩個符號是最大匹配。
獲取參數值長度(Parameter length)
${#parameter}
這個擴展很簡單,就是返回parameter值的長度值。
[20:49:27@astrol:~]$ MYSTRING="Be liberal in what you accept, and conservative in what you send"
[20:49:27@astrol:~]$ echo ${MYSTRING}
Be liberal in what you accept, and conservative in what you send
[20:49:34@astrol:~]$ echo ${#MYSTRING}
64
大小寫轉換(Case modification)
${parameter^}
${parameter^^}
${parameter,}
${parameter,,}
字符^意思是將第一個字符轉換成大寫字母,^^的意思是將所有的字符轉換成大寫字母。
字符,意思是將第一個字符轉換成小寫字母,,,的意思是將所有的字符轉換成小寫字母。
來看如下例子:
[20:00:49@astrol:~]$ lower="lowercase letters"
[20:00:55@astrol:~]$ echo ${lower}
lowercase letters
[20:01:08@astrol:~]$ echo ${lower^}
Lowercase letters
[20:01:12@astrol:~]$ echo ${lower^^}
LOWERCASE LETTERS
[20:01:15@astrol:~]$ echo ${lower} | tr '[:lower:]' '[:upper:]'
LOWERCASE LETTERS
[20:01:43@astrol:~]$
[20:01:49@astrol:~]$ UPPER="UPPER LETTERS"
[20:02:01@astrol:~]$ echo ${UPPER}
UPPER LETTERS
[20:02:07@astrol:~]$ echo ${UPPER,}
uPPER LETTERS
[20:02:09@astrol:~]$ echo ${UPPER,,}
upper letters
[20:02:11@astrol:~]$ echo ${UPPER} | tr '[:upper:]' '[:lower:]'
upper letters
關於大小寫轉換的更多例子可以參考《Shell Script: Convert Lowercase to Uppercase》
參考鏈接:
《BASH: Parameter expansion》(需梯子)
《Linux 技巧: Bash 參數和參數擴展》《Bash parameters and parameter expansions》
《Linux Shell參數擴展(Parameter Expansion)》
《Bash parameters and parameter expansions》