bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
find用於搜索文件或目錄,功能非常強大。該工具是findutils包提供的,該包中還包括一個老版本的oldfind工具,以及另一個非常強大的xargs命令,在搜索文件時,如果還要對搜索的文件進行后續的處理,一般都會結合xargs來實現。但本文並不過多涉及xargs,如果要了解xargs用法,見我的另一篇關於xargs的總結xargs的原理剖析及用法詳解,目前在網上暫時還沒找到比這篇文檔更詳細的xargs說明。
find搜索是從磁盤進行指定目錄開始掃描,而不是從數據庫搜索。
find [path...] [expression_list]
1.1 find基礎示例
因為內容有點多,所以放進單獨一篇文章中了。參見find常用用法示例,建議在閱讀本文之前一看,相信其中一些用法不會讓你失望的。
1.2 find理論部分
find [path...] [expression_list]
expression分為三種:options、test、action。對於多個表達式,find是從左向右處理的,所以表達式的前后順序不同會造成不同的搜索性能差距。
find首先對整個命令行進行語法解析,並應用給定的options,然后定位到搜索路徑path下開始對路徑下的文件或子目錄進行表達式評估或測試,評估或測試的過程是按照表達式的順序從左向右進行的(此處不考慮操作符的影響),如果最終表達式的表達式評估為true,則輸出(默認)該文件的全路徑名。
對於find來說,一個非常重要的概念:find的搜索機制是根據表達式返回的true/false決定的,每搜索一次都判斷一次是否能確定最終評估結果為true,只有評估的最終結果為true才算是找到,並切入到下一個搜索點。
1.2.1 expression operators
操作符控制表達式運算方式。確切的說,是控制expression中的options/tests/actions的運算方式,無論是options、tests還是actions,它們都可以給定多個,例如find /tmp -type f -name "*.log" -exec ls '{}' \; -print,該find中給定了兩個test,兩個action,它們之間從前向后按順序進行評估,所以如果想要改變運算邏輯,需要使用操作符來控制。
注意,理解and和or的評估方式非常重要,很可能寫在and或or后面的表達式不起作用,而導致跟想象中的結果不一樣。
下面的操作符優先級從高到低。
------------------------------------------------------------------------------------
( expr ) :優先級最高。為防止括號被shell解釋(進入子shell),所以需要轉義,即\(...\)
------------------------------------------------------------------------------------ ! expr :對expr的true和false結果取反。同樣需要使用引號包圍
------------------------------------------------------------------------------------ -not expr :等價於"! expr"
------------------------------------------------------------------------------------ expr1 expr2 :等同於and操作符。
------------------------------------------------------------------------------------ expr1 -a expr2 :等同於and操作符。
------------------------------------------------------------------------------------ expr1 -and expr2 :首先要求expr1為true,然后expr2以expr1搜索的結果為基礎繼續檢測,然后再返回
:檢測值為true的文件。因為expr2是以expr1結果為基礎的,所以如果expr1返回
:false,則expr2直接被忽略而不會進行任何操作
------------------------------------------------------------------------------------ expr1 -o expr2 :等同於or操作符
------------------------------------------------------------------------------------ expr1 -or expr2 :只有expr1為假時才評估expr2。
------------------------------------------------------------------------------------ expr1 , expr2 :逗號操作符表示列表的意思,expr1和expr2都會被評估,但expr1的true或false是被
:無視的,只有expr2的結果才是最終狀態值。
關於and和or操作符,一定要明確的是and后的表達式操作的對象是前面表達式的結果,而or操作符的對象則是前面評估后為假時文件。
例如:
它是一個and操作符,-name表達式是在-type篩選的結果基礎上再匹配文件名的。但如果是:
則-type f返回真的文件直接執行它后面默認的-print,而不滿足-type f的才會去被-name評估,所以返回結果中即有任意普通文件,也有任意log文件,但兩者同名的文件只返回一次。
總之,and和or對於find的影響比較大,后面會專門用一節徹底搞懂operator和action來詳細解釋。另外,在后文find深入用法示例中還有一個關於"忽略目錄"的例子,它很好的解釋了操作符的行為。
1.2.2 expression-options
options總是返回true。除了"-daystart",options會影響所有指定的test表達式部分,哪怕是test部分寫在options的前面。這是因為options是在命令行被解析完后立即處理的,而test是在檢測到文件后才處理的。對於"-daystart"這個選項,它們僅僅影響寫在它們后面的test部分,因此,建議將任何options部分寫在expression的最前面,若不如此,會給出一個警告信息。
---------------------------------------------------------------------------------------------------
-daystart:指定以每天的開始(凌晨零點)計算關於天的時間,用於改變時間類(-amin,-atime,-cmin,-ctime,-mmin和-mtime)
:的計算方式。默認天的計算是從24小時前計算的。例如,當前時間為5月3日17:00,要求搜索出2天內修改過的文件,默認
:搜索文件的起點是5月1日17:00,如果使用-daystart,則搜索文件的起點是是5月1日00:00。
:注意,該選項只會影響寫在它后面的test表達式。
--------------------------------------------------------------------------------------------------- -depth :搜索到目錄時,先處理目錄中的文件(子目錄),再處理目錄本身。對於"-delete"這個action,它隱含"-depth"選項。
--------------------------------------------------------------------------------------------------- -maxdepth levels:指定tests和actions作用的最大目錄深度,只能為非負整數。可以簡單理解為目錄搜索深度,但並非如此。當
:前path目錄的層次為1,所以若指定-maxdepth 0將得不到任何結果。
--------------------------------------------------------------------------------------------------- -mindepth levels:tests和actions不會應用於小於指定深度的目錄,"-mindepth 1"表示應用於所有的文件。
--------------------------------------------------------------------------------------------------- -ignore_readdir_race:當無法用stat檢測文件信息時(如無權限)會給出下圖所示的錯誤信息,如要忽略該信息,可以使用該選項。
------------------------------------------------------------------------------------------------
-warn:忽略警告信息。
1.2.3 expression-tests
find解析完命令行語法之后,開始搜索文件,在搜索過程中,每次檢測到的文件都會被test expression進行測試,符合條件的將被保留。
數值部分可以設置為以下3種模式:n可以是小數。
+n:大於n
-n:小於n
n :精確的等於n
對於文件大小而言,文件的大小是精確的,指定100KB,則比對的值必定是100KB。此時(+ -)n和字面意思是一樣的。
但對於時間而言,時間是有時間段的,例如指定前第四天,第四天也整整占用了一天,所以(+ -)n和文件大小的計算方法是不一樣的。
find在計算以天數為單位的時間時,默認會轉換為24小時制,除非同時指定了"-daystart"這個選項,這在前面已經解釋過了。例如當前時間為5月3號17:00,那么計算"atime +1"的時候,真正計算的是24*1=24小時之前,又因為前48小時到前24小時之間都屬於前一天內,所以其搜索的是是5月1號17:00以前被訪問過,若指定了"-daystart",則計算的是5月2號00:00之前被訪問過。
具體的選項如下:
-type X:根據文件類型來搜索
· b:塊設備文件
· c:字符設備文件
· d:目錄
· p:命名管道文件(FIFO文件)
· f:普通文件
· l:符號鏈接文件,即軟鏈接文件
· s:套接字文件(socket)
【文件大小或內容類測試條件】
-size n[cwbkMG]:根據文件大小來搜索,可以是(+ -)n,單位可以是: · b:512字節的(默認單位) · c:1字節的 · w:2字節 · k:1024字節 · M:1024k · G:1024M empty:空文件,對於目錄來說,則是空目錄
【文件名或路徑名匹配類測試條件】
------------------------------------------------------------------------------------------------ -name pattern | 文件的basename(不包括其前導目錄的純文件名)能被通配符模式的pattern匹配到。由於前導目錄被移除,
| 所以find對包含"/"的pattern是絕對不可能匹配到內容的,例如"-name a/b"的結果一定是空且會給出 | 警告信息,若要匹配這樣的文件,可考慮使用"-path"或"-name b"。需要注意的是,在find中的通配元
| 字符"*"、"?"和"[]"是能夠匹配以點開頭的文件的,之所以要在此說明這一點,是因為在bash中,這些通 | 配元字符默認是無法匹配"."開頭的文件的,例如"cp ~/* /tmp"不會把隱藏文件也拷貝走。若要忽略一個
| 目錄及其內的文件,可以配合"-prune",它會跳過整個目錄而不對此目錄做任何檢查。注意pattern要用 | 引號包圍防止被shell解釋 ----------------|------------------------------------------------------------------------------- -iname pattern | 不區分大小的"-name" ----------------|------------------------------------------------------------------------------- -path pattern | 文件名能被通配符模式的pattern匹配到。此模式下,通配元字符"*"、"?"和"[]"不認為字符"/"或"."是
| 特殊字符,也就是說這兩個字符也在通配范圍內,所以能匹配這兩個字符。例如find . -path "./sr*sc" | 可以匹配到名為"./src/misc"的目錄。find會將"-path"的pattern與文件的dirname和basename的結
| 合體進行比較,由於dirname和basename的結合體不包含尾隨"/",所以如果pattern中指定了尾隨"/"是 | 不可能匹配到任何東西的,例如find /tmp -path "/tmp/ab*/",實際上它會給出警告信息,提示
| pattern以"/"結尾。使用"-path"的時候,一定要注意"-path"后指定的路徑起點屬於
| "find path expression"的path內,例如"find /bar -path /foo/bar/myfile -print"不可能 | 匹配到任何東西。若要忽略目錄及其內文件,可配合"-prune",它會跳過整個目錄而不對此目錄做檢查。如
| "find . -path ./src/emacs -prune -o -print"將跳過對目錄"./src/emacs"的檢查。
| 注意pattern要用引號包圍防止被shell解釋 ----------------|--------------------------------------------------------------------------------- -ipath pattern | 不區分大小寫的"-path" ----------------|--------------------------------------------------------------------------------- -regex pattern | 文件名能被正則表達式pattern匹配到的文件。正則匹配會匹配整個路徑,例如要匹配文件名為"./fubar3"
| 的文件,可以使用".*bar."或".*b.*3",但不能是"f.*r3",默認find使用的正則類型是Emacs正則,
| 但可以使用-regextype來改變正則類型 ----------------|--------------------------------------------------------------------------------- -iregex pattern | 不區分大小寫的"-regex"
【權限類測試條件】
-perm mode | 精確匹配給定權限的文件。"-perm g=w"將只匹配權限為0020的文件。當然,也可以寫成三位數字的權限模式
-----------|------------------------------------------------------------------------------------- -perm -mode| 匹配完全包含給定權限的文件,這是最可能用上的權限匹配方式。例如給定的權限"-0766",則只能匹配"N767"、 | "N777"和"N776"這幾種權限的文件,如果使用字符模式的權限,則必須指定u/g/o/a,例如"-perm -u+x,a+r"
| 表示至少所有人都有讀權限,且所有者有執行權限的文件
-----------|------------------------------------------------------------------------------------- -perm /mode| 匹配任意給定權限位的權限,例如"-perm /640"可以匹配出600,040,700,740等等,只要文件權限的任意位能
| 包含給定權限的任意一位就滿足
-----------|------------------------------------------------------------------------------------- -perm +mode| 由於某些原因,此匹配模式被替換為"-perm /mode",所以此模式已經廢棄
-----------|------------------------------------------------------------------------------------- -executable| 具有可執行權限的文件。它會考慮acl等的特殊權限,只要是可執行就滿足。它會忽略掉-perm的測試
-----------|------------------------------------------------------------------------------------- -readable | 具有可讀權限的文件。它會考慮acl等的特殊權限,只要是可讀就滿足。它會忽略掉-perm的測試
-----------|------------------------------------------------------------------------------------- -writable | 具有可寫權限的文件。它會考慮acl等的特殊權限,只要是可寫就滿足。它會忽略掉-perm的測試(不是writeable)
【所有者所屬組類測試條件】
-gid n :gid為n的文件 -group gname:組名為gname的文件 -uid n :文件的所有者的uid為n -user uname :文件的所有者為uname,也可以指定uid -nogroup :匹配那些所屬組為數字格式的gid,且此gid沒有對應組名的文件 -nouser :匹配那些所有者為數字格式的uid,且此uid沒有對應用戶名的文件
【時間戳類測試條件】
-anewer file:atime比mtime更接近現在的文件。也就是說,文件修改過之后被訪問過 -cnewer file:ctime比mtime更接近現在的文件 -newer file:比給定文件的mtime更接近現在的文件。 -newer[acm]t TIME:atime/ctime/mtime比時間戳TIME更新的文件 -amin n:文件的atime在范圍n分鍾內改變過。注意,n可以是(+ -)n,例如-amin +3表示在3分鍾以前 -cmin n:文件的ctime在范圍n分鍾內改變過 -mmin n:文件的mtime在范圍n分鍾內改變過 -atime n:文件的atime在范圍24*n小時內改變過 -ctime n:文件的ctime在范圍24*n小時內改變過 -mtime n:文件的mtime在范圍24*n小時內改變過 -used n:最近一次ctime改變n天范圍內,atime改變過的文件,即atime比ctime晚n天的文件,可以是(+ -)n
【軟硬鏈接類測試條件】
-samefile name:找出指定文件同indoe的文件,即其硬鏈接文件 -inum n:inode號為n的文件,可用來找出硬鏈接文件。但使用"-samefile"比此方式更方便 -links n:有n個軟鏈接的文件
【雜項測試】
-false:總是返回false,這選項有奇用 -true :總是返回true,這選項有奇用
1.2.4 expression-actions
actions部分一般都是執行某些命令,或實現某些功能。這部分是find的command line部分。
-delete | 刪除文件,如果刪除成功則返回true,如果刪除失敗,將給出錯誤信息。"-delete"動作隱含"-depth"。
--------------------------------------------------------------------------------------------------- -exec command ;| 注意有個分號";"結尾,該action是用於執行給定的命令。如果命令的返回狀態碼為0則該action返回true。
| command后面的所有內容都被當作command的參數,直到分號";"為止,其中參數部分使用字符串"{}"時,它
| 表示find找到的文件名,即在執行命令時,"{}"會被逐一替換為find到的文件名,"{}"可以出現在參數中的
| 任何位置,只要出現,它都會被文件名替換。 | 注意,分號";"需要轉義,即"\;",如有需要,可以將"{}"用引號包圍起來
---------------|----------------------------------------------------------------------------------- -ok command ; | 類似於-exec,但在執行命令前會交互式進行詢問,如果不同意,則不執行命令並返回false,如果同意,則執 | 行命令,但執行的命令是從/dev/null讀取輸入的
---------------|----------------------------------------------------------------------------------- -print | 總是返回true。這是默認的action,輸出搜索到文件的全路徑名,並尾隨換行符"\n"。由於在使用"-print"時所有的結
| 果都有換行符,如果直接將結果通過管道傳遞給管道右邊的程序,應該要考慮到這一點:文件名中有空白字符(換行符、制表
| 符、空格)將會被右邊程序誤分解,如文件"ab c.txt"將被認為是ab和c.txt兩個文件,如不想被此分解影響,可考慮使
| 用"-print0"替代"-print"將所有換行符替換為"\0"
-------|-------------------------------------------------------------------------------------------
-printf| 輸出格式太多,所以具體用法見man文檔
-------|------------------------------------------------------------------------------------------- -print0| 總是返回true。輸出搜索到文件的全路徑名,並尾隨空字符"\0"。由於尾隨的是空字符,所以管道傳遞給右邊的程序,然后 | 只需對這個空字符進行識別分隔就能保證文件名不會因為其中的空白字符被誤分解
--------------------------------------------------------------------------------------------------- -prune | 不進入目錄,所以可用於忽略目錄,但不會忽略普通文件。沒有給定-depth時,總是返回true,如果給定-depth,則直接 | 返回false,所以-delete(隱含了-depth)是不能和-prune一起使用的
-------|------------------------------------------------------------------------------------------- -ls | 總是返回true。將找到的文件以"ls -dils"的格式打印出來,其中文件的size部分以KB為單位
一定要注意,action是可以寫在tests表達式前面的,它並不一定是在test表達式之后執行。
例如:
shell> find /tmp -print -type f -name "*.txt" /tmp /tmp/userfile /tmp/passwdfile /tmp/testdir /tmp/testdir/a.log /tmp/a.txt /tmp/time.sh /tmp/b.txt /tmp/abc /tmp/abc/axyz.log /tmp/about.html
它將輸出/tmp下所有文件,而不是輸出滿足-type f -name "*.txt"的文件,因為-print總是返回true,它是第一個表達式,所以直接輸出整個目錄,后面的表達式-type f -name "*.txt"雖然在-print之后也被評估了,但是卻沒有對應的action,所以它們的匹配行為並沒顯示出來。如果在表達式-type f -name "*.txt"之后加上另外一個action,那么這個action將只針對匹配到的文件進行操作。
[root@server2 tmp]# find /tmp -print -type f -name "*.txt" -ls /tmp /tmp/userfile /tmp/passwdfile /tmp/testdir /tmp/testdir/a.log /tmp/a.txt 69617151 4 -rw-r--r-- 1 root root 1758 Jun 8 03:50 /tmp/a.txt /tmp/time.sh /tmp/b.txt 101409594 4 -rw-r--r-- 1 root root 68 Jun 8 08:02 /tmp/b.txt /tmp/abc /tmp/abc/axyz.log /tmp/about.html
可以看到結果中,只有滿足條件的兩個文件才執行了ls命令。
還可以寫多個action,寫在哪里要注意它們的執行順序。其實,不推薦使用除了"-print"、"-print0"和"-prune"外其他所有的action,如有需要,應該配合xargs命令來實現目的。
1.3 徹底搞懂operator和action的關系
find的邏輯是很嚴格的,但是如果沒有深究過,可能會出現不容易理解的偏差。
下面,我對and和or操作符,並以-print這個action為例來詳細說明它們的關系。在測試的時候,find的"-D debug"選項非常好用,它能讓我們知道find是按照什么邏輯處理各個選項的。debug有幾種類型,這里選擇"-D rates"這種調試類型。
首先要明確一個結論,and的優先級高於or。
"expr1 -a expr2":-a的邏輯是只有當expr1為真時才評估expr2。 "expr1 -o expr2":-o的邏輯是只有當expr1為假時才評估expr2。 expr1 -o expr2 -a expr3:and的優先級高於or,所以等價於expr1 -o (expr2 -a expr3)
例如, -name "*.log" -o -name "*.txt" ,搜索到1.txt文件時,*.log評估結果為假,於是評估*.txt,評估為真,於是整個-o邏輯返回真。
第二個結論:如果find評估完所有表達式后發現沒有action(-prune這個action除外),則在最末尾加上-print作為默認的action。注意,這個默認的action是在評估完所有表達式后加上的。且還需注意,如果只有-prune這個action,它還是會補上-print。
以下是准備數據:
rm -rf /tmp/* touch {1,2,3,4}.txt {a,b}.log
1.3.1 測試"and"操作符
現在先測試"expr1 -a expr2"中的"-a"。
[root@xuexi tmp]# find /tmp -type f -a -name "*.log" /tmp/a.log /tmp/b.log
用"-D rates"來看調試結果:
[root@xuexi tmp]# find -D rates /tmp -type f -a -name "*.log" /tmp/a.log /tmp/b.log Predicate success rates after completion: ( -name *.log [0.8] [2/12=0.166667] -a [0.95] [2/12=0.166667] [need type] -type f [0.95] [2/2=1] ) -a [0.76] [2/12=0.166667] -print [1] [2/2=1]
將最后一行進行簡化,刪掉所有中括號中的內容,得到的結果是:
( -name *.log -a -type f ) -a -print
所以,上面的語句執行邏輯是:
find /tmp ( -name *.log -a -type f ) -a -print
可見,find自動補上了-print這個默認的action,且邏輯是評估-name,在-name為真的基礎上再評估-type,最后在-type為真的基礎上評估-print,也就是將log后綴且是普通文件的文件打印出來。
這里的-name為什么被修改到了-type的前面去?這是因為find自帶優化功能,它會評估各個表達式的開銷,將開銷小的表達式放在前面,以便優化搜索。但要注意的是,優化后的表達式和我們給出的find命令行的結果不會有任何出入,它們在邏輯上是相同的,只是更換了一些表達式的前后位置。
然后,按照前面的結論,猜測下面3個find語句的邏輯:
find /tmp -type f -a -name "*.log" -print find /tmp -type f -print -a -name "*.log" find /tmp -type f -print -a -name "*.log" -print
上面3個find都給出了action,所以find不會再補上默認的action:-print。
所以,上面3個語句分別等價於下面3個語句:
find /tmp ( -name *.log -a -type f ) -a -print find /tmp ( -type f -a -print ) -a -name *.log find /tmp (( -type f -a -print ) -a -name *.log ) -a -print
唯一需要注意的是,有時候某些expr后面沒有action,這部分的expr將不會做任何處理,正如上面第二個語句,-name *.log后並不會補上-pinrt,所以對於find來說,-a -name *.log這個條件完全是多余的表達式。
以下是上述3個find命令的輸出結果以及分析。
[root@xuexi tmp]# find /tmp -type f -a -name "*.log" -print /tmp/a.log /tmp/b.log
-type f評估后得到所有普通文件,-name再在普通文件的基礎上篩選文件名后綴為".log"的文件,最后評估-print輸出".log"文件。
[root@xuexi tmp]# find /tmp -type f -print -a -name "*.log" /tmp/a.log /tmp/1.txt /tmp/2.txt /tmp/3.txt /tmp/4.txt /tmp/b.log
-type f評估后得到所有普通文件,然后在普通文件的基礎上評估-print,它直接將所有普通文件都輸出,再在輸出的文件的基礎上,評估-name,得到log文件,但之后沒有action,所以評估得到log文件的結果作廢。
[root@xuexi tmp]# find /tmp -type f -print -a -name "*.log" -print /tmp/a.log /tmp/a.log /tmp/1.txt /tmp/2.txt /tmp/3.txt /tmp/4.txt /tmp/b.log /tmp/b.log
-type f評估后得到所有普通文件,然后在普通文件的基礎上評估-print,它直接將所有普通文件都輸出,再在輸出的文件的基礎上,評估-name,得到log文件,再對得到的log文件評估-print,它將再次輸出log文件,所以結果中將輸出兩次log文件。
1.3.2 測試"or"操作符
有了上面的基礎,再理解or操作符就簡單多了。
直接猜下面4個命令的等價語句:
find /tmp -type f -o -name "*.log" --> find /tmp ( -type f -o -name *.log ) -a -print find /tmp -type f -o -name "*.log" -print --> find /tmp -type f -o ( -name *.log -a -print ) find /tmp -type f -print -o -name "*.log" --> find /tmp ( -type f -a -print ) -o -name *.log find /tmp -type f -print -o -name "*.log" -print --> find /tmp ( -type f -a -print ) -o ( -name *.log -a -print )
顯然,上面第三個語句中的-o -name *.log是多余的。
以下是這4個命令的輸出結果以及分析。
[root@xuexi tmp]# find /tmp -type f -o -name "*.log" /tmp/a.log /tmp/1.txt /tmp/2.txt /tmp/3.txt /tmp/4.txt /tmp/b.log
-type f評估后得到所有普通文件,但因為只有-o前面的表達式為假時才會評估-o后面的表達式,所以將會對非普通文件評估-name *.log,由於本示例中/tmp下沒有非普通文件,所以-name將得不到結果,於是評估得到普通文件,最后在普通文件的基礎上評估-print,即輸出普通文件。
[root@xuexi tmp]# find /tmp -type f -o -name "*.log" -print
-type f評估后得到所有普通文件,然后對非普通文件評估(-name *.log -a -print),由於沒有非普通文件,所以這個括號評估結果為空。由於-type f后面沒有給action,所以評估得到普通文件的結果也作廢。因此,這個find命令什么都不會輸出。
如果在/tmp下創建一個以".log"為后綴的目錄,則該find命令會輸出這個目錄。
[root@xuexi tmp]# find /tmp -type f -print -o -name "*.log" /tmp/a.log /tmp/1.txt /tmp/2.txt /tmp/3.txt /tmp/4.txt /tmp/b.log
-type f評估后得到所有普通文件,然后直接輸出,然后再對未輸出的文件評估-name,也就是找出".log"結尾的非普通文件,但即使如此,后面也沒有action,所以這里的-name是多余的。所以該命令等價於 find /tmp -type f -print 。
注意,-name評估的對象是未被-print輸出的文件,而不是未被-type評估的文件。雖然在本示例中是等價的,但如果-print前還有-a條件,如 find /tmp -type f -a "*.txt" -print -o -name "*.log" ,這里的-name評估的對象是未被輸出的文件,也就是(-type f -a -name .txt)的取反,而不是"-type f"的取反,所以log結尾的普通文件也會被-name評估,但因為沒有后續的action,評估的結果會作廢。
[root@xuexi tmp]# find /tmp -type f -print -o -name "*.log" -print /tmp/a.log /tmp/1.txt /tmp/2.txt /tmp/3.txt /tmp/4.txt /tmp/b.log
-type f評估后得到所有普通文件,然后直接輸出,然后對非普通文件評估(-name *.log -a -print),由於本處示例中沒有以".log"結尾的非普通文件,所以這個括號的評估結果為空。但如果在/tmp下創建一個以".log"為后綴的目錄,則該find命令會也輸出這個目錄。
1.4 find深入用法示例
(1). 指定目錄的搜索深度
例如搜素/etc目錄下的".conf"文件,但不搜索任何子目錄。
注意,"-depth"、"-maxdepth"和"-mindepth"是find的option表達式,所以它們寫在任意位置都會對test和action產生影響,所以它的位置可隨意書寫。
(2). 指定目錄的處理順序:-depth
"-depth"選項用於改變find使得它先搜索目錄中的文件,然后才處理目錄本身。默認情況下,find查找文件和目錄的順序是:假如/tmp下有a文件、b和c兩個目錄,b和c目錄中都有一些文件和目錄。
先查找/tmp本身
再查找/tmp下的各個文件,如a
再按序查找/tmp下的第一個目錄,如b
再查找b中的文件。
查找/tmp下的下一個目錄,如c
查找c中的文件
如果b和c目錄中還有其他目錄,則依照上面的處理規則進行處理。
即/tmp --> /tmp中的文件 --> /tmp中第一個目錄 --> 第一個目錄中的文件 --> 第一個文件中的目錄 --> … --> /tmp中第二個目錄 --> 第二個目錄中的文件 --> … --> /tmp中的第三個目錄 --> …/tmp中的最后一個目錄。
如下面的圖所示。
如果指定了-depth,則先處理目錄中的文件,再處理目錄本身。在Linux一切皆文件,子目錄也是文件。
例如,下面在/tmp目錄下新建一個目錄/tmp/tmp,並在子/tmp下創建文件a,目錄b和c,其中分別有一些文件。使用-depth參數運行find。
[root@xuexi tmp]# touch a [root@xuexi tmp]# mkdir b c [root@xuexi tmp]# touch ./b/{1..3}.log ./c/{1..3}.sh [root@xuexi tmp]# find /tmp/tmp -depth /tmp/tmp/b/2.log /tmp/tmp/b/1.log /tmp/tmp/b/3.log /tmp/tmp/b /tmp/tmp/c/2.sh /tmp/tmp/c/3.sh /tmp/tmp/c/1.sh /tmp/tmp/c /tmp/tmp/a /tmp/tmp
上面的find工作過程是:先找到/tmp/tmp,准備處理/tmp/tmp下的文件a、b、c(目錄也是文件),在這里的處理順序是b、c、a(為什么這樣的順序我也不知道),處理b的時候發現它是一個目錄,轉去處理b目錄里的文件,處理完b中的文件后處理b目錄,再處理c,發現是目錄,轉去處理c中的文件,處理完c中的文件后處理a文件。
需要注意的是,不一定總是先處理目錄和目錄中的內容。在同一個目錄下的文件是有處理順序的。例如下面在/tmp/tmp下添加一個.x隱藏文件,再測試"-depth"。
[root@xuexi tmp]# touch .x [root@xuexi tmp]# find /tmp/tmp -depth /tmp/tmp/b/2.log /tmp/tmp/b/1.log /tmp/tmp/b/3.log /tmp/tmp/b /tmp/tmp/.x # # 提前於c目錄被查找到 /tmp/tmp/c/2.sh /tmp/tmp/c/3.sh /tmp/tmp/c/1.sh /tmp/tmp/c /tmp/tmp/a /tmp/tmp
可以看到.x隱藏文件是在中途被處理的。
最后看看不加"-depth"的搜索順序。
[root@xuexi tmp]# find /tmp/tmp /tmp/tmp /tmp/tmp/b /tmp/tmp/b/2.log /tmp/tmp/b/1.log /tmp/tmp/b/3.log /tmp/tmp/.x /tmp/tmp/c /tmp/tmp/c/2.sh /tmp/tmp/c/3.sh /tmp/tmp/c/1.sh /tmp/tmp/a
一般只考慮忽略目錄,不考慮忽略文件,一般忽略文件時會給出警告。再者,忽略某種文件可以使用其他條件來忽略。忽略目錄后,Linux將直接不處理忽略的位置。
-prune需要放在定義忽略表達式的后面,之所以會如此,請看示例並考慮true和false進行理解。因為要忽略的是目錄,所以一般都只和"-path"配合,而不跟"-name"配合,實際上和-name配合的時候會給出警告信息。
例如,查找/tmp中的".log"文件,但排除/tmp子abc目錄內的".log"文件。
shell> find /tmp -path "/tmp/abc" -prune -o -name "*.log" /tmp/testdir/a.log /tmp/abc # 注意這個被忽略的目錄也在結果內 /tmp/a.log /tmp/axyz.log /tmp/xyz/axyz.log
為何這里使用"-o"操作符而不是"-a"操作符呢?先將上述find表達式進行分解。第一個表達式-path "/tmp/abc"可以搜索出/tmp/abc文件,它可能是目錄,也可能是文件,但無論它是什么文件,該表達式返回的總是true;第二個表達式是action類的"-prune",它和第一個表達式是"expr1 expr2"這樣的操作符格式,它等價於and邏輯關系,所以-path "/tmp/abc" -prune表示不進入匹配到的/tmp/abc目錄(若abc為文件,則給出警告信息),由於下一個表達式是使用"-o"連接的,所以到此為止就確定了該目錄/tmp/abc,且是不進入的,由於返回true,-prune會將結果輸出出來。
shell> find /tmp -path "/tmp/abc" -prune /tmp/abc
繼續,目的是搜索出非abc目錄下的log文件,它的表達式應該是-name "*.log"。但由於前面返回的是true,如果使用"-a"選項連接前后,則表示后面的表達式的操作對象是/tmp/abc,但前面的邏輯是不進入/tmp/abc,所以將得不到任何結果。
shell> find /tmp -path "/tmp/abc" -prune -a -name "*.log" | wc -l 0
而使用"-o"連接,則表示-name "*.log"的操作對象是path部分(即/tmp目錄)除了/tmp/abc的其余文件,它將找出/tmp下所有的log文件,但由於前面明確表示了不進入/tmp/abc目錄,所以就實現了忽略/tmp/abc目錄的作用。
它的等價命令如下:
find /tmp ( ( -path "/tmp/abc" -a -prune ) -o -name "*.log" ) -a print
應該注意到了,上面的/tmp/abc目錄也出現在結果中了,但是它並非log文件。這是因為-prune的副作用,find認為-prune不是一個完整的action,它會補上-print。如果想要完完全全的忽略該目錄,則可以使用下面的方式,在-prune之后加上-false強制使得該段表達式返回false,這樣該段結果就不會被輸出。
shell> find /tmp -path "/tmp/abc" -prune -false -o -name "*.log" /tmp/testdir/a.log /tmp/a.log /tmp/axyz.log /tmp/xyz/axyz.log
-prune的一個弱點是不適合通過通配符來忽略目錄,因為通配符出來的很可能導致非預期結果。
shell> find /tmp -path "/tmp/a*" -prune -o -name "*.log" /tmp/testdir/a.log /tmp/a.txt /tmp/abc /tmp/about.html /tmp/a.log /tmp/axyz.log /tmp/xyz/axyz.log
所以想要忽略多個目錄,最好的方法是多次使用-path,但要注意它們的邏輯順序。例如,搜索/tmp下所有log文件,但忽略/tmp/abc和/tmp/xyz兩個目錄中的log文件。
shell> find /tmp \( -path '/tmp/abc' -o -path '/tmp/xyz' \) /tmp/abc /tmp/xyz
所以完整的寫法是:
shell> find /tmp \( -path /tmp/abc -o -path /tmp/xzy \) -prune -o -name "*.log" /tmp/testdir/a.log /tmp/abc /tmp/a.log /tmp/axyz.log /tmp/xyz
(4). 不顯示待搜索目錄本身
在find顯示結果的時候,如果沒有表達式過濾掉目錄本身,那么目錄本身也會被顯示出來,但是很多時候這時不必要的。一般用於只顯示一級目錄下所有的文件,但不包括目錄本身。
[root@xuexi ~]# find ~ -maxdepth 1 /root # 搜索目錄本身也被顯示出來 /root/.bash_history /root/.lesshst /root/install.log /root/.viminfo /root/install.log.syslog /root/.tcshrc /root/.lftp /root/.cshrc /root/raid.sh /root/.bashrc /root/anaconda-ks.cfg /root/.bash_profile /root/bin /root/.bash_logout
要過濾掉目錄本身,方式也很簡單,多使用一個匹配表達式即可。
[root@xuexi ~]# find ~ -maxdepth 1 ! -name root /root/.bash_history /root/.lesshst /root/install.log /root/.viminfo /root/install.log.syslog /root/.tcshrc /root/.lftp /root/.cshrc /root/raid.sh /root/.bashrc /root/anaconda-ks.cfg /root/.bash_profile /root/bin /root/.bash_logout
(5). 搜索指定目錄下非空文件
"-empty"測試條件用於測試文件是否非空,對於目錄而言則是目錄為空目錄。一般可用於在搜索時,排除空文件或空目錄,所以一般和"!"或"-not"一起使用。
例如,搜索/tmp下非空文件和非空目錄。