這是 Bash One-Liners Explained 系列的第二篇文章。在這一篇裡,我會給你們介紹如何用 Bash 來完成各種各樣的字串操作。我會選擇用最好的 Bash 做法,各種常見的語法和技巧,向各位闡明如何用 Bash 內建的命令和 Bash 程式語言來完成各式各樣的任務。
1. 生成從 a 到 z 的字母表
1 |
$ echo {a..z} |
這一行命令用到了括號展開(Brace expansion)功能,它可以用於生成任意的字串。{x..y}是一個序列表示式,其中 x 和 y 都是單個字元,這個表示式展開後包含 x 與 y 之間的所有字元。
執行上面的命令會生成從 a 到 z 的所有字母:
1 2 |
$ echo {a..z} a b c d e f g h i j k l m n o p q r s t u v w x y z |
2. 生成從 a 到 z 的字母表,字母之間不包含空格
1 |
$ printf "%c" {a..z} |
這是一個 99.99% 的人都不知道的非常棒的技巧。如果你在printf命令之後指定一個列表,最終它會迴圈依次列印每個元素,直到完成為止。
在這一行命令中,printf 的格式為"%c"
,代表一個字元(character),後面的引數是從 a 到 z 的字元列表,字元之間以空格分隔。所以,當printf執行時,它依次輸出每個字元直到所有字元全被處理完成為止。
下面是執行的結果:
1 |
abcdefghijklmnopqrstuvwxyz |
輸出的結果最後不包含換行符,因為printf
的輸出格式是"%c"
,其中並沒有包含\n
。如果你想輸出完整的一行,可以簡單地在字元列表後面增加一個$’\n’:
1 |
$ printf "%c" {a..z} $'\n' |
1 |
$'\n'代表換行符,這是一個常用的技巧。 |
另外一種方式是,通過 echo 來輸出 printf
的結果:
1 |
$ echo $(printf "%c" {a..z}) |
這一行命令用到了命令替換功能:執行printf "%c" {a..z}
命令然後用執行的輸出替換命令。然後,echo
列印輸出結果時會帶上換行符。
如果你想要每一行僅輸出一個字母,在字元後面增加一個換行符:
1 |
$ printf "%c\n" {a..z} |
輸出:
1 2 3 4 |
a b ... z |
如果想要快速地將 printf
的結果儲存到變數中,可以使用-v
選項:
1 |
$ printf -v alphabet "%c" {a..z} |
結果會將abcdefghijklmnopqrstuvwxyz
儲存到變數alphabet
中。
類似地,你也可以利用同樣的語法生成一個數字列表,例如從1到100:
1 |
$ echo {1..100} |
輸出:
1 |
1 2 3 ... 100 |
或者,如果你忘記這種方法,可以使用 seq 命令來做這個事情:
1 |
$ seq 1 100 |
3. 輸出從 00 到 09 的數字
1 |
$ printf "%02d " {0..9} |
這裡我們又用到了printf
的迴圈輸出功能,這一次的輸出格式為"%02d "
,意思是在輸出數字的時候,如果不滿兩位就用0補齊。同時,輸出的元素是 0 到 9的列表(括號展開後的結果)。
輸出結果:
1 |
00 01 02 03 04 05 06 07 08 09 |
如果你使用的是最新的 Bash 4 版本,你可以使用加強的括號展開功能:
1 |
$ echo {00..09} |
老版本不包含該特性。
4. 生成 30 個英文單詞
1 |
$ echo {w,t,}h{e{n{,ce{,forth}},re{,in,fore,with{,al}}},ither,at} |
這是一個濫用括號展開的例子,看看最終輸出的結果是什麼:
1 |
when whence whenceforth where wherein wherefore wherewith wherewithal whither what then thence thenceforth there therein therefore therewith therewithal thither that hen hence henceforth here herein herefore herewith herewithal hither hat |
是不是很棒?
你可以通過括號展開生成一組單詞或者符號的排列。例如:
1 |
$ echo {a,b,c}{1,2,3} |
上面的命令會生成以下結果:a1 a2 a3 b1 b2 b3 c1 c2 c3
。首先,它取出第一個括號中的第一個元素a
,然後依次與第二個括號{1,2,3}
的所有元素組合,生成a1 a2 a3
,依此類推。
5. 重複輸出 10 次字串
1 |
$ echo foo{,,,,,,,,,,} |
這一行命令兩次利用了括號展開功能,字串foo
與10個空字串組合,最終生成10分拷貝:
1 |
foo foo foo foo foo foo foo foo foo foo foo |
6. 拼接字串
1 |
$ echo "$x$y" |
這一行命令簡單地將兩個變數的值連線在一起,所以如果x
變數的值為foo
,而y
的值為bar
,則結果為foobar
。
注意,這裡的"$x$y"
是加了雙引號的,如果忘記加了,echo
會將$x$y
當成常規的命令列引數去解析。所以,如果$x
在開頭包含-
,它就變成一個命令列引數,而不是被 echo 輸出的內容。
1 2 3 |
x=-n y=" foo" echo $x$y |
執行後的輸出:
1 |
foo |
相反,正確書寫的方式執行後的結果如下所示:
1 2 3 4 |
x=-n $ y=" foo" $echo "$x$y" -n foo |
不過,如果你要將兩個字串相連的結果賦值給變數,是可以將雙引號省略的:
1 |
var=$x$y |
7. 分割字串
假設變數$str
的值為foo-bar-baz
,如果你想按-
分割成多個段並遍歷它,可以使用read
命令,並且設定 IFS 的值為-
:
1 |
$ IFS=- read -r x y z <<< "$str" |
這裡我們使用read x
命令從標準輸入讀取內容,分割後並依次儲存到x y z
變數中。其中,$x
為foo
, $y
為 bar
, $z
為 baz
。
另外要留意的一處是here-string
操作符<<<
,可以很方便地將字串傳遞給命令的標準輸入。在這個例子中,$str
的內容傳給 read
命令的標準輸入。
你也可以將分割後的幾個欄位儲存到陣列型別的變數中:
1 |
$ IFS=- read -ra parts <<< "foo-bar-baz" |
在這裡,-a
選項告訴read
命令將分割後的元素儲存到陣列parts
中。隨後,你可以通過${parts[0]}
, ${parts[1]}
和${parts[0]}
來訪問陣列的各個元素,或者通過${parts[@]}
來訪問所有元素。
8. 逐個字元方式處理字串
1 2 3 |
$ while IFS= read -rn1 c; do # do something with $c done <<< "$str" |
這裡我們通過指定-n1
引數,讓read
命令依次讀入一個字元,類似地,-n2
說明每次讀入兩個字元。
9. 將字串中的 foo 替換成 bar
1 |
$ echo ${str/foo/bar} |
這一行命令用到了引數展開的另外一種形式:${var/find/replace}
,找到$var
變數中的find
字串,並將它替換成bar
。
要想替換所有出現的字串"foo"
,請使用${var//find/replace}
的形式:
1 |
$ echo ${str//foo/bar} |
10. 檢查字串是否匹配模式
1 2 3 |
$ if [[ $file = *.zip ]]; then # do something fi |
這一行命令是說,如果$file
的值匹配*.zip
,則執行if
語句裡的命令。這種語法下的模式是最簡單的萬用字元(glob pattern)匹配,萬用字元包括* ? [...]
。其中,*
可以匹配一個或者多個字元,?
只能匹配單個字元,[...]
能夠匹配任意出現在中括號裡面的字元或者一類字符集。
下面是另外一個例子,用來判斷回答是否匹配 Y 或者 y:er is Y or y:
1 2 3 |
$ if [[ $answer = [Yy]* ]]; then # do something fi |
11. 檢查字串是否匹配某個正規表示式
1 2 3 |
$ if [[ $str =~ [0-9]+\.[0-9]+ ]]; then # do something fi |
這一行命令檢查$str
是否能夠匹配正規表示式[0-9]+\.[0-9]+
,即兩個數字中間包含一個點號。正規表示式的規範可以通過 man 手冊查詢: man 3 regex
12.計算字串的長度
1 |
$ echo ${#str} |
這裡我們又用到了引數展開(也可以叫引數替換)的語法: ${#str}
,它返回$str
變數值的長度。
13. 從字串中提取子串
1 2 |
$ str="hello world" $ echo ${str:6} |
這一行命令通過子串提取操作,從字串hello world
中取到了子串world
。子串提取操作的語法格式為${var:offset:length}
,它的意思是說從變數var
中,提取第offset
個位置(下標從0開始計算)開始的總共length
個數的字元。在我們這個例子中,忽略了length
,預設會返回所有剩餘的字元。
下面是另外一個例子,返回$str
變數中第7、8位置的兩個字元:
1 |
$ echo ${str:7:2} |
輸出結果為or
。
14. 轉換成太寫
1 2 |
$ declare -u var $ var="foo bar" |
Bash 中的內建命令 declare
可以用於宣告一個變數,或者設定變數的屬性。在這個例子中,通過指定-u
選項,使得變數$var
在賦值時,就會自動地將內容轉換成大寫的格式。現在你 echo 它,可以看到所有內容已經變成大寫了:
1 2 |
$ echo $var FOO BAR |
注意,-u
選項也是在 Bash 4 新版本中引入的功能,在低版本下是沒有的。類似地,你還可以使用 Bash 4 提供的另外一種引數展開語法${str^^}
,也可以將字串轉換成太寫的格式:
1 2 |
$ str="zoo raw" $ echo ${str^^} |
15. 轉換成小寫
1 2 |
$ declare -l var $ var="FOO BAR" |
同上面一條類似,-l
選項宣告變數的小寫屬性,使得其值轉換成小寫的格式:
1 2 |
$ echo $var foo bar |
同樣,只有 Bash 4 以及以上的版本才支援-l
選項。另外一種方式是使用引數展開語法:
1 2 |
$ str="ZOO RAW" $ echo ${str,,} |
我補充一句,如果是 Bash 4 以下,還是老老實實地用tr
命令就可以了。
本文完