Linux 檔案、內容查詢(遞迴) ,grep ,find

chaiqi發表於2007-03-05

內容查詢(遞迴)

grep /etc/httpd/modules/ -lr '51ditu' *

檔名查詢(遞迴,不適合查詢modules)

find /etc/httpd/ -name httpd.conf 

 

引用:

grep 命令用於搜尋由 Pattern 引數指定的模式,並將每個匹配的行寫入標準輸出中。這些模式是具有限定的正規表示式,它們使用 ed egrep 命令樣式。grep 命令使用壓縮的不確定演算法。

如果在 File 引數中指定了多個名稱,grep 命令將顯示包含匹配行的檔案的名稱。對 shell 有特殊含義的字元 ($, *, [, |, ^, (, ), / ) 出現在 Pattern 引數中時必須帶雙引號。如果 Pattern 引數不是簡單字串,通常必須用單引號將整個模式括起來。在諸如 [a-z] 之類的表示式中,-(減號)cml 可根據當前正在整理的序列來指定一個範圍。整理順序可以定義等價的類以供在字元範圍中使用。如果未指定任何檔案,grep 會假定為標準輸入。

 

注:
  1. 請勿對特殊檔案執行 grep 命令,這樣做可能產生不可預計的結果。
  2. 輸入行不應包含空字元。
  3. 輸入檔案應該以換行符結束。
  4. 換行符不會與正規表示式匹配。
  5. 雖然一些標誌可以同時被指定,但其中的某些標誌會覆蓋其他標誌。例如,-l 選項將優先於所有其他標誌。另外,如果您同時指定了 -E-F 標誌,則後指定的那個會有優先權。

標誌

 

-b 在每行之前新增找到該行時所在的塊編號。使用這個標誌有助於通過上下文來找到磁碟塊號碼。-b 標誌不能用於來自標準輸入和管道的輸入。
-c 僅顯示匹配行的計數。
-E 將每個指定模式視作擴充套件的正規表示式(ERE)。ERE 的空值將匹配所有的行。

 

注:帶有 -E 標誌的 grep 命令等價於 egrep 命令,只不過它們的錯誤和使用資訊不同以及 -s 標誌的作用不同。
-e PatternList 指定一個或多個搜尋模式。其作用相當於一個簡單模式,但在模式以 -(減號)開始的情況下,這將非常有用。模式之間應該用換行符分隔。連續使用兩個換行符或者在引號後加上換行符 ("/n) 可以指定空模式。除非同時指定了 -E-F 標誌,否則每個模式都將被視作基本正規表示式(BRE)。grep 可接受多個 -e-f 標誌。在匹配行時,所有指定的模式都將被使用,但評估的順序沒有指定。
-F 將每個指定的模式視作字串而不是正規表示式。空字串可匹配所有的行。

 

注: 帶有 -F 標誌的 grep 命令等價於 fgrep 命令,只不過它們的錯誤和使用資訊不同以及 -s 標誌具有不同的作用。
-f PatternFile 指定包含搜尋模式的檔案。模式之間應該用換行符加以分隔,空行將被認為是空模式。每種模式都將被視作基本的正規表示式(BRE),除非同時指定了 -E-F 標誌。
-h 禁止在匹配行後附加包含此行的檔案的名稱。當指定多個檔案時,將禁止檔名。
-H 如果指定了 -r-R 選項並且在命令列上指定了引用檔案型別目錄的符號連結,則 grep 將搜尋符號連結所引用的目錄檔案以及檔案層次結構中在它以下的所有檔案。
-i 在進行比較時忽略字母的大小寫。

 

-l 僅列出(一次)包含匹配行的檔案的名稱。檔名之間用換行符加以分隔。如果搜尋到標準輸入,將返回(標準輸入)的路徑名。-l 標誌同 -c-n 標誌的任意組合一起使用時,其作用類似於僅使用了 -l 標誌。
-L 如果指定了 -r-R 選項,並且引用檔案型別目錄的符號連結在命令列上指定或在檔案層次結構轉移過程中遇到,則 grep 將搜尋符號連結所引用的目錄檔案以及檔案層次結構中在它以下的所有檔案。如果同時指定了 -H-L,則命令列上最近指定的選項將生效。
-n 在每一行之前放置檔案中相關的行號。每個檔案的起始行號為 1,在處理每個檔案時,行計數器都將被複位。
-p[ Separator] 顯示包含匹配行的整個段落。段落之間將按照 Separator 引數指定的段落分隔符加以分隔,這些分隔符是與搜尋模式有著相同格式的模式。包含段落分隔符的行將僅用作分隔符,它們不會被包含在輸出中。預設的段落分隔符是空白行。
-q 禁止所有寫入到標準輸出的操作,不管是否為匹配行。如果選擇了輸入行,則以零狀態退出。-q 標誌同 -c-l-n 標誌的任意組合一起使用時,其作用類似於僅使用了 -q 標誌。
-r 遞迴地搜尋目錄。在預設情況下,按照到目錄的連結。
-r 遞迴地搜尋目錄。在預設情況下,不按照到目錄的連結。
-s 禁止通常因為檔案不存在或不可讀取而寫入的錯誤資訊。其他的錯誤資訊並未被禁止。
-v 顯示所有與指定模式不匹配的行。
-w 執行單詞搜尋。
-x 顯示與指定模式精確匹配而不含其他字元的行。
-y 當進行比較時忽略字元的大小寫。
PatternList 指定將在搜尋中使用的一個或多個模式。這些模式將被視作如同是使用 -e 標誌指定的。
File 指定將對其進行模式搜尋的檔案的名稱。如果未給出 File 變數,將使用標準輸入

 

find與grep命令簡介及正規表示式

  兩個更為有用的命令和正規表示式

  在我們開始學習新的Shell程式設計知識之前,我們先來看一下兩個更為有用的兩個命令,這兩個命令雖然並不是Shell的一部分,但是在進行Shell程式設計時卻會經常用到.隨後我們會來看一下正規表示式.

find命令

  我們先來看的是find命令.這個命令對於我們用來查詢檔案時是相當有用的,但是對於Linux新手來說卻有一些難於使用,在一定程式是由於他所帶的選項,測試,動作型別引數,而且一個引數的執行結果會影響接下來的引數.
在我們深入這些選項和引數之前,我們先來看一個非常簡單的例子.假如在我們的機子上有一個檔案wish.我們來進行這個操作時要以root身份來執行,這樣就可以保證我們可以搜尋整個機子:

# find / -name wish -print
/usr/bin/wish
#

  正如我們可以想到的,他會列印出搜尋到的結果.很簡單,是不是?
然而,他卻需要一定的時間來執行,因為他也會同時搜尋網路上的Window機器上的磁碟.Linux機器會掛載大塊的Window機器的檔案系統.他也會同時那些位置,雖然我們知道我們要查詢的檔案位於Linux機器上.
這也正是第一個選項的用武之地.如果我們指定了-mount選項,我們就可以告訴find命令不要搜尋掛載的目錄.

# find / -mount -name wish -print
/usr/bin/wish
#

這樣我們仍然可以搜尋這個檔案,但是這一次並沒有搜尋掛載的檔案系統.

find命令的完整語法如下:

find [path] [options] [tests] [actions]

path是一個很簡單的部分:我們可以使用絕對路徑,例如/bin,或者是使用相對路徑,例如.. .如果我們需要我們還可以指定多個路徑,例如 find /var /home

主要的一些選項如下:
-depth    在檢視目錄本身以前要先搜尋目錄中的內容
-follow    跟隨符號連結
-maxdepths N    在搜尋一個目錄時至多搜尋N層
-mount(或-xdev)    不要搜尋其他的檔案系統

  下面的是一些test的選項.我們可以為find命令指定大量的測試,並且每一個測試會返回真或是假.當find命令工作時,他會考查順序查詢到的檔案,並且會在這個檔案上按順序進行他們所定義的測試.如果一個測試返回假,find命令會停止他當前正在考查的檔案並繼續進行下面的動作.我們在下表中列出的只是一些我們最常用到的測試,我們可以通過檢視手冊頁得到我們可以利用find命令使用的可能的擴充套件列表項.

-atime N    N天以前訪問的檔案
-mtime N    N天以前修改的檔案
-name pattern    除了路徑,與指定的型別匹配的檔名.為了保證指定的型別傳遞給find命令而並不是立即被Shell賦值,指定的型別必須用引號進行引用.
-newer otherfile    與otherfile檔案相比要新的檔案
-type C        C型別的檔案,而這裡的C可以指定的一種型別.最常用的是d代表目錄,而f是指普通的檔案.對於其他的檔案型別,我們可以檢視手冊頁.
-user username    指定的使用者所擁有的檔案
我們也可以使用運算子進行測試的組合.大多數的有兩種格式:短格式和長格式.
!    -not    測試的反
-a    -and    所有的測試必須為真
-o    -or    測試中某一個為真

  我們可以使用括號來強行改變測試和運算子的次序.因為這些對於Shell來說有著特殊的意義,所以我們也需要使用反斜線將他們作為一個整體進行引用.另外, 如果我們為檔名指定了匹配型別,我們也必須用引號進行引用,這樣就可以避免他們被Shell進行擴充套件,從而可以將他們直接傳遞給find命令.所以如果我們要寫一個這樣的測試,要查詢比X檔案要近或者是以一個範圍開頭的檔案,我們要寫成下面的形式:

/(-newer X -o -name “_*” /)

現在我們要試著在當前的目錄下查詢最近修改日期比while2更近的檔案,我們可以用下面的命令:

$ find . -newer while2 -print
.
./elif3
./words.txt
./words2.txt
./_trap
$

  我們在上面所用的命令看起來似乎不錯,但是我們卻同時也搜尋了當前的目錄檔案,而這並不是我們所希望的,我們所感興趣只是常規檔案.所以我們可以加上另外一個測試-type f:

$ find . -newer while2 -type f -print
./elif3
./words.txt
./words2.txt
./_trap
$

工作原理:

  這些命令是如何進行工作的呢?我們指定find命令應該在當前的目錄下進行查詢(.),而我們所要查詢的是比while2更新的檔案(-newer while2),而且如果已經傳遞了測試,還要測試這個檔案是否為一個常規檔案(-type -f).最後,我們使用我們以前用過的動作,-print,僅僅是來驗證我們所找到的檔案.

  下面我們要查詢的檔案或者是以下劃線開頭的或者是要比while2檔案新的檔案,但是也必須為一個常規檔案.這個例子可以向我們展示如何來進行測試的組合:

$ find . /( -name “_*” -or -newer while2 /) -type f -print
./elif3
./words.txt
./words2.txt
./_break
./_if
./_set
./_shift
./_trap
./_unset
./_until
$

  這時我們可以看到這並不是一件很難的事情,不是這樣嗎?我們必須轉義圓括號,這樣他就不會被Shell所保護,同時用引號引用*,這樣他就可以直接傳遞給find命令了.

  既然我們現在能夠可靠的查詢檔案,下面我們就來看一下當我們查詢指定的檔案時我們可以進行的一些協作.我們要再一次強調,我們在這裡所列出的只是一些最常用的選項,我們可以檢視手冊頁得到全部的集合.

-exec command    執行一個命令.這是我們最常執行的動作.
-ok command    與-exec相類似,所不同的只是他會提示使用者在執行將要執行的命令之前進行命令的確認.
-print        列印出檔名
-ls        使用ls命令列出當前的檔案
-exec和-ok命令會同一行的引數子序列作為他的引數的一部分,直到遇到一個終結符/;序列.對於-exec和-ok來說字串{}是珍上特殊的型別,而且會為當前檔案的絕對路徑所替換.

這樣的解釋也許並不是太認人容易理解,但是一個例子也許可以很好的來說明這些.

如下面的一個簡單的例子:

$ find . -newer while2 -type f -exec ls -l {} /;
-rwxr-xr-x    1 rick     rick          275 Feb 8 17:07 ./elif3
-rwxr-xr-x    1 rick     rick          336 Feb 8 16:52 ./words.txt
-rwxr-xr-x    1 rick     rick         1274 Feb 8 16:52 ./words2.txt
-rwxr-xr-x    1 rick     rick          504 Feb 8 18:43 ./_trap
$

  正如我們現在所看到的,find命令是相當有用的.要用好這個命令只需要一些簡單的練習.然而這樣的練習也許要付一定的代價,所以我們應做一些find命令的實驗.

grep命令

  我們將要看到的第二個非常有用的命令為grep命令,這是一個並不常見的名字,他是通用正規表示式解析器的簡稱(General Regular Expression Parser).我們使用find命令在我們的系統是查詢所需的檔案,但是我們卻要使用grep命令在檔案中查詢指定的字串.而事實上,最常用的做法就是當我們在使用find命令時將grep作為一個命令傳遞給-exec.

grep命令可以帶選項,匹配的模式以及我們要在其中查詢的檔案:
grep [options] PATTERN [FILES]
如果並沒有指定檔名,他就會搜尋標準輸入.
讓我們從grep命令的主要的選項開始.我們在這裡列出的只是一些主要的選項,我們可以從手冊中得到更為詳細的內容說明.

-c    列印出匹配行的總數,而不是列印出匹配的行
-E    開啟擴充套件表示式
-h    禁止將在其中查詢到匹配內容的檔名作為輸出行的字首
-i    忽略大小寫
-l    列出帶用匹配行的檔名,而不是輸出實際的匹配行
-v    將匹配型別轉換為選擇不匹配的行而不是匹配的行
如下面的一些例子:
$ grep in words.txt
When shall we three meet again. In thunder, lightning, or in rain?
I come, Graymalkin!
$ grep -c in words.txt words2.txt
words.txt:2
words2.txt:14
$ grep -c -v in words.txt words2.txt
words.txt:9
words2.txt:16
$

工作原理:

  第一個例子中並沒有指定選項,grep命令只是簡單在的words.txt檔案中查詢字串in,並且列印出所匹配的行.在這裡並沒有列印出檔名,這是因為在這裡我們只是使用了一個檔案.
在第二個例子中列印出在兩個不同的中匹配行的總數,在這種情況就要列印出檔名.
在最後的一個例子中我們使用了-v選項來轉換查詢的條件並且列印出在兩個檔案中不匹配的總行數.

正規表示式

  正是我們所看到的,grep命令的基本用法是比較容易掌握的.現在我們要來看一下基本的正規表示式,這會允許我們做一些更為複雜的匹配.正如我們在前面所提到的,正規表示式是用在Linux或是共他的一些開源中的語言.我們可以在vi或是在編寫Perl指令碼時使用.

在正規表示式的使用過程中,一些字元會被以不同的方式進行處理.最常見的一些用法如下:

^    在一行的開頭
$    在一行的結尾
.    任意一個單一字元
[]    方括號中所包含是字母的範圍,其中的任何一個都可以進行匹配,例如a-e的字母範圍,或者是我們可以使用^來進行反義.
如果我們要將他們作為普通的字元來使用就要在這些字元前面加上/.所以如果我們要查詢一個$字元,我們就要使用/$來進行查詢.

下面的是一些可以在方括號中使用的比較有用的特殊匹配:

[:alnum:]    字母數字字元
[:alpha:]    字母
[:ascii:]    ASCII字元
[:blank:]    空格或是Tab
[:cntrl:]    ASCII碼控制字元
[:digit:]    數字
[:graph:]    非控制,非空格字元
[:lower:]    小寫字母
[:print:]    可列印字元
[:punct:]    標點字元
[:space:]    空白字元,包括垂直Tab
[:upper:]    大寫字元
[:xdigit:]    十六進位制數字

  另外,如果同時使用-E選項指定了擴充套件匹配,在正規表示式的後面也許會跟一些其他的控制匹配型別組合的字元.如果我們只是想將他們作為普通的字元進行使用,我們也要在其前面加上轉義符/.

?    可選的匹配,但是最多匹配一次
*    必須匹配0個或是多個專案
+    必須匹配1個或是多個專案
{n}    必須匹配n次
{n,}    必須匹配n次或是更多次
{n,m}    匹配範圍為n次到m次,包括m次

  這些內容看起來有一些複雜,但是如果我們循序漸進,我們就會發現事實上這些內容並不如我們在第一眼看到時那樣的複雜.最簡單的掌握正規表示式的方法就是簡單的試一些例子:

如果我們要查詢以字元e結尾的行我們可以用下面的命令:

$ grep e$ words2.txt
Art thou not, fatal vision, sensible
I see thee yet, in form as palpable
Nature seems dead, and wicked dreams abuse
$

  正如我們所看到的,這個命令會搜尋出以e結尾的匹配行.
現在假設我們要查詢以字母a結尾的單詞.要達到這個目的,我們在方括號中使用特殊的匹配.在這樣的情況下,我們要使用[[:blank:]],這會測試一個空格或是一個Tab:

$ grep a[[:blank:]] words2.txt
Is this a dagger which I see before me,
A dagger of the mind, a false creation,
Moves like a ghost. Thou sure and firm-set earth,
$

  現在假設我們要查詢一個以Th開頭的三個字母的單詞.在這種情況下,我們需要同時使用[[:space:]]來決定一個單詞的結尾並使用.來匹配另外的一個字母:

$ grep Th.[[:space:]] words2.txt
The handle toward my hand? Come, let me clutch thee.
The curtain’d sleep; witchcraft celebrates
Thy very stones prate of my whereabout,
$

  最後我們要使用擴充套件的grep命令來查詢10個字元長的小寫字母的單詞.在這裡我們要指定一個字元的範圍的來匹配a到z,同時指定字元的10次重複:

$ grep -E [a-z]/{10/} words2.txt
Proceeding from the heat-oppressed brain?
And such an instrument I was to use.
The curtain’d sleep; witchcraft celebrates
Thy very stones prate of my whereabout,
$

  我們在這裡只是接觸正規表示式一些相對來說更為重要的一部分.正如在Linux中的其他的大多數的內容,在這之外會許多的文件來幫助我們要發現更為詳細的內容,但是學習正規表示式的最好的方法就是要實驗這些表示式.

命令執行:

  當我們編寫指令碼時,我們常常需要在Shell指令碼中取得命令執行結果的結果來使用.也就說我們需要執行一個命令並將這個命令的輸出結果放在一個變數中.這時我們可以使用我們在前面的set命令的例子中所介紹的$(command)語法.這也是一個相對較老的格式,而最常使用的用法是`command`格式.

  所有新的指令碼應使用$(...)的格式,這可以用來避免一些相當複雜的在反引號命令中使用$,`,/所造成的轉換規則.如果在`...`結構中使用了反引號,我們就需要使用/進行轉義.這些相對模糊的字元會使得程式感到迷惑,有時甚至是一些經驗豐富的程式也不得不進行一些試驗以使得在反引號命令中的引號可以正確的進行工作.

$(command)命令的結果只是簡單的命令的輸出.在這裡我們要注意的是這並不是這個命令的返回狀態,而是輸出的字串.如下面的例子:

#!/bin/sh
echo The current directory is $PWD
echo The current users are $(who)
exit 0

  因為當前的目錄是一個Shell環境變數,所以第一行並不需要使用這種命令執行結構.然而,who命令的執行結果,如果希望他在這個指令碼中可見,我們就要使用這種命令結構.

如果我們希望將他們的結果放在一個變數中,我們可以像平常一樣將他們賦值給一個變數:

whoisthere=$(who)
echo $whoisthere

  將一個命令的執行結果放在一個指令碼變數中的能力是相當強大的,因為這樣就可以很容易的在指令碼中使用現在的命令並取得他們的輸出.如果你發現在你正在試著轉換一個標準命令在標準輸出上的輸出結果的引數集合並將他們作為一個程式的引數,你就會發現命令xargs會幫助你完成這一切.可以檢視手冊頁得到更深更詳細的內容.
有時會出現的一個問題就是我們要呼叫的命令會在我們所希望的文字出現之前輸出了一些空白符,或者是比我們所希望的更多的內容.在這樣的情況下,我們可以使用我們在前面所說到的set命令.

算術擴充套件

  我們已經使用了expr命令,這可以允許處理簡單的算術命令,但是他的執行是相當的慢的,因為在處理expr命令時需要呼叫一個新的Shell.

  一個新的更好的替換就是$((...))擴充套件.通過將我們所希望的表示式包在括號裡以便在$((...))中進行賦值,我們可以進行更為有效的簡單算術.

如下面的例子:

#!/bin/sh
x=0
while [ “$x” -ne 10 ]; do
    echo $x
    x=$(($x+1))
done
exit 0

引數擴充套件

我們在前面已經看到了引數分配與擴充套件的最簡單形式,在那裡我們是這樣寫的:

foo=fred
echo $foo

  當我們要在一個變數的結尾處加上另外的一個字元時卻會發生問題.假設我們要寫一個簡短的指令碼來處理名為1_tmp和2_tmp的檔案,我們可以試著用下面的指令碼來處理:

#!/bin/sh
for i in 1 2
do
    my_secret_process $i_tmp
done

但是在每一個迴圈中,我們會得到下面的資訊:

my_secret_process: too few arguments

發生了什麼錯誤呢?

  問題就在於Shell會試著將變數$i_tmp用他的變數值進行替換,但是卻並不存在這個變數.而Shell並不會認為這是一個錯誤,而只是用空值來進行替換,所以並沒有引數傳遞給my_secret_process.要將$i的擴充套件保護為變數的一部分,我們需要將i放在一對花括號中:

#!/bin/sh
for i in 1 2
do
    my_secret_process ${i}_tmp
done

  這樣以後在第一個迴圈中,i的值會用${i}進行替換,從而給出一個實際的檔名.這樣我們就已經將一個引數的值替換為一個字串了.

  我們可以在Shell中進行許多的替換.常常這樣的方法會為引數的處理問題提供一個優雅的解決方法.

常用到的一些如下表:

${parm:-default}    如果一個引數為空,則將他設定為一個預設值.
${#parm}        給出引數的長度.
${parm%word}        從末尾開始,移除與word相匹配的最小部分並返回其餘的部分.
${parm%%word}        從末尾開始,移除與word相匹配的最長部分並返回其餘的部分.
${parm#word}        從開頭開始,移除與word相匹配的最小部分並返回其餘的部分.
${parm##word}        從開頭開始,移除與word相匹配的最長部分並返回其餘的部分.

  這些替換對於我們要處理字串來說是相當有用的.而最後的四個可以用來移除字串中的部分內容,而這對於處理檔名和路徑是更為有用的.如下面的一些例子中所示的:

#!/bin/sh
unset foo
echo ${foo:-bar}
foo=fud
echo ${foo:-bar}
foo=/usr/bin/X11/startx
echo ${foo#*/}
echo ${foo##*/}
bar=/usr/local/etc/local/networks
echo ${bar%local*}
echo ${bar%%local*}
exit 0

如果我們執行這個指令碼我們會得到下面的輸出結果:

bar
fud
usr/bin/X11/startx
startx
/usr/local/etc
/usr

工作原理:

  第一個句子,${foo:-bar},會為foo的值指定為bar,因為當這個語句開始執行時並沒有為foo指定任何值.foo的值會保持不變直到他遇到unset語句.

在這裡我們有一些需要我們注意的內容:

${foo:=bar}將會設定變數$foo.這個字串運算子會檢測foo存在並且不為空值.如果他不為空,則會返回他的值,但是如果是相反的情況,就會將foo的值設為bar並且會返回替換的結果值.
${foo:?bar}會列印出foo: bar,而如果foo並不存在或是他被設為空值則會退出命令.
最後,${foo:+bar},如果foo存在並且不為空則會返回bar.
{foo#*/}語句進行匹配並且只是移除左面的內容(在這裡我們要記住*匹配0個或是多個字元).{foo##*/}進行匹配並會移除儘可能多的內容,所以他會移除了最右面的/以及他前面的所有字元.
{bar%local*}語句匹配從右面開始直到第一次出現local的字元,而{bar%%local*}會從右面開始匹配儘可能多的字元,直到第一次發現local.

  因為Unix和Linux都比較強的依賴於過濾的概念,所以我們常常要將一個操作的執行結果進行手工重定向.假設我們要使用cjpeg命令將一個GIF的檔案轉換為JPEG的檔案:

$ cjpeg image.gif > image.jpg

也許有時我們會在大量的檔案上進行這樣的操作.這時我們如何自動重定向?我們可以很容易的這樣來做:

#!/bin/sh
for image in *.gif
do
   cjpeg $image > ${image%%gif}jpg
done

這個指令碼可以將當前目錄下的每一個GIF檔案轉換成為JPEG檔案.

相關文章