Shell 搜索與匹配
1、在檔案中查找字串
grep 命令可以搜索檔案,查找指定的字串,
$ grep myvar *.c
在這個例子中,我們搜索的檔案全都位于當前目錄下,因此,我們只使用了簡單的 shell 模式 *.c 來匹配以 .c 結束的檔案,并沒有在檔案名前再添加路徑,
但并非所有待搜索的檔案都老老實實地待在當前目錄下,但因為shell 并不在意你輸入多少路徑名,所以我們也可以這么寫:
$ grep myvar ../lib/*.c ../server/*.c ../cmd/*.c */*.c
如果待搜索的檔案不止一個,grep 會在輸出前加上檔案名以及冒號,然后是該檔案中包含 grep 搜索內容的文本,
grep 的第一個(非選項)引數可以是一個簡單的字串,也可以是更復雜的正則運算式(regexp),正則運算式不同于 shell 的模式匹配,盡管兩者有時看起來差不多,
常見錯誤
忘記指定 grep 的輸入,例如 grep myvar,這種情況下,grep 會認為你要從 STDIN 提供輸入,而你以為它會讀取檔案,于是 grep 就干等著,無所事事,
2、只顯示包含搜索結果的檔案名
你需要找出包含特定字串的檔案,但是不想看到其所在的文本行,只用輸出檔案名即可,經常在線上為了搜索組態檔,
用 grep 的 -l 選項僅顯示檔案名即可,如下:
$ grep -l myvar *.c
both.c
good.c
somio.c
$
如果在一個檔案中找到了多次匹配,grep 仍然只輸出該檔案名一次,如果沒有找到匹配,則什么都不輸出,
由于這些檔案包含了你要查找的字串,如果想據此構建一個待處理
檔案的串列,選項 -l 就能派上用場了,將 grep 命令放進 $(),然后就可以在命令列上使用這些檔案名了,如下:
rm -i $(grep -l 'This file is obsolete' * )
洗掉包含字串“This file is obsolete”的檔案,我們給 rm 加上了 -i 選項,以便在洗掉每個檔案前都先詢問你,
3、不區分大小寫搜索
你想要在日志檔案中不區分大小寫地搜索字串(如“error”),以匹配該字串的所有出現,用 grep 的 -i 選項忽略大小寫,如下所示:
grep -i error logfile.log
不區分大小寫的搜索能夠找出包含“ERROR”、“error”、“Error”的日志訊息,“ErrOR”和“eRrOr”這樣的也不例外,該選項在查找大小寫混合的單詞時尤其管用,或者對于查找的內容無法確定大小時,
4、縮減搜索結果
如果搜索回傳的結果不符合預期,其中包括許多并不需要的內容,將結果通過管道傳給 grep -v 并用運算式描述出你不想看到的內容,假設你想在日志檔案中找出整個 12 月的日志訊息,你知道日志檔案用字母縮寫 Dec 代表 12 月,但不敢肯定總是如此,為了確保找出所有的日志訊息,輸入下列命令:
grep -i dec logfile
得到的結果卻如下所示:
...
error on Jan 01: not a decimal number
error on Feb 13: base converted to Decimal
warning on Mar 22: using only decimal numbers
error on Dec 16 : the actual message you wanted
error on Jan 01: not a decimal number
...
一種快而糙的解決方案是,將第一次得到的結果通過管道傳給另一個grep,由后者過濾掉所有的“decimal”,
grep -i dec logfile | grep -vi decimal
將多個 grep 串聯在一起(因為前所未見、出乎意料的匹配會不斷出現),逐步過濾搜索結果,直至滿意,這種做法并不鮮見,
-v 選項非常方便,你只需要記住該排除什么就行了,
5、搜索更復雜的模式
grep 中的正則運算式提供了更為強大的模式匹配功能,能夠滿足大部分需求,正則運算式描述了待匹配字串的模式,字母字符(或者對于 shell沒有特殊含義的其他字符)只匹配自身,“A”匹配 A,“B”匹配B,這沒什么好說的,另一個重要的規則是按位置組合字母,如 AB匹配“AB”,這看起來也是顯而易見的,但是,正則運算式還定義了其他一些特殊字符,它們既可以單獨使用,也可以與其他字符結合,從而形成更為復雜的模式,
第一個特殊字符是點號(.),它可以匹配任意單個字符,因此,.... 可以匹配任意 4 個字符;A. 匹配“A”以及緊隨其后的任意單個字符;.A. 匹配任意單個字符,然后是“A”,接著是任意單個字符(未必和匹配到的第一個字符相同),
第二個特殊字符時星號(**),匹配上一個字符的 0 次或多次出現,因此,A* 匹配 0 個或多個“A”字符,.* 匹配 0 個或多個任意字符(如“abcdefg”、“aaaabc”、“sdfgf ;lkjhj”,甚至是空行),
那么 ..* 是什么意思?它匹配任意單個字符以及緊隨其后的 0 個或多個任意字符(也就是一個或多個字符,但不能是空行),
如下所示,我們知道一行的某些單詞,我們想模糊匹配,如下操作:
grep -E "1.*22" 2.text // 匹配1開頭,任意個字符后是22
結果如下:
1898090808098822:
Shell 檔案查找
1、查找所有的txt檔案
檔案系統中到處都是 txt 檔案,你想將它們集中到一個位置,那么我們該如何做呢?
find 命令可以找出符合要求的所有檔案并執行命令,將其移動到指定位置,例如:
find . -name '*.txt' -print -exec mv '{}' /txts \;
find 命令的語法和其他 Unix 命令不同,其選項并不是那種典型的連字符加上單字母,后面再跟上若干引數,find 命令的選項看起來像是簡短的單詞 1,依照邏輯順序出現,并描述要查找哪些檔案以及如何處理找到的檔案(如果存在的話),這種像單詞一樣的選項通常稱為謂詞(predicate),
find 命令的第一個引數是待搜索的目錄,典型用法是用點號(.)代表當前目錄,不過你也可以提供一個目錄串列,甚至通過指定根目錄(/)來搜索整個檔案系統(只要權限允許),
示例中的第一個選項(謂詞 -name)指定了要搜索的檔案模式,其語法和 bash 的模式匹配語法差不多,因此 *.txt 能夠匹配所有以“.txt”結尾的檔案名,匹配該模式的檔案被認為回傳的是真(true),接著將其交給下一個謂詞進行處理,
find 會遍歷檔案系統,將找到的檔案名交給謂詞測驗,如果謂詞回傳真,就通過,如果回傳假,則不再繼續往下進行,會接著處理下一個檔案名,
謂詞 -print 很簡單,它總是回傳真,同時會將檔案名列印到標準輸出,因此,能在謂詞序列中通過測驗而到達這一步的檔案都會輸出其名稱,如果不寫,默認會帶有這個謂詞,
-exec 就有點怪異了,到達這一步的檔案名都會變成接下來要執行的命令的一部分,剩下一直到 ; 的這部分就是命令,其中的 {} 會被替換成已查找到的檔案名,因此,在上面的例子中,如果 find 在./txt/jazz 子目錄中找到名為 1.txt 的檔案,那么要執行的命令就會是:
mv ./txt/jazz/1.txt /txts
所有匹配指定模式的檔案都會執行命令,如果找到的檔案數量眾多,那么命令的執行次數自然也不會少,
2、提升已找到檔案的處理速度
按照上面的例子,find命令會為每個名字符合要求的檔案執行命令,但是當檔案過多時命令自然會很慢,那么我們如何提高速度呢?
xargs 命令從標準輸入中接收以空白字符分隔(指定 -0 時除外)的檔案名,然后對盡可能多的檔案(略微少于系統的 ARG_MAX 值,參見 15.13 節)執行指定命令,由于呼叫其他命令會帶來不小的開銷,因此使用 xargs 可以顯著提升操作速度,因為它能夠盡量減少命令的呼叫次數,而不是每個檔案都呼叫,如下所示:
find . -name '*.txt' -print | xargs mv '{}' /txts;
3、查找檔案時不區分大小寫
有些 TXT 檔案的擴展名是 .TXT,而不是 .txt,查找時該如何兼顧兩者?
用 -iname 謂詞(如果使用的 find 版本支持)執行不區分大小寫的搜索,例如:
find . -iname '*.txt' -print | xargs mv '{}' /txts;
4、按日期查找檔案
幾個月前,有人給你發了一張 JPEG 圖片,你接收后就保存了起來,但現在記不清放哪了,怎樣才能找到這張圖片呢?
使用 find 命令的 -mtime 謂詞來檢查檔案的最后修改日期,例如:
find . -name '*.jpg' -mtime +90 -print
-mtime 謂詞接受一個引數,用于指定要搜索的時間段,90 代表 90天,在數字前使用加號(+90)表明要搜索的檔案是在 90 天前修改的,使用減號(-90)表明檔案是在 90 天以內修改的,如果既沒減號,也沒加號,則表明正好就是 90 天,
find 還可以使用邏輯運算子 AND、OR、NOT,如果知道檔案修改時間至少在一周(7 天)前,但不超過 14 天,那么就可以像下面這樣將兩個謂詞結合起來,如下所示:
find . -mtime +7 -a -mtime -14 -print
5、按型別查找檔案
你正在查找名稱中帶有單詞“java”的目錄,先嘗試了以下命令,
find . -name '*java*' -print
找到的檔案太多了,其中還包括檔案系統中所有的 Java 源代碼檔案,使用 -type 謂詞只選擇目錄,如下:
find . -type d -name '*java*' -print
同樣,我們可以使用 -type f指定指查找檔案,
我們將 -type d 放在前面,然后是 -name 'java',兩者的順序并不影響最終結果,但將 -type d 放在謂詞串列的最前面能略微提高搜索效率:對于碰到的每個檔案,先測驗其是否為目錄,如果是,才測驗名稱是否符合模式,目錄的數量比檔案要少一些,因此,這種測驗順序使得大部分檔案不用再進一步比較名稱了,
6、按內容查找檔案
你之前寫了一份重要的信件,并將其保存為以 .txt 為擴展名的文本檔案,但現在想不起檔案名的其余部分了,除此之外,唯一記得的就是信件內容中用到過單詞“portend”,那么該如何查找已知部分內容的檔案呢?
如果檔案就在當前目錄下,可以使用簡單的 grep 命令,
grep -i portend *.txt
如果還沒找到,我們換用一個更完備的解決方案:find 命令,使用其 -exec 選項對滿足謂詞的檔案執行命令,你可以按下列方式使用grep 或其他實用工具:
find . -name '*.txt' -exec grep -Hi portend '{}' \;
或者也可以使用xargs
find . -name '*.txt' | xargs grep -Hi portend
Shell 文本決議awk
1、保留部分輸出
你需要用某種方法保留部分輸出,丟棄其余輸出,比如我們日常線上日志,我們可能會輸出很多屬性,但是真正能用來解決實際問題的,大多是我們輸出的文字資訊,以下代碼會列印出所有輸入行的第一個欄位:
awk '{print $1}' myinput.file
欄位之間以空白字符分隔,實用工具 awk 從命令列上指定的檔案中讀取資料,如果沒有指定檔案,則從標準輸入讀取,$1代表每行以空格分割后的第一列,
除了上面的寫法,我們還可以通過管道傳入:
cat myinput.file | awk '{print $1}'
awk 的用法多變,最簡單的用法就是從輸入中列印出所選的一個或多個欄位,欄位之間以空白字符分隔(也可以用 -F 選項指定分隔字符),編號從 1 開始,欄位 $0 代表整個輸入行,
2、保留部分輸入行
你只想保留部分輸入行,例如第一個和最后一個欄位,舉例來說,你希望 ls 只列出檔案名和權限,不需要 ls -l 所提供的其他資訊,可惜的是,ls 并沒有相應的選項能夠按照這種方式限制輸出,可以通過管道將 ls 的輸出傳給 awk,并從中挑選出你需要的欄位,如下:
$ ls -l | awk '{print $1, $NF}'
total 151130
-rw-r--r-- add.1
drwxr-xr-x art
drwxr-xr-x bin
-rw-r--r-- BuddyIcon.png
drwxr-xr-x CDs
drwxr-xr-x downloads
drwxr-sr-x eclipse
...
$
如果我們用ls -l 命令的輸出,其形式如下所示:
drwxr-xr-x 2 username group 176 2026-10-28 20:09 bin
對于 awk 而言,決議這種輸出易如反掌(在 awk 中,默認的欄位分隔符為空白字符),
在輸出檔案名時,我們用了點小技巧,在 awk 中,各種欄位是用美元符號和欄位編號來參考的(如 $1、$2、$3),而且 awk 還有一個內建變數 NF,其中保存著當前行中的欄位總數,$NF 總是參考最后一個欄位,(例如,ls 的輸出行共有 8 個欄位,因此變數 NF 的值就是 8,$NF 指向的就是輸入行中的第 8 個欄位,在這個例子中就是檔案名),
注意:讀取 awk 變數時不需要使用 $(這一點和 bash 變數不同),NF 本身就是一個有效的變數參考,在其之前加上 $ 就將其含義從“當前行的欄位總數”改成了“當前行的最后一個欄位”,
3、顛倒每行的單詞
如果想按照逆序輸出輸入行中的單詞,通過下列腳本:
$ awk '{
> for (i=NF; i>0; i--) {
> printf "%s ", $i;
> }> printf "\n"
> }' <filename>
字符 > 不用你輸入,shell 會輸出該字符來提醒你還沒有敲完命令(shell 在查找能配對的單引號),由于 awk 程式位于單引號中,因此 bash shell 允許我們輸入多行代碼,同時使用 > 作為輔助提示符,直到我們給出與先前匹配的結束單引號,考慮到可讀性,我們在程式中加入了空白字符,不過也完全可以寫成一行,
$ awk '{for (i=NF; i>0; i--) {printf "%s ", $i;} printf "\n"}' <filename>
awk 語言的 for 回圈語法和 C 語言中的非常相似,我們用 for 回圈從最后一個欄位開始倒著處理到第一個欄位,同時輸出每個欄位的內容,
4、匯總數字串列
如果你需要匯總數字串列,其中有些數字并未出現在行中,用 awk 先過濾出待匯總的欄位,然后再做匯總,這里我們要對 ls -l 命令輸出的檔案大小進行匯總,如下:
ls -l | awk '{sum += $5}; END {print sum}'
我們要匯總 ls -l 輸出的第 5 個欄位,ls -l 的輸出如下所示:
-rw-r--r--. 1 root root 37 12月 23 21:44 2.text
-rwxr--r--. 1 root root 110 12月 10 02:20 ifTest.sh
各個欄位分別為:權限、鏈接、所有者、所屬組、大小(以位元組為單位)、最后一次修改日期、最后一次修改時間,以及檔案名,我們只對檔案大小感興趣,因此在 awk 程式中用 $5 來參考該欄位,我們在花括號({})里放置了兩段 awk 代碼,注意,awk 程式中可以有多個代碼段(或代碼塊),前有關鍵詞 END 的代碼塊僅在程式其他部分完成后運行一次,
本文由
傳智教育博學谷教研團隊發布,如果本文對您有幫助,歡迎
關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/541799.html
標籤:Java
