Linux技巧:find 命令簡單入門介紹和問題點解析


Linux技巧:find 命令簡單入門介紹和問題點解析

在 Linux 命令中,find 是比較複雜難用的命令。使用該命令搜索文件時,常常發現自己找了一些例子能用,但稍微改一下條件,就搜不到想要的結果。

下面會以一些實例來說明使用 find 命令的關鍵要點和注意事項,解釋清楚各個條件能夠工作、或者不能工作的原因。

find 命令格式

要使用一個命令,首先要了解命令的格式,知道要提供什麼參數、參數作用是什麼。

查看 man find 對該命令的說明如下:

  • find - search for files in a directory hierarchy.
  • find [-H] [-L] [-P] [-D debugopts] [-Olevel] [path...] [expression]
  • GNU find searches the directory tree rooted at each given file name by evaluating the given expression from left to right, according to the rules of precedence, until the outcome is known (the left hand side is false for and operations, true for or), at which point find moves on to the next file name.
  • The -H, -L and -P options control the treatment of symbolic links. Command-line arguments following these are taken to be names of files or directories to be examined, up to the first argument that begins with ‘-’, or the argument ‘(’ or ‘!’.
  • If no paths are given, the current directory is used. If no expression is given, the expression -print is used (but you should probably consider using -print0 instead, anyway).
  • The expression is made up of options (which affect overall operation rather than the processing of a specific file, and always return true), tests (which return a true or false value), and actions (which have side effects and return a true or false value), all separated by operators. -and is assumed where the operator is omitted.
  • If the expression contains no actions other than -prune, -print is performed on all files for which the expression is true.

即,find 命令的作用是在目錄層次結構下搜索文件,默認會遞歸搜索所給目錄的子目錄,對查找到的每一個文件名(目錄名也屬於文件名)依次進行後面表達式的判斷,來決定是否打印搜索到的文件名、或者進行其他的操作。

注意:對每一個搜索到的文件名都依次進行表達式評估是非常關鍵的點,find 命令會把搜索到的每一個文件名都依次作為參數傳遞給後面的表達式進行評估,來決定如何處理這個文件,某個文件的表達式評估為 false,還是會繼續評估下一個文件,除非主動執行了結束的操作。

理解這一點,就會清楚為什麼有些文件名會打印出來,而有些文件名不會打印出來,因為它們本身就相互不關聯。

下面具體說明 find 命令格式各個部分的含義:

  • [-H] [-L] [-P] [-D debugopts] [-Olevel] 這部分屬於命令選項,比較少用到,這裡不做說明。
  • [path...] 該參數指定要查找哪個目錄,可以同時提供多個目錄名,用空格隔開,如果沒有提供該參數,默認查找當前目錄、及其子目錄。也可以提供文件名,只在當前目錄下查找該文件,不會在子目錄中查找。
  • [expression] 該參數指定評估表達式,可以提供多個表達式,不同表達式之間要用 operator 操作符來分隔開,如果表達式之間沒有提供操作符,默認會用 -and 操作符。表達式有 option、test、action 三種類型。如果不提供該參數,默認使用 -print 表達式,也就是打印出所給的文件名。參考上面說明,表達式參數要求以 ‘-’、‘(’、或者 ‘!’開頭,以便區分開前面的目錄參數。注意,在 bash 中要用 ‘\\(’ 來對 ‘(’ 進行轉義。

關於 find 命令的說明,也可以查看 GNU find 的在線幫助手冊 https://www.gnu.org/software/findutils/manual/html_mono/find.html,這裡面的說明比 man find 詳細,並提供了不少例子,可供參考。

在 Linux 中,目錄也屬於文件,find 在查找時,把目錄也當成文件處理,會查找並處理目錄名,並不是只處理文件名。

後面在說明時,如無特別備註,所說的文件名包含了目錄名。

查找指定目錄下的所有文件

find 命令最簡單的用法就是直接執行這個命令,不提供任何參數,默認會查找當前目錄、及其子目錄下的所有文件,並打印出所有文件名。

具體舉例如下:

<code>$ ls
Makefile.am src tests
$ find
.
./src
./src/main.c
./tests
./tests/bre.tests
./Makefile.am
/<code>

可以看到,在 shell 的當前工作目錄下執行 find 命令,不提供任何參數,會打印出當前目錄、及其子目錄下的所有文件名,包括目錄名。

可以在 find 命令後面提供目錄名,指定要查找哪個目錄:

<code>$ find .
.
./src
./src/main.c
./tests
./tests/bre.tests
./Makefile.am
$ find src
src
src/main.c
$ find src tests
src
src/main.c
tests
tests/bre.tests
/<code>

在 Linux 下,點號 ‘.’ 對應當前目錄,所以 find . 就是查找當前目錄下的所有文件,當沒有提供目錄參數時,默認就是使用 ‘.’ 這個參數。

find src 命令指定只查找 src 這個目錄下的所有文件。

find src tests 命令指定查找 src、tests 這兩個目錄下的所有文件,可以同時提供多個目錄名來指定查找這些目錄。

find src tests 命令也可以寫為 find ./src ./tests。

如果在 find 命令後面提供文件名,則只在當前目錄下查找該文件,不會在子目錄下查找:

<code>$ find Makefile.am
Makefile.am
$ find main.c
find: `main.c': No such file or directory
/<code>

結合上面打印的文件信息,可以看到當前目錄下有一個 Makefile.am 文件,find Makefile.am 命令可以找到這個文件,不會報錯。

而 main.c 文件是在 src 子目錄下,find main.c 命令執行報錯,提示找不到這個文件,它不會進入 src 子目錄進行查找。

注意:前面提到,查找條件要求以 ‘-’、‘(’、或者 ‘!’ 開頭,在遇到以這幾個字符開頭的任意參數之前,前面的參數都會被當作目錄參數,指定查找多個目錄時,直接在 find 命令後面寫上這些目錄的路徑,用空格隔開即可,不用加上 -o、-path 等選項,加上反而有異常。

剛接觸 find 命令,常見的誤區之一就是認為要用 -o 選項來指定查找多個目錄。

例如認為 find src -o tests 是同時查找 src、tests 這兩個目錄,這是錯誤的寫法,執行會報錯:

<code>$ find src -o tests
find: paths must precede expression: tests
/<code>

可以看到,執行報錯,提示目錄路徑參數必須在表達式參數之前提供。-o 參數以 - 開頭,會被認為是表達式參數,它自身、以及在它之後的所有參數都會認為是表達式參數,之後提供的目錄名不會被當作要查找的目錄。

某些表達式參數的後面可以提供目錄名,但是這些目錄名並不是用於指定查找該目錄下的文件,而是另有含義。

另一個誤區是,執行 find src -o tests 命令報錯後還不知道錯在哪裡,望文生義,又加上 -path 選項,誤寫為 find src -o -path tests、或者 find src -path -o tests。這兩個命令都會報錯,自行測試即知。

雖然寫為 find src -path tests 不會報錯,但是它並不會打印出 src、tests 這兩個目錄下的文件名。

後面會具體說明 -path 參數的用法。

查找時指定忽略一個或多個目錄

基於上面例子的目錄結構,如果想查找當前目錄下的文件,且忽略 tests 目錄,可以執行下面的命令:

<code>$ find . -path ./tests -prune -o -print
.
./src
./src/main.c
./Makefile.am
/<code>

可以看到,打印的文件名裡面沒有 tests 目錄名、以及它底下的文件名。

但是如果把上面 -path 後面的 ./tests 改成 tests,還是會查找 tests 目錄下的文件:

<code>$ find . -path tests -prune -o -print
.
./src
./src/main.c
./tests
./tests/bre.tests
./Makefile.am
/<code>

這個結果比較奇怪,查找時想要忽略 tests 目錄,寫為 -path ./tests 可以忽略,寫為 -path tests 就不能忽略。

這是使用 find 命令的 -path 參數時常見的錯誤,別人的例子可以生效,自己寫的時候就不生效。這需要理解 -path 參數的含義才能正確使用它。

前面提到,不同的表達式之間要用操作符分隔開,如果沒有提供操作符,默認使用 -and 操作符。

所以 find . -path ./tests -prune -o -print 命令的完整格式其實是 find . -path ./tests -and -prune -o -print。

下面對這個完整命令格式的各個參數進行詳細說明,以便理解它的工作原理,就能知道為什麼寫為 -path ./tests 可以忽略,寫為 -path tests 不能忽略。

-path pattern

這是一個 test 類型表達式,GNU find 在線幫助手冊對該表達式的說明如下:

Test: -path pattern

True if the entire file name, starting with the command line argument under which the file was found, matches shell pattern pattern.

To ignore a whole directory tree, use ‘-prune’ rather than checking every file in the tree.

The “entire file name” as used by find starts with the starting-point specified on the command line, and is not converted to an absolute pathname.

即,當 find 命令查找到的文件名完全匹配所給的 pattern 模式時,該表達式返回 true。

這裡面最關鍵的點是,要完全匹配 find 命令查找到的名稱,而不是部分匹配,也不是匹配文件的絕對路徑名

具體舉例說明如下:

<code>$ find . -path ./tests
./tests
$ find . -path tests
$ find . -path ./tests/
$ find tests
tests
tests/bre.tests
$ find tests -path tests
tests
$ find tests -path ./tests
/<code>

可以看到,find . -path ./tests 命令打印了 ./tests 目錄名,但是 find . -path tests 命令什麼都沒有打印。

查看上面 find . 命令打印的信息,可以看到該命令打印的 tests 目錄名是 ./tests,-path 參數要求是完全匹配才會返回 true,所以基於打印結果,就是要寫為 -path ./tests 才會返回 true。

前面貼出的 man find 說明提到,沒有提供除了 -prune 表達式之外的其他 action 類型表達式時,默認會對所有返回 true 的文件名執行 -print 表達式,打印該文件名。

所以打印結果裡面只有匹配到的 ./tests 目錄名,那些沒有完全匹配 ./tests 的文件名會返回 false,沒有被打印。

由於 find . 命令打印的目錄名後面沒有加上 / 字符,所以 find . -path ./tests/ 也匹配不到任何文件名,沒有打印任何信息。

類似的,執行 find tests 命令,打印的 tests 目錄名是 tests,那麼 find tests -path tests 命令可以完全匹配 tests 模式,打印出這個目錄名。

而 find tests -path ./tests 就匹配不到,沒有打印。

即,根據傳入的目錄參數不同,find 打印的目錄名不同,-path 後面要提供的目錄名也不同。

總的來說,在 -path 後面跟著的目錄名,需要完全匹配 find 命令打印的目錄名,而不是部分匹配。如果不確定 find 命令打印的目錄名是什麼,可以先不加 -path 參數執行一次 find 命令,看打印的文件名是什麼,再把對應的文件名寫到 -path 參數後面

在 -path 後面的 pattern 模式可以用通配符匹配特定模式的文件名,常見的通配符是用 * 來匹配零個或多個字符。

在 find 中使用時有一些需要注意的地方,舉例說明如下:

<code>$ find . -path *test*
$ find . -path ./test*
./tests
$ find . -path \\*test\\*
./tests
./tests/bre.tests
$ find . -path "*test*"
./tests
./tests/bre.tests
/<code>

可以看到,find . -path *test* 什麼都沒有打印,*test* 沒有匹配到 ./tests 這個名稱。

原因是這裡的 * 通配符是由 bash 來處理,通過文件名擴展來得到當前目錄下的子目錄名或者文件名,但是不會在目錄名前面加上 ./。

即,這裡的 find . -path *test* 相當於 find . -path tests,前面已經說明這是不匹配的。

find . -path ./test* 可以打印出匹配到的目錄名,經過 bash 擴展後,這個命令相當於 find . -path ./tests。

find . -path \\*test\\* 命令不但匹配到了 ./tests 目錄,還匹配到了該目錄下的 ./tests/bre.tests 文件。

這裡用 \\* 對 * 進行轉義,對 bash 來說它不再是通配符,不做擴展處理,而是把 * 這個字符傳遞給 find 命令,由 find 命令自身進行通配符處理,可以匹配到更多的文件。

這裡面涉及到 bash 和 find 對 * 通配符擴展的區別,bash 在文件名擴展 * 時,遇到斜線字符 / 則停止,不會擴展到目錄底下的文件名。

而 find 沒有把 / 當成特殊字符,會繼續擴展到目錄底下的文件名。

查看 GNU find 在線幫助手冊 https://www.gnu.org/software/findutils/manual/html_mono/find.html#Shell-Pattern-Matching 的說明如下:

Slash characters have no special significance in the shell pattern matching that find and locate do, unlike in the shell, in which wildcards do not match them.

find . -path "*test*" 命令的打印結果跟 find . -path \\*test\\* 相同。

原因是,bash 沒有把雙引號內的 * 當成通配符,會傳遞這個字符給 find,由 find 來處理通配符擴展。

如果不想用 \\* 來轉義,可以用雙引號把模式字符串括起來。

注意:雖然 -path 表達式的名字看起來是對應目錄路徑,但是也能用於匹配文件名,並不只限於目錄。

在 man find 裡面提到,有一個 -wholename 表達式和 -path 表達式是等效的,但是隻有 GNU find 命令支持 -wholename 表達式,其他版本的 find 命令不支持該表達式。從名字上來說,-wholename 表達式比較準確地表達出要完全匹配文件名稱。

-and

這是一個 operator 操作符,GNU find 在線幫助手冊對該操作符的說明如下:

expr1 expr2

expr1 -a expr2

expr1 -and expr2

And; expr2 is not evaluated if expr1 is false.

可以看到,-and 操作符有三個不同的寫法,都是等效的。

find 命令的操作符把多個表達式組合到一起,成為一個新的組合表達式,組合表達式也會有自身的返回值。

使用 -and 操作符組合的表達式要求兩個表達式都是 true,該組合表達式才是 true。

左邊的 expr1 表達式為 false 時,不再評估右邊的 expr2 表達式,該組合表達式會返回 false。

上面例子的 find . -path tests 命令什麼都沒有打印,就跟 -and 操作符的特性有關。

由於該命令沒有提供 action 類型表達式,默認會加上 -print 表達式,也就是 find . -path tests -print。

由於在 -path tests 和 -print 之間沒有提供操作符,默認會加上 -and 操作符,也就是 find . -path tests -and -print。

而 find . 命令搜索到的所有文件名都不匹配 -path tests 模式,都返回 false,基於 -and 操作符的特性,不往下執行 -print 表達式,也就不會打印任何文件名。

-prune

這是一個 action 類型表達式,GNU find 在線幫助手冊對該表達式的說明如下:

Action: -prune

If the file is a directory, do not descend into it. The result is true.

For example, to skip the directory src/emacs and all files and directories under it, and print the names of the other files found:

find . -wholename './src/emacs' -prune -o -print

The above command will not print ./src/emacs among its list of results. This however is not due to the effect of the ‘-prune’ action (which only prevents further descent, it doesn’t make sure we ignore that item).

Instead, this effect is due to the use of ‘-o’. Since the left hand side of the “or” condition has succeeded for ./src/emacs, it is not necessary to evaluate the right-hand-side (‘-print’) at all for this particular file.

這裡舉的例子就類似於我們現在討論的例子,裡面也解釋了查找時能夠忽略目錄的原因,可供參考。

前面提到,find 命令會把搜索到的每一個文件名都依次作為參數傳遞給後面的表達式進行評估。

如果傳遞到 -prune 表達式的文件名是一個目錄,那麼不會進入該目錄進行查找。

這個表達式的返回值總是 true。

具體舉例說明如下:

<code>$ find . -path \\*test\\* -prune
./tests
$ find . -path \\*test\\* -o -prune
.
/<code>

前面例子提到,find . -path \\*test\\* 會匹配到 ./tests 目錄和該目錄下的 ./tests/bre.tests 文件。

而這裡的 find . -path \\*test\\* -prune 只匹配到 ./tests 目錄,沒有進入該目錄下查找文件,就是受到了 -prune 表達式的影響。

基於前面的說明,find . -path \\*test\\* -prune 相當於 find . -path \\*test\\* -and -prune -and print。

對於不匹配 \\*test\\* 模式的文件名,-path \\*test\\* 表達式返回 false,不往下處理,不打印不匹配的文件名。

對於匹配 \\*test\\* 模式的文件名,-path \\*test\\* 表達式返回 true,會往下處理,遇到 -prune 表達式。該表達式總是返回 true,繼續往下處理 -print 表達式,打印出該目錄名。

由於 -prune 表達式指定不進入對應的目錄,所以沒有查找該目錄下的文件,沒有查找到 ./tests/bre.tests 文件。

-o

這是一個 operator 操作符,GNU find 在線幫助手冊對該操作符的說明如下:

expr1 -o expr2

expr1 -or expr2

Or; expr2 is not evaluated if expr1 is true.

使用 -o 操作符組合的表達式要求兩個表達式都是 false,該組合表達式才是 false。

左邊的 expr1 表達式為 true 時,不再評估右邊的 expr2 表達式,該組合表達式會返回 true。

前面提到, find . -path tests 命令什麼都沒有打印,跟使用了 -and 操作符有關,如果改成 -o 操作符,結果就會不一樣。

具體舉例如下:

<code>$ find . -path tests -o -print
.
./src
./src/main.c
./tests
./tests/bre.tests
./Makefile.am
$ find . -path ./tests -o -print
.
./src
./src/main.c
./tests/bre.tests
./Makefile.am
/<code>

可以看到,find . -path tests -o -print 命令打印了當前目錄下的所有文件名。

由於 -path tests 什麼都匹配不到,都返回 false,基於 -o 操作符的特性,全都執行後面的 -print 表達式,打印所有文件名。

這個結果跟 find . -path tests 命令完全相反。

類似的,find . -path ./tests -o -print 命令的打印結果跟 find . -path ./tests 命令也相反。

前者的打印結果不包含 ./tests 目錄名,後者的打印結果只包含 ./tests 目錄名。

對於匹配 -path ./tests 模式的目錄名,該表達式返回 true,基於 -o 操作符的特性,不往下執行 -print 表達式,所以不打印該目錄名。

-print

這是一個 action 類型表達式,GNU find 在線幫助手冊對該表達式的說明如下:

Action: -print

True; print the entire file name on the standard output, followed by a newline.

前面例子已經說明過 -print 表達式的作用,它會打印傳遞下來的完整文件路徑名,會自動添加換行符。

如果沒有提供除了 -prune 之外的其他 action 類型表達式,find 默認會加上 -print 表達式,並用 -and 來連接前面的表達式。

這個行為可能會帶來一些誤解,認為 find 命令總是會打印搜索到、或者匹配到的文件名,但有時候搜索到、或者匹配到的文件名反而不打印。

例如上面 find . -path ./tests -o -print 的例子。

要消除這個誤解,就一定要清楚地認識到,find 命令想要打印文件名,就必須執行到 -print 表達式、或者其他可以打印文件名的表達式。

即,要執行可以打印文件名的表達式才會打印文件名,否則不會打印。

至於是匹配特定模式的文件名會打印,還是不匹配特定模式的文件名才會打印,取決於各個表達式、包括操作符組合表達式的判斷結果,看是否會執行到可以打印文件名的表達式。

總結

結合上面的說明,對 find . -path ./tests -and -prune -o -print 命令在查找時能夠忽略 ./tests 目錄底下文件的原因總結如下:

  • find . 指定查找當前目錄、及其子目錄下的所有文件,每查找到一個文件名,就把這個文件名傳遞到後面的表達式進行評估,進行相應處理。
  • -path ./tests 指定傳遞下來的文件名要完全匹配 ./tests 這個字符串。對於不匹配的文件名,該表達式返回 false,那麼 -path ./tests -and -prune 這個組合表達式會返回 false,且沒有評估後面的 -prune 表達式。由於 -and 操作符優先級高於 -o 操作符,該組合表達式再跟後面的 -o -print 形成新的組合表達式,它返回 false,會往下執行 -print 表達式,從而打印出來不匹配的文件名。
  • 對於匹配 -path ./tests 模式的目錄名,該表達式返回 true,-path ./tests -and -prune 組合表達式會評估後面的 -prune 表達式,指定不進入匹配的目錄名查找底下的文件,這個例子裡面就是不進入 ./tests 目錄,所以查找時會忽略該目錄底下的文件,但還是會查找到 ./tests 目錄名自身。
  • 對於 ./tests 這個目錄名,由於 -prune 返回 true,-path ./tests -and -prune 組合表達式會返回 true,基於 -o 操作符的特性,不執行後面的 -print 表達式,所以沒有打印這個目錄名。
  • 最後的 -o -print 是必要的,如果不加這兩個參數,將不會打印不匹配 ./tests 模式的文件名。
  • 基於這幾條分析,這個命令最終打印的結果裡面,即不包含 ./tests 這個目錄名,也不包含它底下的文件名。

總的來說,使用 find 命令查找時,如果要忽略一個目錄,可以用類似 find . -path ./tests -prune -o -print 這樣的寫法。

理解了上面對該命令的說明後,想要忽略其他模式的目錄,應該就比較容易了。

忽略多個目錄的寫法

如果想要忽略多個目錄,要使用 -o 操作符把多個 -path pattern 表達式組合起來。

基於上面例子的目錄結構,舉例如下:

<code>$ find . \\( -path ./tests -o -path ./src \\) -prune -o -print
.
./Makefile.am
/<code>

可以看到,find . \\( -path ./tests -o -path ./src \\) -prune -o -print 命令打印的查找結果裡面,沒有 ./src、./tests 這兩個目錄、及其底下文件,也就是忽略了這兩個目錄。

基於 -o 操作符的特性,-path ./tests -o -path ./src 組合表達式在不匹配 ./tests 模式時,會再嘗試匹配 ./src 模式,兩個模式都不匹配,才會返回 false。

由於 -and 操作符優先級高於 -o 操作符,所以要用小括號 () 把 -path ./tests -o -path ./src 組合表達式括起來,形成一個獨立的表達式,再跟後面的 -prune 組合成新的表達式。

小括號在 bash 中有特殊含義,所以要加 \\ 轉義字符,寫成 \\(,避免 bash 對小括號進行特殊處理。

注意:在 \\( 和 \\) 前後要用空格隔開,這兩個是單獨的操作符,如果不加空格,會組合成其他名稱。

其他表達式的含義和作用可以參考前面例子的說明。

如果能夠基於這個命令的各個表達式、各個操作符的作用,推導出打印結果,就基本理解 find 命令的工作原理了。

匹配特定模式的文件名

上面說明的 -path pattern 表達式要求完全匹配整個目錄路徑,如果想要只匹配文件名,不包含目錄路徑部分,可以使用 -name pattern 表達式。

這是一個 test 類型表達式,GNU find 在線幫助手冊對該表達式的說明如下:

Test: -name pattern

True if the base of the file name (the path with the leading directories removed) matches shell pattern pattern. As an example, to find Texinfo source files in /usr/local/doc:

find /usr/local/doc -name '*.texi'

Notice that the wildcard must be enclosed in quotes in order to protect it from expansion by the shell.

如這個幫助說明所舉的例子,一般常用這個表達式來匹配特定後綴名的文件。具體舉例如下。

匹配單個後綴名

下面是匹配單個後綴名的例子:

<code>$ find . -name '*.c'
./src/main.c
/<code>

可以看到,find . -name '*.c' 命令打印出所有後綴名為 .c 的文件名。

注意 *.c 要用引號括起來,避免 bash 當 * 號當成通配符處理。

該命令相當於 find . -name '*.c' -and -print,只有 -name '*.c' 表達式返回為 true 的文件名才會執行到 -print 表達式,打印出該文件名。

注意:使用 -name pattern 表達式並不表示只查找符合 pattern 模式的文件,find 命令還是會查找出所給目錄的所有文件,並把每個文件名依次傳給後面的表達式進行評估,只有符合 -name pattern 表達式的文件名才會返回 true,才會被打印出來。

不符合這個表達式的文件也會被查找到,只是沒有打印出來而已。

匹配多個後綴名

下面是匹配多個後綴名的例子:

<code>$ find . -name '*.c' -o -name '*.am'
./src/main.c
./Makefile.am
$ find . \\( -name '*.c' -o -name '*.am' \\) -and -print
./src/main.c
./Makefile.am
$ find . -name '*.c' -o -name '*.am' -and -print
./Makefile.am
/<code>

可以看到,find . -name '*.c' -o -name '*.am' 命令打印出所有後綴名為 .c 和 .am 的文件名。

該命令相當於 find . \\( -name '*.c' -o -name '*.am' \\) -and -print,而不是相當於 find . -name '*.c' -o -name '*.am' -and -print,後者只能打印出後綴名為 .am 的文件名。

前面也有說明,find 命令會對所有返回為 true 的文件名默認執行 -print 表達式,這個返回為 true 是手動提供的整個表達式的判斷結果。

也就是說手動提供的整個表達式應該會用小括號括起來,組成獨立的表達式,再跟默認添加的 -and -print 表達式組合成新的表達式,避免直接加上 -and -print 後,會受到操作符優先級的影響,打印結果可能不符合預期。

重新格式化要打印的文件名信息

除了使用 -print 表達式打印文件名之外,也可以使用 -printf 表達式格式化要打印的文件名信息。

這是一個 action 類型表達式,GNU find 在線幫助手冊對該表達式的說明如下:

Action: -printf format

True; print format on the standard output, interpreting ‘\\’ escapes and ‘%’ directives.

Field widths and precisions can be specified as with the printf C function.

Unlike ‘-print’, ‘-printf’ does not add a newline at the end of the string. If you want a newline at the end of the string, add a ‘\\n’.

即,-printf 表達式使用類似於C語言 printf 函數的寫法來格式化要打印的信息,支持的一些格式如下:

  • %pFile’s name.這個格式包含完整路徑的文件名。
  • %fFile’s name with any leading directories removed (only the last element).這個格式只包含文件名,會去掉目錄路徑部分。

注意:-printf 是 action 類型表達式,前面提到,如果提供除了 -prune 之外的 action 類型表達式,將不會自動添加 -print 表達式。

加了 -printf 表達式將由該表達式來決定打印的文件信息。

使用 -printf 表達式的例子如下:

<code>$ find . \\( -name '*.c' -o -name '*.am' \\) -printf "%f  \\t%p\\n"
main.c ./src/main.c
Makefile.am ./Makefile.am
/<code>

可以看到,所給 find 命令打印出指定後綴的文件名本身、以及完整路徑的文件名。

-name '*.c' -o -name '*.am' 表達式需要用小括號括起來,組成獨立的表達式。

如果不加小括號,由於 -and 操作符優先級高於 -o 操作符,-name '*.am' 實際上是跟 -printf 表達式組合,後綴名為 .c 的文件名無法執行到 -printf 表達式,將不會打印這些文件名。

由於 -printf 表達式不會在末尾自動加上換行符,想要換行的話,需要在格式字符串裡面加上 ‘\\n’ 換行符。

使用正則表達式匹配完整路徑文件名

在 find 命令裡面,-path pattern 表達式和 -name pattern 表達式都是使用通配符來匹配模式,如果想要用正則表達式進行匹配,可以使用 -regex expr 表達式。

這是 test 類型表達式,GNU find 在線幫助手冊對該表達式的說明如下:

-regex expr

True if the entire file name matches regular expression expr. This is a match on the whole path, not a search.

As for ‘-path’, the candidate file name never ends with a slash, so regular expressions which only match something that ends in slash will always fail.

即,-regex expr 表達式用正則表達式匹配完整路徑的文件名,包含目錄路徑部分。

用正則表達式匹配後綴名為 .c 文件的例子如下:

<code>$ find . -regex '.*\\.c'
./src/main.c
$ find . -regex '.*c'
./src
./src/main.c
/<code>

可以看到,find . -regex '.*\\.c' 命令只打印出後綴名為 .c 的文件名。

而 find . -regex '.*c' 命令除了打印後綴名為 .c 的文件名,還打印了其他的文件名,這個命令的正則表達式不夠精確,少了關鍵的 \\. 來轉義匹配點號 . 字符。

在 .*\\.c 這個正則表達式中,最前面的 . 表示匹配任意單個字符,* 表示匹配零個或連續多個前面的字符,\\. 通過轉義來表示 . 字符自身,c 表示字符 c 自身,組合起來就是匹配後綴名為 .c 的字符串。

而 .*c 這個正則表達式匹配最後一個字符為 c 的字符串,不管在字符 c 前面是否有 . 字符,這個不符合後綴名的要求。

下面例子用正則表達式來匹配多個後綴名:

<code>$ find . -regex '.*\\.\\(c\\|am\\)'
./src/main.c
./Makefile.am
/<code>

這個例子同時匹配後綴名為 .c 和 .am 的文件名。

在正則表達式中,(a|b) 表示匹配 a 或者匹配 b。上面的 \\(c\\|am\\) 經過轉義後,也就是 (c|am),用於匹配 c 或者 am,這樣就比較好理解,不要被轉義字符嚇到了。

匹配特定類型的文件

在 Linux 中,文件類型可以分為目錄 (directory)、文本文件 (regular file)、符號鏈接 (symbolic link)、socket,等等。

find 命令可以用 -type c 表達式來指定匹配這些類型的文件。

這是一個 test 類型表達式,GNU find 在線幫助手冊對該表達式的說明如下:

Test: -type c

True if the file is of type c:

d: directory

f: regular file

l: symbolic link

s: socket

例如,使用 -type f 來指定只匹配文本文件:

<code>$ find . -type f
./src/main.c
./tests/bre.tests
./Makefile.am
/<code>

可以看到,在打印結果裡面,沒有看到目錄名,只有文本文件名。

注意:-type f 表達式只表示匹配文本文件,並不表示只查找文本文件,find 命令還是會查找出所給目錄的所有文件,並把每個文件名依次傳給後面的表達式進行評估,只有符合 -type f 表達式的文件才會返回 true,才會被打印出來。

不符合這個表達式的文件也會被查找到,只是沒有打印出來而已。


分享到:


相關文章: