這是翻譯系列文章的第2篇,參考原文連結 。
翻譯的系列文章列表:
- Bash 單命令列解釋,第一部分:檔案操作
- Bash 單命令列解釋,第二部分:字串操作(本篇)
這是 Bash One-Liners Explained 系列文章的第2篇。本文中我將利用 bash 最佳實踐,多變的 bash 方言和 bash 命令技巧,展示僅利用 bash 內建命令和外部程式命令構建命令流完成各種各樣的任務需求。
參考系列文章的 第一篇 Bash 單命令列解釋,第一部分:檔案操作 。若這個 bash 系列文章完成後,我將放出其同名的電子書。我的電子書 有 pdf 格式及方便在手機上看的格式(mobi 和 epub)。同時,像 perl1line.txt 一樣,也有純文字 txt 格式的文件。
也可參考我其它的高效使用 bash 的系列文章:
- Working Productively in Bash's Emacs Command Line Editing Mode (comes with a cheat sheet)
- Working Productively in Bash's Vi Command Line Editing Mode (comes with a cheat sheet)
- The Definitive Guide to Bash Command Line History (comes with a cheat sheet)
開始學習
第二部分:字串操作
1. 建立輸出字母表
$ echo {a..z}
以上命令使用 bash 的 大括號展開(brace expansion) 機制,其含義是生成「字面字串」序列(列舉每個條目)。此例中表示式是 {x..z} ,其中 x 和 z 是單個字元,這個表示字母表中從 x 到 z 的所有字元(包含 x 和 z)(注意:x 和 z 之間是2個 . 符號)。
若執行以上命令,其輸出 a-z 的所有字母:
$ 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. 無空格分隔輸出字母表中字母
$ printf "%c" {a..z}
這個神奇的 bash 技巧 99.99% 的使用者不知道。如果把一個列表傳遞個 printf
命令, printf 命令會迴圈依次處理列表的每個條目,迴圈結束,命令退出。這是一個極佳的技巧。
以上命令使用 "%c" 作為 printf 命令的格式輸出控制符。(與 C 語言中函式類似)它將列表序列中的字母依次輸出。
下面是命令執行輸出結果:
abcdefghijklmnopqrstuvwxyz
這個輸出後沒有回車,因為列表序列中不包含回車符( '\n' )。加入行終止,只需在字元序列尾加入回車符,方法是在列表序列表示式尾加上 $'\n'
,命令如下:
$ printf "%c" {a..z} $'\n'
$'\n'
是 bash 方便表示,代表回車符。命令輸出 {a..z} 後,接著輸出回車符。(注意:printf 類似 C 語言中的 printf 函式,接受可變引數)。
另一方法使用 echo
命令輸出 printf "%c" {a..z}
命令結果。這裡使用了命令執行替換機制(第一篇文講解過)。命令如下:
$ echo $(printf "%c" {a..z})
如上命令,使用命令執行替換(command substitution)輸出字母表傳遞給 echo
命令輸出, echo
命令預設在命令後輸出回車。
想以列方式輸出字母?在 printf
命令的格式輸出控制中加入回車符!
$ printf "%c\n" {a..z}
a
b
...
z
想將 printf
命令展開的列表賦值給變數?使用 -v
命令選項:
$ printf -v alphabet "%c" {a..z}
這將 "abcdefghijklmnopqrstuvwxyz" 字串賦值給 $alphabet
變數。
類似地,可建立整數列表序列,我們試驗一下從 1 到 100 :
$ echo {1..100}
輸出:
1 2 3 ... 100
也可使用 seq
命令工具建立數字序列,替換上述的方法:
$ seq 1 100
3. 給 0 到 9 數字序列中每個數加前導 0
$ printf "%02d " {0..9}
這裡我們再次使用 printf
命令的迴圈能力。 這次 printf 的格式控制字串是 ""%02d" ,它的意思是輸出的數字以0前導補足數字到2位。後面的命令引數通過大括號展開傳遞進來從 0 到 9 的數字。
輸出:
00 01 02 03 04 05 06 07 08 09
如果使用的 bash 版本大於 4,也可使用下面的大括號展開表示式:
$ echo {00..09}
老的 bash 版本沒有這個特性。
4. 生成 30 個英語單詞
$ echo {w,t,}h{e{n{,ce{,forth}},re{,in,fore,with{,al}}},ither,at}
上面的命令是對大括號展開(brace expansion) 機制濫用(也能很好理解它)。來看看它的輸出:
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
太神奇了!
它怎樣做到的! 在 大括號展開 機制中,在大括號中的每個被逗號 , 分隔的條目都可以分別交叉放置。例如,如果像下面的命令:
$ echo {a,b,c}{1,2,3}
上面的大括號表示式將會產生 a1 a2 a3 b1 b2 b3 c1 c2 c3 ,其首先使用第1個大括號中的 a 依次和第2個大括號中的 1,2,3 組合,構成 a1 a2 a3 ,接著使用第1個大括號中的 b 依次和第2個大括號中的 1,2,3 組合,構成 b1 b2 b3 ,接著是 c 。(矩陣乘的概念)
所以,上面生成單詞的命令就會構成上面那些單詞的輸出!
5. 生成1個字串的10個副本
$ echo foo{,,,,,,,,,,}
上述命令再次使用大括號展開機制。前面的字串 foo 和後面的10個條目都為空的列表的每個條目組合,形成字串 foo 的10個副本:
foo foo foo foo foo foo foo foo foo foo foo
6. 合併連線2個字串
$ echo "$x$y"
這條命令簡單合併連線2個變數,如果 x
變數包含字串 foo ,且 y
變數包含字串 bar ,結果為 foobar 。
注意命令中 "$x$y" 是被雙引號括住的。若不括住它,echo
命令會優先將 $x$y 變數中的值解釋為命令列引數或命令選項。因此,若 $x 變數的值與命令列引數或命令選項相同(以 - 開始的字元等),將被按命令引數或命令選項解釋,使結果不能按我們的意願輸出:
x=-n
y=" bar"
echo $x$y
輸出:
foo
相反,合併連線2個字串方法:
x=-n
y=" foo"
echo "$x$y"
輸出:
\-n foo
若想合併連線2個字串賦值到另一個變數,就不必要使用雙引號括住:
var=$x$y
7. 以指定字元分割字串
若有個字串變數 $str
,其值為 foo-bar-baz ,你想把它按橫槓符 - 分割成3個字串。可以使用 IFS
和 read
命令的組合:
$ str=foo-bar-baz
$ IFS=- read -r x y z <<< "$str"
這裡,使用 read x y z
命令讀取進入的資料並將它分割成3個變數,使用 IFS=-
命令列即時設定分隔符為 - ,read
命令選項 -r
表示保持原樣。
這行命令結果將使 $x 被賦值 foo , $y 被賦值 bar , $z 被賦值 baz 。
另外注意 <<<
是 即入字串(here-string) 的操作符,其使一個字串重定向為標準輸入。這裡,$str 變數的字串做為了 read
命令的輸入。
也可把分割開的變數放到一個陣列變數中:
$ IFS=- read -ra parts <<< "foo-bar-baz"
命令 read
的 -a 命令選項意味著將分割的字串賦值給陣列變數 parts ,之後,可通過 ${parts[0]}
,${parts[1]}
分別訪問陣列中的元素,也可使用 ${parts[@]}
訪問所有元素。(譯註:若直接使用 $parts 將訪問 ${parts[0]}
即變數中的第1個元素。自己實踐驗證)
8. 依次處理字串中的字元
$ while IFS= read -rn1 c; do
# 對變數 $c 即字串中的字元操作
done <<< "$str"
這裡使用 read
命令選項 -n1 一次從輸入字串中讀1個字元處理。類似地可以使用 n2 命令選項指示一次從輸入字串中讀2個字元進行處理等:
9. 在字串中用 "bar" 替換 "foo"
$ str=baz-foo-bar-foo-foo
$ echo ${str/foo/bar}
baz-bar-bar-foo-foo
命令 echo ${str/foo/bar}
使用 bash 的引數展開機制,它在 $str 中找到 foo 最開始出現位置並將其用 bar 替換。很簡單吧!
用 bar 替換所有出現的 foo ,如下命令表示式:
$ echo ${str//foo/bar}
baz-bar-bar-bar-bar
10. 檢查一個字串模式匹配
$ if [[ $file = *.zip ]]; then
# 執行一些操作
fi
這裡如果 $file 變數的字串值模式匹配 *.zip (包含 .zip 子字串)。這是個簡單的模式匹配。萬用字元 * 表示匹配任意數量的任意字元(包括空白符),萬用字元 ? 匹配任意單個字元,[...] 表示匹配 [] 中的任一字元。(參考 bash 手冊中的檔名展開的模式匹配)
下面是另一個例子,判斷螢幕回答是 Y 或 y 開始的字串:
# read answer
$ if [[ $answer = [Yy]* ]]; then
# 執行一些操作
fi
11. 判斷一個字串匹配正規表示式
$ if [[ $str =~ [0-9]+.[0-9]+ ]]; then
# 執行一些操作
fi
以上命令以擴充套件正規表示式 [0-9]+.[0-9]+ 判斷變數 $str 是否匹配。正規表示式 [0-9]+.[0-9]+ 含義,匹配 數字+1個任意字元+數字 的組合。詳見手冊 man 3 regex
命令。(譯註:命令中雙目運算子 =~ 等同 == ,只表示使用擴充套件正規表示式,前面例子中的 = 等同 == 是為與 POSIX 規範相容)
12. 獲取字串長度
$ echo ${#str}
這裡利用引數展開機制,${#str} 表示返回變數 $str 值的長度,非常簡單!(譯註:前面第7節的例子中,有個陣列變數 $parts ,若執行命令 $ echo ${#parts}
將返回陣列元素個數,輸出 3 )
13. 從字串中按位置取出子串
$ str="hello world"
$ echo ${str:6}
以上命令從字串 "hello world" 中取出子串 "world" 。它使用子字串展開機制。更一般的形式 ${var:offset:length}
,注意:1. 字串位置以 0 開始記,故例子中 offset 值 6 指向字元 w 。2. 若省略 length 將從位置 取到字串尾。
下面是另一個例子:
$ echo ${str:7:2}
輸出:
or
14. 大寫化字串
$ declare -u var
$ var="foo bar"
bash 的declare
命令宣告一個變數的同時也可賦予變數一些特性。本例中,通過命令選項 -u 使宣告的變數 var 的值大寫化。意味著之後無論給 var 變數賦值什麼字串,都將自動全部變成大寫:
$ echo $var
FOO BAR
注意命令選項 -u 在 bash 版本 4 之後引入。類似地可以使用 bash 4 的另一引數展開特性完成大寫轉換的功能,形如: ${var^^}
表示式:
$ str="zoo raw"
$ echo ${str^^}
輸出:
ZOO RAW
15. 小寫化字元
$ declare -l var
$ var="FOO BAR"
類似於 -u 命令選項, 選項 -l 對於 declare
命令是使變數 var 小寫化:
$ echo $var
foo bar
選項 -l 也同樣是在 bash 版本 4 之後有效。
另一方法使用形如 ${var,,}
的引數展開表示式使 var 變數值轉換小寫:
$ str="ZOO RAW"
$ echo ${str,,}
輸出:
zoo raw
歡迎指正
享受文中所說的方法和技巧使用的樂趣吧,並且讓我知道您的想法。也許我漏掉了什麼,非常樂意收到您的指正。