內容尋找(遞迴)
grep /etc/httpd/modules/ -lr '51ditu' *
檔案名稱尋找(遞迴,不適合尋找modules)
find /etc/httpd/ -name httpd.conf
引用:
grep 命令用於搜尋由 Pattern 參數指定的模式,並將每個匹配的行寫入標準輸出中。這些模式是具有限定的Regex,它們使用 ed 或 egrep 命令樣式。grep 命令使用壓縮的不確定演算法。
如果在 File 參數中指定了多個名稱,grep 命令將顯示包含匹配行的檔案的名稱。對 shell 有特殊含義的字元 ($, *, [, |, ^, (, ), / ) 出現在 Pattern 參數中時必須帶雙引號。如果 Pattern 參數不是簡單字串,通常必須用單引號將整個模式括起來。在諸如 [a-z] 之類的運算式中,-(減號)cml 可根據當前正在整理的序列來指定一個範圍。整理順序可以定義等價的類以供在字元範圍中使用。如果未指定任何檔案,grep 會假定為標準輸入。
註: 請勿對特殊檔案運行 grep 命令,這樣做可能產生不可預計的結果。 輸入行不應包含Null 字元。 輸入檔案應該以分行符號結束。 分行符號不會與Regex匹配。 雖然一些標誌可以同時被指定,但其中的某些標誌會覆蓋其他標誌。例如,-l 選項將優先於所有其他標誌。另外,如果您同時指定了 -E 和 -F 標誌,則後指定的那個會有優先權。 標誌
-b |
在每行之前添加找到該行時所在的塊編號。使用這個標誌有助於通過上下文來找到磁碟塊號碼。-b 標誌不能用於來自標準輸入和管道的輸入。 |
-c |
僅顯示匹配行的計數。 |
-E |
將每個指定模式視作擴充的Regex(ERE)。ERE 的空值將匹配所有的行。 註:帶有 -E 標誌的 grep 命令等價於 egrep 命令,只不過它們的錯誤和使用資訊不同以及 -s 標誌的作用不同。 |
-e PatternList |
指定一個或多個搜尋模式。其作用相當於一個簡單模式,但在模式以 -(減號)開始的情況下,這將非常有用。模式之間應該用分行符號分隔。連續使用兩個分行符號或者在引號後加上分行符號 ("/n) 可以指定空模式。除非同時指定了 -E 或 -F 標誌,否則每個模式都將被視作基本Regex(BRE)。grep 可接受多個 -e 和 -f 標誌。在匹配行時,所有指定的模式都將被使用,但評估的順序沒有指定。 |
-F |
將每個指定的模式視作字串而不是Regex。Null 字元串可匹配所有的行。 註: 帶有 -F 標誌的 grep 命令等價於 fgrep 命令,只不過它們的錯誤和使用資訊不同以及 -s 標誌具有不同的作用。 |
-f PatternFile |
指定包含搜尋模式的檔案。模式之間應該用分行符號加以分隔,空行將被認為是空模式。每種模式都將被視作基本的Regex(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命令簡介及Regex
兩個更為有用的命令和Regex
在我們開始學習新的Shell編程知識之前,我們先來看一下兩個更為有用的兩個命令,這兩個命令雖然並不是Shell的一部分,但是在進行Shell編程時卻會經常用到.隨後我們會來看一下Regex.
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命令,這是一個並不常見的名字,他是通用Regex解析器的簡稱(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選項來轉換尋找的條件並且列印出在兩個檔案中不匹配的總行數.
Regex
正是我們所看到的,grep命令的基本用法是比較容易掌握的.現在我們要來看一下基本的Regex,這會允許我們做一些更為複雜的匹配.正如我們在前面所提到的,Regex是用在Linux或是共他的一些開源中的語言.我們可以在vi或是在編寫Perl指令碼時使用.
在Regex的使用過程中,一些字元會被以不同的方式進行處理.最常見的一些用法如下:
^ 在一行的開頭
$ 在一行的結尾
. 任意一個單一字元
[] 方括弧中所包含是字母的範圍,其中的任何一個都可以進行匹配,例如a-e的字母範圍,或者是我們可以使用^來進行反義.
如果我們要將他們作為普通的字元來使用就要在這些字元前面加上/.所以如果我們要尋找一個$字元,我們就要使用/$來進行尋找.
下面的是一些可以在方括弧中使用的比較有用的特殊匹配:
[:alnum:] 字母數字字元
[:alpha:] 字母
[:ascii:] ASCII字元
[:blank:] 空格或是Tab
[:cntrl:] ASCII碼控制字元
[:digit:] 數字
[:graph:] 非控制,非空白字元
[:lower:] 小寫字母
[:print:] 可列印字元
[:punct:] 標點字元
[:space:] 空白字元,包括垂直Tab
[:upper:] 大寫字元
[:xdigit:] 十六進位數字
另外,如果同時使用-E選項指定了擴充匹配,在Regex的後面也許會跟一些其他的控制匹配類型組合的字元.如果我們只是想將他們作為普通的字元進行使用,我們也要在其前面加上轉義符/.
? 可選的匹配,但是最多匹配一次
* 必須匹配0個或是多重專案
+ 必須匹配1個或是多重專案
{n} 必須匹配n次
{n,} 必須匹配n次或是更多次
{n,m} 匹配範圍為n次到m次,包括m次
這些內容看起來有一些複雜,但是如果我們循序漸進,我們就會發現事實上這些內容並不如我們在第一眼看到時那樣的複雜.最簡單的掌握Regex的方法就是簡單的試一些例子:
如果我們要尋找以字元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,
$
我們在這裡只是接觸Regex一些相對來說更為重要的一部分.正如在Linux中的其他的大多數的內容,在這之外會許多的文檔來協助我們要發現更為詳細的內容,但是學習Regex的最好的方法就是要實驗這些運算式.
命令執行:
當我們編寫指令碼時,我們常常需要在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檔案.