背景
現(xiàn)在做的項(xiàng)目有個(gè)批量修改類名的需求泼诱,包括文件名坛掠、類名、工程文件中的名字治筒。去github上搜了一下還真找到一個(gè)似乎看起來比較滿足需求的腳本:<br /> rename-xcode-files
不過畢竟不能完全滿足自己的需求比如類名的前綴匹配比如屉栓,ATestXXX=>BTestXXX這種形式。
先把這個(gè)腳本的源碼部分貼一下:
#!/bin/bash
1) PROJECT_DIR=.
2) RENAME_CLASSES=rename_classes.txt
#First, we substitute the text in all of the files.
5) sed_cmd=`sed -e 's@^@s/[[:<:]]@; s@[[:space:]]\{1,\}@[[:>:]]/@; s@$@/g;@' ${RENAME_CLASSES} `
6) find ${PROJECT_DIR} -type f \
7) \( -name "*.pbxproj" -or -name "*.h" -or -name "*.m" -or -name "*.xib" -or -name "*.storyboard" \) \
8) -exec sed -i.bak "${sed_cmd}" {} +
# Now, we rename the .h/.m files
11) while read line; do
12) class_from=`echo $line | sed "s/[[:space:]]\{1,\}.*//"`
13) class_to=`echo $line | sed "s/.*[[:space:]]\{1,\}//"`
14) find ${PROJECT_DIR} -type f -regex ".*[[:<:]]${class_from}[[:>:]][^\/]*\.[hm]" -print | egrep -v '.bak$' | \
15) while read file_from; do
16) file_to=`echo $file_from | sed "s/\(.*\)[[:<:]]${class_from}[[:>:]]\([^\/]*\)/\1${class_to}\2/"`
17) echo mv "${file_from}" "${file_to}"
18) mv "${file_from}" "${file_to}"
19) done
20) done < ${RENAME_CLASSES}
其中rename_classes.txt文件內(nèi)容的形式是:
AClass BClass
TestClass TTestClass
源碼解析
雖然算上空行和注釋總共也只有20行代碼耸袜,不過乍一看還是很懵的友多,各種/@:.\等特殊的字符,有些沒有思緒堤框。不過這都是因?yàn)槲覍hell腳本的認(rèn)知其實(shí)還屬于小白階段域滥,所以對一些語法和特性并不理解纵柿,導(dǎo)致想修改腳本滿足自己更多個(gè)性化的需求不知道從何入手。查閱了很多資料启绰,文檔以及請教了一些對Shell腳本非常有經(jīng)驗(yàn)的同事昂儒,終于讀懂了這個(gè)腳本,現(xiàn)在就逐行分析一下源碼委可,算是Mark一下自己這幾天的收獲渊跋。
第1行,第2行
這兩行比較簡單着倾,將當(dāng)前的目錄路徑和當(dāng)前目錄下的rename_classes.txt
文件地址賦值給兩個(gè)變量
第5行
sed命令用于對文件以行為單位進(jìn)行內(nèi)容操作拾酝,如增刪改,查找替換卡者。sed命令的語法如下:
sed [選項(xiàng)] '[動(dòng)作]' 文件名
選項(xiàng):
-n:一般sed命令會(huì)把所有數(shù)據(jù)都輸出到屏幕蒿囤,如果加入此選擇則只會(huì)把經(jīng)過sed命令處理的行輸出到屏幕。
-e:允許對輸入數(shù)據(jù)應(yīng)用多條sed命令編輯崇决。
-i:用sed的修改結(jié)果直接修改讀取數(shù)據(jù)的文件材诽,而不是由屏幕輸出。
動(dòng)作:
a:追加嗽桩,在當(dāng)前行后添加一行或多行
c:行替換岳守,用c后面的字符串替換原數(shù)據(jù)行
i:插入凄敢,在當(dāng)前行前插入一行或多行
d:刪除碌冶,刪除指定的行
p:打印,輸出指定的行
s:字串替換涝缝,用一個(gè)字符串替換另外一個(gè)字符串扑庞。格式為“行范圍s/舊字串/新字串/g”,這里/g指的是在整行內(nèi)完整的匹配拒逮,否則默認(rèn)的只是匹配第一次查找到的舊字符串
根據(jù)上面的語法解釋罐氨,sed -e 后面接著的應(yīng)該是多條命令,以;分割滩援。
等同于下面三行命令:
s@^@s/[[:<:]]@;
s@[[:space:]]\{1,\}@[[:>:]]/@;
s@$@/g;@
那么@是什么呢栅隐?
原來,在執(zhí)行替換操作的時(shí)候玩徊,如果要替換的內(nèi)容中包含/租悄,這個(gè)時(shí)候需要對/進(jìn)行了轉(zhuǎn)義成\/
,不過這樣表達(dá)式的可讀性必然會(huì)降低恩袱,因此在sed中還可以使用|泣棋,@,^畔塔,!
作為命令的分隔符潭辈。
[[:space:]]
鸯屿、[[:<:]]
、[[:>:]]
又是什么意思呢把敢?
<br /> POSIX Bracket Expressions
從這個(gè)文檔里可以查到寄摆,[:space:]表示空格或者制表符的字符集合,外面那一層[]表示匹配[]中的字符查找技竟,是正則表達(dá)式中的規(guī)則冰肴, ^和$也是正則表達(dá)式中的規(guī)則,分別表示行首和行尾榔组。{1,}是表示匹配次數(shù)的限制熙尉,至少匹配到一次,{m,n}表示匹配到m到n次搓扯。[:<:]和[:>:]查閱很多資料后依然查詢不到检痰,后來在自己的猜測和驗(yàn)證下,這兩個(gè)字符的含義分別是從xx字符開始和以xx字符結(jié)束锨推,其中開始或者結(jié)束的標(biāo)志都是挨著xx字符的不是大寫/小寫字母铅歼、數(shù)字和_
這樣上面三行sed指令就可以翻譯為:
將行首替換為s/[:<:]
將至少匹配到一次的空格字符替換為[:>:]
將行尾替換為/g;
所以
AClass BClass
TestClass TTestClass
執(zhí)行完上面的三行命令后就變成了:
s/[:<:]AClass[:>:]/BClass/g;
s/[:<:]TestClass[:>:]/TTestClass/g;
執(zhí)行到這里看出來了:這幾行命令實(shí)際上最后生成的是一個(gè)新的sed命令,作用是將配置文件中的舊類名替換為新類名换可。個(gè)人認(rèn)為這里的命令行嵌套是此腳本的精髓之處椎椰。
第6行,第7行沾鳄,第8行
find命令可以實(shí)現(xiàn)對于文件的查找慨飘。
find命令的基本組成:
find pathname -options [-print -exec -ok]
參數(shù)
pathname: find命令所查找的目錄路徑。例如用.來表示當(dāng)前目錄译荞,用/來表示系統(tǒng)根目錄瓤的。
-print: find命令將匹配的文件輸出到標(biāo)準(zhǔn)輸出。
-exec: find命令對匹配的文件執(zhí)行該參數(shù)所給出的shell命令吞歼。相應(yīng)命令的形式為'command' {} \;圈膏,注意{ }和\;之間的空格篙骡。
-ok: 和-exec的作用相同稽坤,只不過以一種更為安全的模式來執(zhí)行該參數(shù)所給出的shell命令,在執(zhí)行每一個(gè)命令之前糯俗,都會(huì)給出提示尿褪,讓用戶來確定是否執(zhí)行。
find命令選項(xiàng)
-name:按照文件名查找文件叶骨。
-perm:按照文件權(quán)限來查找文件茫多。
-prune:使用這一選項(xiàng)可以使find命令不在當(dāng)前指定的目錄中查找,如果同時(shí)使用-depth選項(xiàng)忽刽,那么-prune將被find命令忽略天揖。
-user: 按照文件屬主來查找文件夺欲。
-group:按照文件所屬的組來查找文件。
-mtime -n +n:按照文件的更改時(shí)間來查找文件今膊, - n表示文件更改時(shí)間距現(xiàn)在n天以內(nèi)些阅,+n表示文件更改時(shí)間距現(xiàn)在n天以前。Find命令還有-atime和-ctime選項(xiàng)斑唬,但它們都和-mtime選項(xiàng)市埋。
-nogroup:查找無有效所屬組的文件,即該文件所屬的組在/etc/groups中不存在恕刘。
-nouser:查找無有效屬主的文件缤谎,即該文件的屬主在/etc/passwd中不存在。
-newer file1 ! file2:查找更改時(shí)間比文件file1新但比文件file2舊的文件
-type 查找某一類型的文件
b - 塊設(shè)備文件褐着。
d - 目錄坷澡。
c - 字符設(shè)備文件。
p - 管道文件含蓉。
l - 符號(hào)鏈接文件频敛。
f - 普通文件
sed -i表示對操作文件的原地修改,sed -i .xxx是可以在原地修改前對操作的文件進(jìn)行備份馅扣,備份后的文件名是原文件名.xxx斟赚。
所以,這幾行命令的意思是:找到當(dāng)前目錄下后綴名為pbxproj,h,m,xib的文件差油,對于每個(gè)找到的文件備份為后綴名為.bak的備份文件拗军,然后再執(zhí)行sed_cm的命令,這樣所有上述格式文件中的舊類名就已經(jīng)被替換成了新類名
第11行厌殉,第20行
讀取文件分為兩步:
1.將文件的內(nèi)容通過重定向(<)的方式傳給while
2.while中調(diào)用read將文件內(nèi)容一行一行的讀出來食绿,并付值給read后跟隨的變量侈咕。變量中就保存了當(dāng)前行中的內(nèi)容公罕。
第12行,第13行
這里的|是管道命令的操作符耀销,"|"只能處理經(jīng)由前面一個(gè)指令傳出的正確輸出信息楼眷,對錯(cuò)誤信息信息沒有直接處理能力。然后熊尉,傳遞給下一個(gè)命令罐柳,作為標(biāo)準(zhǔn)的輸入.
管理命令的輸出說明:
指令1 | 指令2 | 指令3
【指令1】正確輸出,作為【指令2】的輸入狰住,然后【指令2】的輸出作為【指令3】的輸入 张吉,【指令3】輸出就會(huì)直接顯示在屏幕上面了。
通過管道之后【指令1】和【指令2】的正確輸出不顯示在屏幕上面催植。
所以這兩行的意思就非常明顯了肮蛹,讀取rename_classes.txt文件中的舊類名最為class_from勺择,新類名作為class_to
第14行
在當(dāng)前目錄下查找class_from.h或者.m的文件,不包括.bak的備份文件伦忠,也不包括class_from的文件夾目錄
第15行,第19行
while語句
語法:
while 命令/條件
do
語句
done
機(jī)制:如果while后的命令執(zhí)行成功省核,或條件真,則執(zhí)行do和done之間的語句昆码,執(zhí)行完成后气忠,再次判斷while后的命令和條件;如果while后的命令執(zhí)行失敗赋咽,或條件為假旧噪,循環(huán)結(jié)束
第16行
\(\)
用于匹配文本中的某個(gè)子串。
在sed中脓匿,使用\(\)
對匹配的內(nèi)容進(jìn)行分組舌菜,使用\N的方式進(jìn)行引用。示例
echo "Three One Two" | sed 's|\(\w\+\) \(\w\+\) \(\w\+\)|\2 \3 \1|'
One Two Three
我們輸出了Three亦镶,One日月,Two三個(gè)單詞,在sed的替換規(guī)則中缤骨,使用空格分隔了三小段正則表達(dá)式\(\w\+\)來匹配每一個(gè)單詞爱咬,后面使用\1,绊起,\2精拟,\3分別引用它們的值。
所以這里的意思是將XX舊類名YY.h替換成XX新類名YY.h
第17行虱歪,第18行
打印出來改名前的文件路徑和改名后的文件路徑蜂绎,將舊的文件名替換為新的文件名
代碼改進(jìn)
讀懂了源碼之后,就可以修改滿足自己的需求了笋鄙。 目前這個(gè)腳本無法滿足需要的主要是兩個(gè)點(diǎn):
1.備份文件其實(shí)沒必要生成
2.支持對于舊類名的前綴匹配替換
修改方式:
1.第8行-exec sed -i.bak "${sed_cmd}" {} +
改為-exec sed -i "" "${sed_cmd}" {} +
师枣,
-i extension
Edit files in-place, saving backups with the specified extension.
If a zero-length extension is given, no backup will be saved. It
is not recommended to give a zero-length extension when in-place
editing files, as you risk corruption or partial content in situ-
ations where disk space is exhausted, etc.
要點(diǎn):
* 用 -i 命令將替換結(jié)果寫入文件
* -i 之后的""表示不生成備份文件,解決報(bào)錯(cuò)問題
2.將原來腳本中的[:>:]
都去掉就實(shí)現(xiàn)了前綴匹配