### 前言 在項目初創階段,經常會遇到各種文件操作,拷貝頭文件,庫,批量重命名等。文件結構一複雜,這就將是個無聊的工作。 ### 查找文件 `find`可以在目錄結構中搜索文件,這是它在`man`裡面的作用描述。那麼怎麼搜索呢?有多種方式,按文件時間,大小,按文件名,路徑名,按文件類型,許可權,按用 ...
前言
在項目初創階段,經常會遇到各種文件操作,拷貝頭文件,庫,批量重命名等。文件結構一複雜,這就將是個無聊的工作。
查找文件
find
可以在目錄結構中搜索文件,這是它在man
裡面的作用描述。那麼怎麼搜索呢?有多種方式,按文件時間,大小,按文件名,路徑名,按文件類型,許可權,按用戶。而這些方式又可以通過與或非的邏輯相互組合,完成更苛刻的查找工作,簡直是文件查找的福音。
通常介紹一種命令都會以命令形式開始,find
的格式如下
find [-H] [-L] [-P] [-D debugopts] [-Olevel] [starting-point...] [expression]
[-H] [-L] [-P] [-D debugopts] [-Olevel]這一些統統不重要,
[-H] [-L] [-P]是針對軟連接的,不常用。[-D debugopts]是顯示運行期間的額外信息,信息太亂太雜,用處不大。[-Olevel]則是用於優化查找的,預設的已經夠用,所以也沒必要深究。
find
的最大魔法在最後的[expression]
,下麵將以實例的形式講解這個[expression]
到底該怎麼玩,原始的文件結構如下
├── alice.h
├── andy
│ ├── jack
│ │ └── mary.h
│ ├── mark.cpp
│ ├── mark.h
│ └── pony.txt
├── andy.c
├── bill.cpp
├── bill.h
├── mark.h
└── mary
現在,我想找到以andy
命名的文件,命令該怎麼寫呢。直覺告訴我們應該是
find andy
但是直覺對嗎,我們來看輸出
andy
andy/mark.cpp
andy/jack
andy/jack/mary.h
andy/pony.txt
andy/mark.h
它只找到了andy
目錄,甚至都沒找到andy.c
,那麼看來我們需要一種方式告訴find
,我們找的東西是文件,不是目錄,這個選項就是-type
。
-type
後面需要緊跟一個參數,常用取值是d
代表目錄,f
代表文件。現在我們需要找到文件,那麼就應該加個-type f
的選項。但這就夠了嗎?執行命令會發現報錯了,因為後面的andy
被認為是路徑,而我們要找的是文件名啊。所以,這又需要另一個選項的幫忙,-name
。-name
後面可以跟具體的名字或者正則。結合這兩個條件,我們得出了最終的命令
find -type f -name "andy*"
這裡有兩點值得註意,首先-type
和-name
其實是兩個獨立選項,可以單獨使用,也可以聯合使用,當聯合使用時,他們之間不使用操作符(-o
(Or),-a
(AND),-not
)連接時,就會把-a
單做連接符,也就是所有的條件都滿足才回出現在最終的結果中。由此,可以延伸出一種反向的查找方法,
find -type f -not -name "andy*"
這個命令就會查找出所有不以andy
開頭的文件。
./andy/mark.cpp
./andy/jack/mary.h
./andy/pony.txt
./andy/mark.h
./bill.h
./bill.cpp
./mark.h
./alice.h
另一個值得註意的點是"andy*"
加了雙引號,因為*
是特殊字元,所以需要用雙引號轉椅一下,假如沒有特殊字元是不需要添加雙引號的。回到最初的命令,為啥第一條我們想當然的命令竟然沒有找到我們期盼的目標呢?因為find
是嚴格匹配的,我們只寫了andy
,而遺漏了尾碼.c
,這是最容易發生錯誤的地方。
其實,到這裡,我們已經學習了這個命令的50%,那剩下的有什麼內容呢?記得find
的主要功能嗎,裡面提到了個目錄結構。對的,find
還可以控制查找範圍。
新需求來了,怎樣找到某個目錄下所有的直接子.h
文件呢?這裡的直接和子合起來的意思是查找範圍只能是當前目錄,不能查找到當前目錄的子目錄。在解決這個問題前,我們需要知道一個前置知識——兩個目錄之間存在兩種相互關係,兄弟或者父子。兄弟目錄深度相同,父子目錄深度相差1。知道了這點,再來看需求——.h
文件很簡單,使用-name "*.h"
就能滿足。但是,這樣會找到andy
目錄下的.h
文件,所以我們需要一種控制目錄查找層級的東西,他們就是-mindepth
,maxdepth
。這兩個參數和前面的不一樣,他們屬於Global options
。什麼是Global options
呢,就是他們的作用是全局性的,並且它們總是返回true
,也就是它們和其他的選項連起來一起查找的時候,只用考慮其他選項的條件。並且,為了凸顯它們的全局性,它們在命令書寫時,必須寫在最前面,否則會觸發警告。如下,就是寫在了-name "*.h"
前面。
find -maxdepth 1 -name "*.h"
這兩個參數有點反直覺,可以這樣理解——最多查找到哪裡,有個最多,就是maxdepth
,反過來就是從哪裡才開始查找,才就是mindepth
。
聊完了目錄結構,名字這些顯眼的部分,文件還有訪問(access
)創建(create
)、修改(modiffy
)時間,許可權(permission
),大小(size
)這些沒有涉及到,而這些也同樣可以作為find
的查找條件,在開始之前,有一些小規則可以對這些選項做個快速分組——選項會以屬性的英文首字母作為開始,如
- 時間相關的選項有
time
和min
,分別表示某個事件發生的前n
天和n
分鐘,這裡的某個事件可以用訪問a
(access
)c
創建(create
)、m
修改(modiffy
)替代,它們組合起來就是一個完整的選項,如mmin n
就表示查找修改時間在n
分鐘以內的文件。 - 以此類推,
i
代表大小寫敏感,如iname
,l
代表link
文件
當然,這些都可能用得不多,實際使用到再查可能還更方便點,但是有兩個很好用的選項不得不講。
考慮以下情況,某天老本發來一堆用戶日誌文件,讓你給這些用戶根據使用頻次分級,你該怎麼辦呢?首先,我們可以把日誌文件大小作為依據,根據最大最小劃分好區間,如(0-100M),然後用定好的級別(如5級)劃分出每級區間(0-20,20-40,...),這樣我們多次運行命令,就能得到所有的分級情況了。想法是美好的,但是find
提供了這種選項了嗎?它提供了-size n
。我們來試著查找一下0-20區間的文件
find -size 20M
回車,你會發現結果貌似不完全正確,它可能確實找出了一些滿足條件的文件,但是有一些滿足的卻沒有找出,問題出現在哪裡呢?原來-size n
中的n
是嚴格匹配,就是你輸入的是20M,它就只找到恰好是20M的文件,而不是我們期望的20M和20M一下的文件。那麼有解決方法嗎,當然有,就是數字前面加上+
,-
號,+
代表大於等於這個值,-
代表小於。所以我們查找20M以下的文件的命令應該是
find -size -20M
解決了符號問題,還有單位問題值得註意,也就是-20M
中的M
。其實-size
的標準形式是-size [+-]n[cwbkMG]
。[+-]
和n
都說過了,後面的則單位。它們是以大小遞增順序排列的,說明如下
c
:位元組w
:雙位元組,也就是word
b
:512位元組構成的塊,數字n
後面沒加單位的話,這個是預設值k
:1024個位元組,也就是kbM
:1024 * 1024個位元組,也就是Mbc
:1024 * 1024 * 1024個位元組,也就是Gb
說完了單位的事,我們接著來出來20——40的分級,直接把20改為40嗎?當然不,改為40找到的就是小於等於40M的文件了,所以我們需要一種區間標定法,find
沒有提供直接的選項支持,但是前面說過,選項是可以組合的,也就是我們可以重覆使用-size
來標識一個區間。也就是
find -size +20M -size -40M
按照這種方法,多次改變值,就可以完成任務啦。
其實上面的方案還有一點小紕漏,就是沒有找出直接沒用過的用戶,也就是size為0的,那把數字改為0,可以嗎?答案是可以,但是假如我們想找的是空目錄,而不是空文件呢,-size
就不能解決了,因為通常空目錄的大小不為0.所以,find
又提供了個檢測文件是否為空的選項-empty
,它不僅可以找到空文件,還可以找到空目錄。在我們的實例中,使用 find -size 0
找到的結果是
./andy/mark.cpp
./andy/jack/mary.h
./andy/pony.txt
./andy/mark.h
./andy.c
./alice.h
沒有找出空目錄mary
。而使用find -empty
查找後,結果是
./mary
./andy/mark.cpp
./andy/jack/mary.h
./andy/pony.txt
./andy/mark.h
./andy.c
./alice.h
不僅找到了mary
空目錄,其他的空文件也找出來了。
至此,find
相關的東西已經瞭解得差不多了。但是,很多時候僅僅找到並不能完全滿足我們的需求,我們可能需要把找到的文件複製到其他地方或者刪除之類的,能否把這些操作合起來呢?這就要請我們的xargs
登場了。
xargs
xargs
只有一個簡單的功能,就是從標準輸入讀入內容,構建並執行命令。怎麼理解呢?假設我們在執行find
命令,find
命令執行肯定是有過程,有邏輯的。按照一定的邏輯和過程,find
對文件進行逐一評估,假如滿足條件,就輸出結果。隨著命令的執行,結果可能越來越多。假如我們需要對產生的每個結果都執行一條命令呢,這該怎麼辦?按照一般的思路,當然是將結果保存起來,然後再寫個腳本,讀取每一條記錄,然後執行相應。但是有了xargs
,我們不用這麼麻煩了,可以一步到位。我們利用管道符將結果從終端連接到xargs
中,xargs
接收到一條信息,就會將它作為構建命令的參數,就好像我們手動輸入了命令那樣,構建完成後還會自動執行。最終的結果就是,沒產生一個輸出,就會產生一條以這個輸出為參數的命令,並且這條命令還自動執行,最終的效果就是實現了一條命令實現了多個功能。
將find
和xargs
結合起來
現在我們挑戰升級了,有個需求,需要提取目錄下的所有頭文件到另一個目錄。這個需求可以分為兩部分,一部分是找到頭文件,這可以用find
命令完成。另一部分是複製找到的頭文件,這就需要xargs的參與了。
首先是找到頭文件。頭文件就是以.h
結尾的文件(暫不考慮.hpp
),這個尾碼是出現在名字里的,所以我們可以使用-name "*。h"
選項,同時為了避免某些目錄名的干擾,我們把類型也做個限定-type f
,只查找文件。這第一步就完成了。
第二布就是複製文件啦。複製文件的標準寫法是
cp [OPTION]... SOURCE... DIRECTORY
根據這個命令格式,我們需要確定幾個參數,源文件當然就是我們查找到的文件啦,這暫時按下不表。目標文件夾,就是我們拷貝到哪裡,我們這裡暫時就新建一個test
的目錄吧,目標文件夾就是test
。這就結束了嗎,還沒有。頭文件往往需要和他的父目錄組成一個依賴路徑,所以我們把所有的頭文件直接一股腦都拷貝到test
目錄下是不可取的,這會打亂頭文件的依賴關係,我們還得拷貝和頭文件相關的父目錄。恰巧的是,cp
提供了這樣的一個選項-parents
——它可以拷貝源文件的完成文件名,也就是包含目錄。所以問題的關鍵就來到了源文件這個參數上了。