Zsh 開(kāi)發(fā)指南(第二篇 字符串處理之常用操作)

導(dǎo)讀

字符串處理是 shell 腳本的重點(diǎn)部分墅拭,因?yàn)?shell 腳本主要的工作是和文件或者其他程序打交道失仁,數(shù)據(jù)格式通常是文本掀序,而處理沒(méi)有統(tǒng)一格式的文本文件出奇地復(fù)雜统舀,shell 命令中也有很多都是處理文本的。用 bash 處理文本的話澄港,因?yàn)樽陨淼墓δ苡邢藿费模?jīng)常需要調(diào)用像 awksed回梧、grep废岂、catcut狱意、comm湖苞、dirnamebasename详囤、expr财骨、sortuniq藏姐、head隆箩、tailtac羔杨、tr捌臊、wc 這樣命令,不留神腳本就成了命令大聚會(huì)兜材。命令用法各異理澎,有的很簡(jiǎn)單(比如 cut逞力、trwc)糠爬,看一眼 man 就會(huì)用寇荧;有的很復(fù)雜(比如 awksed执隧、grep)揩抡,用了好多年基本也只會(huì)用很少一部分功能∨孤辏互相配合也容易出現(xiàn)各種各樣的問(wèn)題(比如要命的空格和換行符問(wèn)題)捅膘,難以調(diào)試添祸,調(diào)用命令的開(kāi)銷也很大滚粟。而用好了 zsh 的話,可以大幅減少這些命令的使用(并不能完全避免刃泌,因?yàn)槟承﹫?chǎng)景確實(shí)比較適合用這樣的命令處理凡壤,比如處理一個(gè)大文本文件),并且大幅提升腳本的性能(主要因?yàn)闇p少了進(jìn)程啟動(dòng)的開(kāi)銷耙替,比如一次簡(jiǎn)單的字符串替換亚侠,調(diào)用外部命令實(shí)現(xiàn)比內(nèi)部實(shí)現(xiàn)的時(shí)間要多好幾個(gè)數(shù)量級(jí))。

但也因此 zsh 的字符串處理功能很復(fù)雜俗扇,可以說(shuō) zsh 的字符串處理功能硝烂,要比絕大多數(shù)編程語(yǔ)言自帶的字符串函數(shù)庫(kù)或者類庫(kù)要強(qiáng)大(在不依賴外部命令的情況)。同時(shí)各種用法也比較怪異铜幽,很多時(shí)候簡(jiǎn)潔性和可讀性是有矛盾的滞谢,很難兼顧。而 shell 的使用場(chǎng)景決定簡(jiǎn)潔性是不能被犧牲掉的除抛,即使用 Python 這樣比較簡(jiǎn)潔的語(yǔ)言來(lái)處理字符串狮杨,很多時(shí)候也只能寫(xiě)出冗長(zhǎng)的代碼,而 zsh 經(jīng)车胶觯可以一行搞定(可能有人想到了 perl橄教,perl 在處理文本方面確實(shí)有比較明顯的優(yōu)勢(shì),但使用 perl 的話也要承擔(dān)更多的成本)喘漏,如果再加上適當(dāng)?shù)厥褂猛獠棵罨さ究梢詰?yīng)付大多數(shù)字符串處理場(chǎng)景。因?yàn)樽址幚淼膬?nèi)容比較豐富翩迈,我會(huì)分多篇文章寫(xiě)持灰。本篇只涉及最基礎(chǔ)和常用的字符串操作,包括字符串的拼接帽馋、切片搅方、截?cái)啾瓤浴⒉檎摇⒈闅v姨涡、替換衩藤、匹配、大小寫(xiě)轉(zhuǎn)換涛漂、分隔等等赏表。

字符串定義和簡(jiǎn)單比較,我已經(jīng)在前一篇文章提過(guò)了匈仗,現(xiàn)在直接進(jìn)入正題瓢剿。

字符串長(zhǎng)度

% str=abcde
% echo $#str
5

# 讀取函數(shù)或者腳本的第一個(gè)參數(shù)的長(zhǎng)度
% echo $#1

字符串拼接

% str1=abc
% str2=def

% str2+=$str1
% echo $str2
defabc

% str3=$str1$str2
abcdefabc

字符串切片

字符串切片之前也提過(guò),這里簡(jiǎn)單復(fù)習(xí)一下悠轩。逗號(hào)前后不能有空格间狂。字符位置是從 1 開(kāi)始算起的。

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

# $1 是文件或者函數(shù)的第一個(gè)參數(shù)
echo ${1[2,4]}

字符串切片還有另一種風(fēng)格的方法火架,即 bash 風(fēng)格鉴象,功能大同小異。通常沒(méi)有必要用這個(gè)何鸡,而且因?yàn)樽址恢檬菑?0 開(kāi)始算纺弊,容易混淆。

% str=abcdef
% echo ${str:1:3}
bcd
% echo ${str:1:-1}
bcde

字符串截?cái)?/h3>
% str=abcdeabcde

# 刪除左端匹配到的內(nèi)容骡男,最小匹配
% echo ${str#*b}
cdeabcde

# 刪除右端匹配到的內(nèi)容淆游,最小匹配
% echo ${str%d*}
abcdeabc

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

# 刪除右端匹配到的內(nèi)容
% echo ${str%%d*}
abc

字符串查找

子字符串定位隔盛。

% str=abcdef

# 這里用的是 i 的大寫(xiě)犹菱,不是 L 的小寫(xiě)
% echo $str[(I)cd]
3

# I 是從右往左找,如果找不到則為 0, 方便用來(lái)判斷
% (($str[(I)cd])) && echo good
good

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

# 也可以使用小 i骚亿,小 i 是從左往右找已亥,找不到則返回?cái)?shù)組大小 + 1
% echo $str[(i)cd]
3

% echo $str[(i)cdd]
7

遍歷字符

% str=abcd

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

字符串替換

按內(nèi)容替換和刪除字符。

% str=abcabc

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

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

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

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


% str=abcABCabcABCabc

# /# 只從字符串開(kāi)頭開(kāi)始匹配来屠,${str/#abc} 也同理
% echo ${str/#abc/123}
123ABCabcABCabc

# /% 只從字符串結(jié)尾開(kāi)始匹配虑椎,echo ${str/%abc} 也同理
% echo ${str/%abc/123}
abcABCabcABC123


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

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

# 加 (M) 后效果反轉(zhuǎn)
% echo ${(M)str:#ab}

按位置刪除字符俱笛。

%str=abcdef

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

# 可以刪除多個(gè)
% str[2,4]=
% echo $str
bf

按位置替換字符捆姜。

% str=abcdefg

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

# 可以多對(duì)多(也包括一對(duì)多和多對(duì)一)地替換字符,兩邊的字符數(shù)量不需要一致迎膜。
# 把第二泥技、三個(gè)字符替換成 2345
% str[2,3]=2345
% echo $str
a2345defg

判斷字符串變量是否存在

如果用 [[ "$strxx" == "" ]] ,那無(wú)法區(qū)分變量是沒(méi)有定義還是內(nèi)容為空磕仅,在某些情況是需要區(qū)分二者的珊豹。

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

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

(($+var)) 的用法也可以用來(lái)判斷其他類型的變量簸呈,如果變量存在則返回真(0),否則返回假(1)店茶。

字符串匹配判斷

判斷是否包含字符串蜕便。

% str1=abcd
% str2=bc

% [[ $str1 == *$str2* ]] && echo good
good

正則表達(dá)式匹配。

% str=abc55def

# 少量字符串的話贩幻,盡量不要用 grep
# 本文不講正則表達(dá)式格式相關(guān)內(nèi)容
# 另外 zsh 有專門(mén)的正則表達(dá)式模塊
% [[ $str =~ "c[0-9]{2}\de" ]] && echo a
a

大小寫(xiě)轉(zhuǎn)換

% str="ABCDE abcde"

# 轉(zhuǎn)成大寫(xiě)轿腺,(U) 和 :u 兩種用法效果一樣
% echo ${(U)str} --- ${str:u}
ABCDE ABCDE --- ABCDE ABCDE

# 轉(zhuǎn)成小寫(xiě),(L) 和 :l 兩種用法效果一樣
% echo ${(L)str} --- ${str:l}
abcde abcde --- abcde abcde

# 轉(zhuǎn)成首字母大寫(xiě)
% echo ${(C)str} 
Abcde Abcde

目錄文件名截取

% filepath=/a/b/c.x

# :h 是取目錄名丛楚,即最后一個(gè) / 之前的部分族壳,如果沒(méi)有 / 則為 .
% echo ${filepath:h}
/a/b

# :t 是取文件名,即最后一個(gè) / 之后的部分趣些,如果沒(méi)有 / 則為字符串本身
% echo ${filepath:t}
c.x

# :e 是取文件擴(kuò)展名仿荆,即文件名中最后一個(gè)點(diǎn)之后的部分,如果沒(méi)有點(diǎn)則為空
% echo ${filepath:e}
x

# :r 是去掉末尾擴(kuò)展名的路徑
% echo ${filepath:r}
/a/b/c

字符串分隔

# 使用空格作為分隔符喧务,多個(gè)空格也只算一個(gè)分隔符
% 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

讀取文件內(nèi)容到字符串

# 比用 str=$(cat filename) 性能好很多
str=$(<filename)

# 比用 cat filename 性能好很多功茴,引號(hào)不能省略
echo "$(<filename)"

# 遍歷每行,引號(hào)不能省略
for i (${(f)"$(<filename)"}) {
    echo $i
}

讀取文件指定行孽亲。

文件 test.txt 內(nèi)容如下:

line 1. apple
line 2. orange
# 小文件或者需要頻繁調(diào)用時(shí)坎穿,盡量不要用 sed
% echo ${"$(<test.txt)"[(f)2]}
line 2. orange

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

# 輸出包含 pp 的第一行,但從左截掉 “l(fā)ine” 4個(gè)字符返劲。
echo ${"$(<test.txt)"[(fr)*pp*]#line}

讀取進(jìn)程輸出到字符串

讀進(jìn)程輸出和讀文件類似玲昧。

上邊字符串相關(guān)的處理,直接把 $(<test.txt) 換成 $(命令) 即可篮绿。如果一定需要一個(gè)文件名孵延,可以這樣。

# 返回 fd 路徑亲配,優(yōu)先使用尘应,但某些場(chǎng)景會(huì)出錯(cuò)
% wc -l <(ps)
4 /proc/self/fd/11

# 臨時(shí)文件,會(huì)自動(dòng)刪除吼虎,適合上邊用法出錯(cuò)的情況
% wc -l =(ps)
3 /tmp/zshMWDpqD

參考

http://tim.vanwerkhoven.org/post/2012/10/28/ZSH/Bash-string-manipulation

本文不再更新犬钢,全系列文章在此更新維護(hù):github.com/goreliu/zshguide

付費(fèi)解決 Windows、Linux思灰、Shell玷犹、C、C++洒疚、AHK歹颓、Python坯屿、JavaScript、Lua 等領(lǐng)域相關(guān)問(wèn)題巍扛,靈活定價(jià)愿伴,歡迎咨詢,微信 ly50247电湘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末隔节,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子寂呛,更是在濱河造成了極大的恐慌怎诫,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贷痪,死亡現(xiàn)場(chǎng)離奇詭異幻妓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)劫拢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)肉津,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人舱沧,你說(shuō)我怎么就攤上這事妹沙。” “怎么了熟吏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵距糖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我牵寺,道長(zhǎng)悍引,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任帽氓,我火速辦了婚禮趣斤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黎休。我一直安慰自己浓领,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布奋渔。 她就那樣靜靜地躺著镊逝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嫉鲸。 梳的紋絲不亂的頭發(fā)上撑蒜,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼座菠。 笑死狸眼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浴滴。 我是一名探鬼主播拓萌,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼升略!你這毒婦竟也來(lái)了微王?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤品嚣,失蹤者是張志新(化名)和其女友劉穎炕倘,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體翰撑,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡罩旋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了眶诈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涨醋。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逝撬,靈堂內(nèi)的尸體忽然破棺而出浴骂,到底是詐尸還是另有隱情,我是刑警寧澤球拦,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布靠闭,位于F島的核電站,受9級(jí)特大地震影響坎炼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拦键,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一谣光、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芬为,春花似錦萄金、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至询张,卻和暖如春孙乖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工唯袄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弯屈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓恋拷,卻偏偏與公主長(zhǎng)得像资厉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔬顾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容