歡迎您光臨本站 註冊首頁

find與grep命令簡介及正則表達式

←手機掃碼閱讀     火星人 @ 2014-03-24 , reply:0

  兩個更為有用的命令和正則表達式

  在我們開始學習新的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文件.

[火星人 ] find與grep命令簡介及正則表達式已經有572次圍觀

http://coctec.com/docs/linux/show-post-135401.html