Zsh 開發指南(第二篇 字符串處理之經常使用操做)

導讀

字符串處理是 shell 腳本的重點部分,由於 shell 腳本主要的工做是和文件或者其餘程序打交道,數據格式一般是文本,而處理沒有統一格式的文本文件出奇地複雜,shell 命令中也有不少都是處理文本的。用 bash 處理文本的話,由於自身的功能有限,常常須要調用像 awksedgrepcatcutcommdirnamebasenameexprsortuniqheadtailtactrwc 這樣命令,不留神腳本就成了命令大聚會。命令用法各異,有的很簡單(好比 cuttrwc),看一眼 man 就會用;有的很複雜(好比 awksedgrep),用了好多年基本也只會用不多一部分功能。互相配合也容易出現各類各樣的問題(好比要命的空格和換行符問題),難以調試,調用命令的開銷也很大。而用好了 zsh 的話,能夠大幅減小這些命令的使用(並不能徹底避免,由於某些場景確實比較適合用這樣的命令處理,好比處理一個大文本文件),而且大幅提高腳本的性能(主要由於減小了進程啓動的開銷,好比一次簡單的字符串替換,調用外部命令實現比內部實現的時間要多好幾個數量級)。git

但也所以 zsh 的字符串處理功能很複雜,能夠說 zsh 的字符串處理功能,要比絕大多數編程語言自帶的字符串函數庫或者類庫要強大(在不依賴外部命令的狀況)。同時各類用法也比較怪異,不少時候簡潔性和可讀性是有矛盾的,很難兼顧。而 shell 的使用場景決定簡潔性是不能被犧牲掉的,即便用 Python 這樣比較簡潔的語言來處理字符串,不少時候也只能寫出冗長的代碼,而 zsh 常常能夠一行搞定(可能有人想到了 Perl,Perl 在處理文本方面確實有比較明顯的優點,但使用 Perl 的話也要承擔更多的成本),若是再加上適當地使用外部命令,基本能夠應付大多數字符串處理場景。由於字符串處理的內容比較豐富,我會分多篇文章寫。本篇只涉及最基礎和經常使用的字符串操做,包括字符串的拼接、切片、截斷、查找、遍歷、替換、匹配、大小寫轉換、分隔等等。github

字符串定義和簡單比較,我已經在前一篇文章提過了,如今直接進入正題。正則表達式

字符串長度

% str=abcde
% echo $#str
5

# 讀取函數或者腳本的第一個參數的長度
% echo $#1複製代碼

字符串拼接

% str1=abc
% str2=def

% str2+=$str1
% echo $str2
defabc

% str3=$str1$str2
abcdefabc複製代碼

字符串切片

字符串切片以前也提過,這裏簡單複習一下。逗號先後不能有空格。字符位置是從 1 開始算起的。shell

% str=abcdef
% echo $str[2,4]
bcd
% echo $str[2,-1]
bcdef

# $1 是文件或者函數的第一個參數
echo ${1[2,4]}複製代碼

字符串切片還有另外一種風格的方法,即 bash 風格,功能大同小異。一般沒有必要用這個,並且由於字符位置是從 0 開始算,容易混淆。編程

% str=abcdef
% echo ${str:1:3}
bcd
% echo ${str:1:-1}
bcde複製代碼

字符串截斷

% str=abcdeabcde

# 刪除左端匹配到的內容,最小匹配
% echo ${str#*b}
cdeabcde

# 刪除右端匹配到的內容,最小匹配
% echo ${str%d*}
abcdeabc

# 刪除左端匹配到的內容,最大匹配
% echo ${str##*b}
cde

# 刪除右端匹配到的內容
% echo ${str%%d*}
abc複製代碼

字符串查找

子字符串定位。數組

% str=abcdef

# 這裏用的是 i 的大寫,不是 L 的小寫
% echo $str[(I)cd]
3

# I 是從右往左找,若是找不到則爲 0, 方便用來判斷
% (($str[(I)cd])) && echo good
good

# 找不到則爲 0
% echo $str[(I)cdd]
0

# 也可使用小 i,小 i 是從左往右找,找不到則返回數組大小 + 1
% echo $str[(i)cd]
3

% echo $str[(i)cdd]
7複製代碼

遍歷字符

% str=abcd

% for i ({1..$#str}) {
>    echo $str[i]
>}
a
b
c
d複製代碼

字符串替換

按內容替換和刪除字符。bash

% str=abcabc

# 只替換找到的第一個
% echo ${str/bc/ef}
aefabc

# 刪除匹配到的第一個
% echo ${str/bc}
aabc

# 替換全部找到的
% echo ${str//bc/ef}
aefaef

# 刪除匹配到的全部的
% echo ${str//bc}
aa


% str=abcABCabcABCabc

# /# 只從字符串開頭開始匹配,${str/#abc} 也同理
% echo ${str/#abc/123}
123ABCabcABCabc

# /% 只從字符串結尾開始匹配,echo ${str/%abc} 也同理
% echo ${str/%abc/123}
abcABCabcABC123


% str=abc
# 若是匹配到了則輸出空字符串
% echo ${str:#ab*}

# 若是匹配不到,則輸出原字符串
% echo ${str:#ab}
abc

# 加 (M) 後效果反轉
% echo ${(M)str:#ab}複製代碼

按位置刪除字符。微信

%str=abcdef

# 刪除指定位置字符
% str[1]=
% echo $str
bcdef

# 能夠刪除多個
% str[2,4]=
% echo $str
bf複製代碼

按位置替換字符。app

% str=abcdefg

# 一對一地替換
% str[2]=1
% echo $str
a1cdefg

# 能夠多對多(也包括一對多和多對一)地替換字符,兩邊的字符數量不須要一致。
# 把第2、三個字符替換成 2345
% str[2,3]=2345
% echo $str
a2345defg複製代碼

判斷字符串變量是否存在

若是用 [[ "$strxx" == "" ]] ,那沒法區分變量是沒有定義仍是內容爲空,在某些狀況是須要區分兩者的。編程語言

% (($+strxx)) && echo good

% strxx=""
% (($+strxx)) && echo good
good複製代碼

(($+var)) 的用法也能夠用來判斷其餘類型的變量,若是變量存在則返回真(0),不然返回假(1)。

字符串匹配判斷

判斷是否包含字符串。

% str1=abcd
% str2=bc

% [[ $str1 == *$str2* ]] && echo good
good複製代碼

正則表達式匹配。

% str=abc55def

# 少許字符串的話,儘可能不要用 grep
# 本文不講正則表達式格式相關內容
# 另外 zsh 有專門的正則表達式模塊
% [[ $str =~ "c[0-9]{2}\de" ]] && echo a
a複製代碼

大小寫轉換

% str="ABCDE abcde"

# 轉成大寫,(U) 和 :u 兩種用法效果同樣
% echo ${(U)str} --- ${str:u}
ABCDE ABCDE --- ABCDE ABCDE

# 轉成小寫,(L) 和 :l 兩種用法效果同樣
% echo ${(L)str} --- ${str:l}
abcde abcde --- abcde abcde

# 轉成首字母大寫
% echo ${(C)str} 
Abcde Abcde複製代碼

目錄文件名截取

% filepath=/a/b/c.x

# :h 是取目錄名,即最後一個 / 以前的部分,若是沒有 / 則爲 .
% echo ${filepath:h}
/a/b

# :t 是取文件名,即最後一個 / 以後的部分,若是沒有 / 則爲字符串自己
% echo ${filepath:t}
c.x

# :e 是取文件擴展名,即文件名中最後一個點以後的部分,若是沒有點則爲空
% echo ${filepath:e}
x

# :r 是去掉末尾擴展名的路徑
% echo ${filepath:r}
/a/b/c複製代碼

字符串分隔

# 使用空格做爲分隔符,多個空格也只算一個分隔符
% str='aa bb cc dd'
% echo ${str[(w)2]}
bb
% echo ${str[(w)3]}
cc

# 指定分隔符
% str='aa--bb--cc'
# 若是分隔符是 : 就用別的字符做爲左右界,好比 ws.:.
% echo ${str[(ws:--:)3]}
cc複製代碼

多行字符串

字符串定義能夠跨行。

% str="line1 > line2"
% echo $str
line1
line2複製代碼

讀取文件內容到字符串

# 比用 str=$(cat filename) 性能好不少
str=$(<filename)

# 比用 cat filename 性能好不少,引號不能省略
echo "$(<filename)"

# 遍歷每行,引號不能省略
for i (${(f)"$(<filename)"}) {
    echo $i
}複製代碼

讀取文件指定行。

文件 test.txt 內容以下:

line 1. apple
line 2. orange複製代碼
# 小文件或者須要頻繁調用時,儘可能不要用 sed
% echo ${"$(<test.txt)"[(f)2]}
line 2. orange

# 輸出包含 「ang」 的第一行
% echo ${"$(<test.txt)"[(fr)*ang*]}
line 2. orange

# 輸出包含 pp 的第一行,但從左截掉 「line」 4個字符。
echo ${"$(<test.txt)"[(fr)*pp*]#line}複製代碼

讀取進程輸出到字符串

讀進程輸出和讀文件相似。

上邊字符串相關的處理,直接把 $(<test.txt) 換成 $(命令) 便可。若是必定須要一個文件名,能夠這樣。

# 返回 fd 路徑,優先使用,但某些場景會出錯
% wc -l <(ps)
4 /proc/self/fd/11

# 臨時文件,會自動刪除,適合上邊用法出錯的狀況
% wc -l =(ps)
3 /tmp/zshMWDpqD複製代碼

參考

tim.vanwerkhoven.org/post/2012/1…

全系列文章地址:github.com/goreliu/zsh…

付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活訂價,歡迎諮詢,微信 ly50247。

相關文章
相關標籤/搜索