天天看點

shell 腳本之文本處理

我學習shell 最初目的就是用于文本處理,以及自動化處理一些繁雜的操作,shell 腳本在這方面正是非常好用的工具。

本文隻介紹常見的文本處理,對于其中涉及到的指令,以及正規表達式則不過多介紹;如果想用好shell, linux 文本操作的一些指令,還有正規表達式都必須要掌握。學習正規表達式開始比較難,多動手練習後,其實還是非常有幫助的。在java 和一些腳本語言中也有正規表達式的概念,是以強烈建議學好它,而且非常值得。

本文圍繞以下幾個問題展開讨論,針對每個問題再細化出另外一些問題:

1、如何查找文本?

2、 如何提取文本?

3、 如何替換文本?

4、 如何删除文本?

5、 如何插入文本?

6、 二進制檔案處理

一、 查找文本,過濾文本

查找文本非常有用, 比如在檢視列印時可以從大量列印中篩選出我們需要的列印。

查找或過濾文本當然用grep,非常強大。

假設我手上有一個檔案:onelife.txt

檔案内容如下:

  You Have Olny One Life

There are moments in life when you miss someone so much that

you just want to pick them from your dreams and hug then for

real! Dream what your want to dream; to whree you want to go;

be what you want to be, because you have only one life and one

chance to do all the things you want to do.

hello# world

string test "start " end

start123end

1. 查找檔案中是否出現life?

$ cat onelife.txt |grep 'life'

通過上面的指令可以将含有life的所有行都列印出來。

如果我們隻需要知道結果是否包含該文本,則可以如下:

$ cat onelife.txt |grep 'life' > /dev/null

$ echo $?

通過傳回值來判斷,0 表示查找成功,1 表示查找失敗。

2. 如何顯示既包含life 的行又包含miss的行?

其實這個問題就是怎麼表示邏輯與的問題

$ cat onelife.txt |grep 'life' |grep 'miss'

通過管道實作’與‘操作

3. 如何顯示包含life 或者包含miss的行?

$cat onelife.txt |grep  -E 'life | miss'

這裡使用-E參數 調用egrep,不調用egrep也可以, 在 | 符号前面加個轉義字元就行。

4. 如何顯示所有不包含 life的行?

cat onelife.txt|grep -v 'life'

這裡借助grep 的-v 參數,來顯示所有不比對行。

5. 如何顯示包含數字的行?

cat onelife.txt |grep -E '[0-9]+'

這重比對一類的問題就需要使用到正規表達式了 

二、 提取文本

提取文本主要會用到grep/ cut /awk/, 對于二進制檔案,需要結合od 指令

1. 如何提取出網頁中的所有網頁連結?

$ grep -E -o 'https?://www\.[a-zA-Z0-9_]+\.[a-zA-Z]{2,4}' baidu.txt |sort|uniq

https://www.baidu.com

http://www.baidu.com

http://www.beian.gov

http://www.hao123.com

http://www.nuomi.com

http://www.w3.org

這裡的baidu.xtx檔案是百度網頁的源碼(用360浏覽器可以直接右鍵檢視源碼),隻是提取了一級網頁, 在上面用到了grep的-o 參數, 這個參數專門用于提取比對成功的文本内容;在‘’單引号包含的部分是一個正規表達式。最後删除一些重複的網站。

同理, 也可以提取圖檔, 視訊,電子郵件等等内容。 在部分網頁中可能沒有視訊的下載下傳位址,對于不太懂js的人來說,那麼我們可以檢視該網頁的源碼,然後儲存成文本,再從中提取出視訊位址。

2. 如何提取出雙引号中包含的字元串?

其實這個問題和上一個一樣,同樣可以用grep來,隻是因為比較常用,是以單獨列出來。

$ echo '"hello" world' |grep -o '".*"'|sed 's/"/ /g'

 hello 

其中grep提取字元串, sed 用來删除雙引号

*** 基本上隻要能寫出正規表達式的,都可以用grep來提取;但是,對于有些比較難寫出正規表達式的就要借用分隔符來提取了。

3. 在linux下如何提取出使用者ID?

 eg:

$ cat /etc/passwd |tail -n 5|cut -d':' -f1,3

lfc:1018

zhq:1019

ljl:1020

lpf:1006

test:1009

 passwd 檔案下包含有使用者名、ID群組ID等内容, 這個檔案以:為分隔符,文本内容都比較相識,是以用正規表達式不好比對,cut 或awk 正合适。

這個例子中我隻提取最後五個使用者的資訊,提取了第一和第三個字段,也就是使用者名和ID, 它們可以指定一個分隔符,然後對每個字段進行提取。

用awk 可以這樣提取:

$ cat /etc/passwd |tail -n 5|awk -F ':' '{printf("%-10s\t%-10s\n",$1,$3)}'

printf用于格式化輸出, 和C用法一樣。

awk 非常強大,在處理分組,統計方面很有優勢,甚至可以當成一門語言,它的文法格式非常簡單,對于基本用法還是很容易掌握的。

三、 替換文本?

簡單的字元串替換可以使用tr指令, 當然最好用的還是sed了,可以比對正規表達式,sed可以完成所有你希望的替換工作。

1. 将 字元串"abcdefgHIJklmn" 全部轉換成大寫?

$ echo abcdefgHIJklmn |tr [a-z] [A-Z] 

ABCDEFGHIJKLMN

$ echo abcdefgHIJklmn |sed 's/[a-z]/\u&/g'

ABCDEFGHIJKLMN

用sed 和 tr都可以,tr比較直覺,sed則需要掌握一些元字元的意義;\u用于将後面的字元轉換成大寫, \l 用于轉換成小寫。

2. 如何将單詞的首字母變成大寫該如何實作呢?

$ echo hello world |sed 's/\b\w/\u&/g'

Hello World

這裡用tr就不太好實作了。

3. 如何将文本中的單引号轉換成雙引号? 

$ echo "hello '123' world"|sed 's/'\''/"/g'

hello "123" world

$ echo 'hello "123" world'|sed 's/"/'\''/g'

hello '123' world

上面 分别實作将123的單/雙引号進行轉換, 最外面的引号隻是為了把這個字元串當成一個字元串處理,實際echo時并不會出現

4. 替換指定行

$ cat onelife.txt |sed '2c change line'

将第二行 替換成 change line

四、 删除文本

删除文本我經常使用tr 或sed, grep指令

1. 如何删除空行?

$ cat onelife.txt |tr -s '\n'

-s 用于縮減連續字元,隻保留一個字元。

$ cat onelife.txt |sed '/^$/d'

sed 中有 d 指令,用于删除一行,^$ 用于比對空行

2. 如 删除Windows檔案“造成”的'^M'字元?

在window下編輯腳本,然後放到linux下執行,很容易出現莫名其妙的錯誤,其實就可能是\r字元導緻的,删掉它就可以了。

因為window下換行符為 \r\n , linux下為\n

$ cat onelife.txt | tr -d "\r"

$ cat onelife.txt|sed 's/\r//'

需要注意的是, tr 删除的是“”中所有的字元, 如tr -d “abc”  會删除所有a b c , 而不是abc 字元串

3. 如何删除雙引号内(包括雙引号)的字元串?

$ echo 'hello "123" world'|sed 's/".*"//'

hello  world

如果要删除整行,可以用sed  d 指令, grep -v 參數

4. 如何删除指定行? 如删除第一行

$ cat onelife.txt |sed '1d'

五、 插入文本

1. 行内插入字元,行首和行尾插入一個雙引号, 行内的數字字元串插入單引号,如 hello 123 word  變成 “hello '123' world”?

$ echo 'hello 123 word' |sed -e 's/^/"/; s/$/"/; s/[0-9]\+/'\''&'\''/' 

"hello '123' word"

^表示行首, $表示行尾, &表示比對成功的pattern

2. 插入一行。 

a.  在指定行插入一行:

cat onelife.txt |sed '2i "insert line"'

在第二行插入 “insertline”

b. 在比對行之前、之後插入一行

cat onelife.txt |sed -e '/dreams/ i\#if 0' -e '/dreams/ a\#endif'

在包含dreams 行的前面插入 #if 0, 後面一行插入#endif

用來注釋某句話,還是很有用的哈, 雖然很少程式員會這樣注釋多個這樣的行。

六、 二進制檔案處理

假設現在有一個二進制檔案data.bin, 其中有兩個位元組内容

shell 腳本之文本處理

1. 如果提取二進制檔案中的第二個位元組内容?

$ od -t xC dss.dat |head -1 |cut -d' ' -f3

02

od 指令用于檢視二進制檔案,預設為八進制顯示,也可以指定顯示格式, -t  x 表示以十六進制顯示。C 字元表示按字元分割, 預設地od 将4個位元組一起作為分割,也就是會将0002 當成一個單元來顯示。

也可以直接用hexdump來檢視十六進制檔案。

$ hexdump -C dss.dat

00000000  00 02                                             |..|

00000002

2. 如何将檔案分割?

先生成一個測試檔案

dd if=/dev/zero bs=100k count=1 of=data.file

這個測試檔案全是0, 在目前目錄下 data.file

按大小分割:

$split -b 10k data.file -d -a 4 split_

 -d 表示字尾為數字, -a 表示字尾長度為4 ,指定檔案名字首為 split_ 

按行分割:

split -l data.file -d -a 2 split_

由于這個檔案實際大小為0, wc 統計到的行數也為0, 是以這個 -l參數無效。