Linux_Shell腳本學習第二章-命令之樂(下)

一、行排序

1.1 sort

1.1.1 排序一組文件(例如file1.txt和file2.txt)

$ sort file1.txt file2.txt > sorted.txt

或是

$ sort file1.txt file2.txt -o sorted.txt

1.1.2 按照數字順序排序

$ sort -n file.txt

1.1.3 按照數字順序排序

$ sort -r file.txt

1.1.4 按照月份排序(依照一月、二月、三月……)

$ sort -M months.txt

1.1.5 合併兩個已排序過的文件

$ sort -m sorted1 sorted2

1.1.6 找出已排序文件中不重複的行

$ sort file1.txt file2.txt | uniq

1.1.7 檢查文件是否已經排序過

#!/bin/bash
#功能描述:排序
sort -C filename ;
if [ $? -eq 0 ]; then
echo Sorted;
else
echo Unsorted;
fi

如果文件已經排序,sort會返回爲0的退出碼($?),否則返回非0。

1.1.8 依據列排序

如果輸入數據的格式如下,我們可以按列排序:

$ cat data.txt
1 mac 2000
2 winxp 4000
3 bsd 1000
4 linux 1000

-k指定了排序所依據的字符。-k後的整數指定了文本文件中的某一列。列與列之間由空格分隔。如果是單個數字,則指的是列號。-r告訴sort命令按照逆序進行排序。例如:

# 依據第1列,以逆序形式排序
$ sort -nrk 1 data.txt
4 linux 1000
3 bsd 1000
2 winxp 4000
1 mac 2000
# -nr表明按照數字順序,採用逆序形式排序

# 依據第2列進行排序
$ sort -k 2 data.txt
3 bsd 1000
4 linux 1000
1 mac 2000
2 winxp 4000

1.1.9 輸出與以\0作爲終止符的xargs命令相兼容

$ sort -z data.txt | xargs -0
# 終止符\0用來確保安全地使用xargs命令

1.1.10 忽略標點符號並以字典序排序

$ sort -bd unsorted.txt

其中,選項-b用於忽略文件中的前導空白行,選項-d用於指明以字典序進行排序。

1.2 uniq

uniq命令可以從給定輸入中(stdin或命令行參數指定的文件)找出唯一的行,報告或刪除那些重複的行。uniq只能作用於排過序的數據,因此,uniq通常都與sort命令結合使用。

1.2.1 生成唯一的行(打印輸入中的所有行,但是其中重複的行只打印一次)

cat sorted.txt
bash
foss
hack
hack
$ uniq sorted.txt
bash
foss
hack

或是

$ sort unsorted.txt | uniq

1.2.2 只顯示唯一的行

$ uniq -u sorted.txt
bash
foss

或是

$ sort unsorted.txt | uniq -u

1.2.3 統計各行在文件中出現的次數

$ sort unsorted.txt | uniq -c
1 bash
1 foss
2 hack

1.2.4 找出文件中重複的行

$ sort unsorted.txt | uniq -d
hack

1.2.5 輸出與以\0作爲終止符的xargs命令相兼容

下面的命令將刪除所有指定的文件,這些文件的名字是從files.txt中讀取的:

$ uniq -z file.txt | xargs -0 rm

如果某個文件名出現多次,uniq命令只會將這個文件名寫入stdout一次,這樣就可以避免出現rm: cannot remove FILENAME: No such file or directory。

二、臨時文件命名與隨機數

shell腳本經常需要存儲臨時數據。最適合存儲臨時數據的位置是 /tmp(該目錄中的內容在系統重啓後會被清空)

2.1 mktemp

mktemp命令可以爲臨時文件或目錄創建唯一的名字。

2.1.1 創建臨時文件

$ filename=`mktemp`
$ echo $filename
/tmp/tmp.8xvhkjF5fH

上面的代碼創建了一個臨時文件,然後打印出保存在變量filename中的文件名。

2.1.2 創建臨時目錄

$ dirname=`mktemp -d`
$ echo $dirname
tmp.NI8xzW7VRX

上面的代碼創建了一個臨時目錄,然後打印出保存在變量dirname中的目錄名。

2.1.3 僅生產文件名,不創建文件

$ tmpfile=`mktemp -u`
$ echo $tmpfile
/tmp/tmp.RsGmilRpcT

文件名被存儲在$tmpfile中,但並沒有創建對應的文件。

2.1.4 基於模板創建臨時文件名

$mktemp test.XXX
test.2tc

如果提供了定製模板,X會被隨機的字符(字母或數字)替換。注意,mktemp正常工作的前提是保證模板中至少要有3個X。

三、分割文件與數據

3.1 split

split命令可以用來分割文件。該命令接受文件名作爲參數,然後創建出一系列體積更小的
文件,其中依據字母序排在首位的那個文件對應於原始文件的第一部分,排在次位的文件對應於原始文件的第二部分,以此類推。

3.1.1 指定分割大小

$ split -b 10k data.file
$ ls
data.file xaa xab xac xad xae xaf xag xah xai xaj

上面的命令將data.file分割成了10個大小爲10KB的文件。這些新文件以xab、xac、xad的形式命名。在split命令中,除了k(KB),我們還可以使用M(MB)、G(GB)、c(byte)和w(word)。

3.1.2 使用數字後綴

$ split -b 10k data.file -d -a 4
$ ls
data.file x0009 x0019 x0029 x0039 x0049 x0059 x0069 x0079

-a length可以指定後綴長度。

3.1.3 爲分割後的文件指定文件名前綴

之前那些分割後的文件名都是以x作爲前綴。如果要分割的文件不止一個,我們自然希望能自己命名這些分割後的文件,這樣才能夠知道這些文件分別屬於哪個原始文件。這可以通過提供一個前綴作爲最後一個參數來實現。
這次我們使用split_file作爲文件名前綴,重新執行上一條命令:

$ split -b 10k data.file -d -a 4 split_file
$ ls
data.file split_file0002 split_file0005 split_file0008
strtok.c
split_file0000 split_file0003 split_file0006 split_file0009
split_file0001 split_file0004 split_file0007

3.1.4 根據行數來分割文件

# 分割成多個文件,每個文件包含10行
$ split -l 10 data.file

3.2 csplit

csplit實用工具能夠基於上下文來分隔文件。它依據的是行計數或正則表達式。這個工具對於日誌文件分割尤爲有用。

$ cat server.log
SERVER-1
[connection] 192.168.0.1 success
[connection] 192.168.0.2 failed
[disconnect] 192.168.0.3 pending
[connection] 192.168.0.4 success
SERVER-2
[connection] 192.168.0.1 failed
[connection] 192.168.0.2 failed
[disconnect] 192.168.0.3 success
[connection] 192.168.0.4 failed
SERVER-3
[connection] 192.168.0.1 pending
[connection] 192.168.0.2 pending
[disconnect] 192.168.0.3 pending
[connection] 192.168.0.4 failed

我們需要將這個日誌文件分割成server1.log、server2.log和server3.log,這些文件的內容分別取自原文件中不同的SERVER部分。實現方法如下:

$ csplit server.log /SERVER/ -n 2 -s {*} -f server -b "%02d.log"
$ rm server00.log
$ ls
server01.log server02.log server03.log server.log

下面是這個命令的詳細說明。
 /SERVER/ 用來匹配特定行,分割過程即從此處開始。
 /[REGEX]/ 用於描述文本模式。它從當前行(第一行)一直複製到(但不包括)包含SERVER
的匹配行。
 {*} 表示根據匹配重複執行分割操作,直到文件末尾爲止。可以用{整數}的形式來指定分
割執行的次數。
 -s 使命令進入靜默模式,不打印其他信息。
 -n 指定分割後的文件名後綴的數字個數,例如01、02、03等。
 -f 指定分割後的文件名前綴(在上面的例子中,server就是前綴)。
 -b 指定後綴格式。例如%02d.log,類似於C語言中printf的參數格式。在這裏:文件
名 = 前綴 + 後綴,也就是server + %02d.log。

因爲分割後得到的第一個文件沒有任何內容(匹配的單詞就位於文件的第一行中),所以我們刪除了server00.log。

四、根據擴展名切分文件名

4.1 提取文件名及擴展名

4.1.1 %操作符提取文件名

藉助%操作符可以從name.extension這種格式中提取name部分(文件名)。下面的例子從sample.jpg中提取了sample:

file_jpg="sample.jpg"
name=${file_jpg%.*}
echo File name is: $name

輸出結果:

File name is: sample

${VAR%.*} 的含義如下:
a.從 $VAR中刪除位於%右側的通配符.*所匹配的字符串。通配符從右向左進行匹配。
b.給VAR賦值,即VAR=sample.jpg。通配符從右向左匹配到的內容是.jpg,得到輸出sample。

%屬於非貪婪(non-greedy)操作。它從右向左找出匹配通配符的最短結果。還有另一個操作符%%,它與%相似,但行爲模式卻是貪婪的,這意味着它會匹配符合通配符的最長結果。例如,我們現在有這樣一個文件:

VAR=hack.fun.book.txt

使用%操作符從右向左執行非貪婪匹配,得到匹配結果.txt:

$ echo ${VAR%.*}

命令輸出:hack.fun.book。

使用%%操作符從右向左執行貪婪匹配,得到匹配結果.fun.book.txt:

$ echo ${VAR%%.*}

命令輸出:hack。

4.1.2 #操作符提取後綴名

#操作符可以提取出擴展名。
提取文件名中的 .jpg並存儲到變量file_jpg中:

extension=${file_jpg#*.}
echo Extension is: jpg

輸出結果:

Extension is: jpg

${VAR%.} 的含義如下:
從VAR中刪除位於#右側的通配符(即在上例中使用的
.)從左向右所匹配到的字
符串。

和%%類似,#也有一個對應的貪婪操作符##。

4.1.2 %、%% 與#、##

這裏有個能夠提取域名中不同部分的實例。假定URL爲www.google.com

$ echo ${URL%.*} # 移除.*所匹配的最右邊的內容
www.google
$ echo ${URL%%.*} # 將從右邊開始一直匹配到最左邊的.*(貪婪操作符)移除
www
$ echo ${URL#*.} # 移除*.所匹配的最左邊的內容
google.com
$ echo ${URL##*.} # 將從左邊開始一直匹配到最右邊的*.(貪婪操作符)移除
com

五、多個文件的重命名與移動

5.1 rename

5.1.1 實例

下面的腳本利用find查找PNG和JPEG文件,然後使用##操作符和mv將查找到的文件重命名爲image-1.EXT、image-2.EXT等。注意,腳本並不會修改文件的擴展名:

#!/bin/bash
#文件名: rename.sh
#用途: 重命名 .jpg 和 .png 文件
count=1;
for img in `find . -iname '*.png' -o -iname '*.jpg' -type f -maxdepth 1`
do
new=image-$count.${img##*.}
echo "Renaming $img to $new"
mv "$img" "$new"
let count++
done

輸出如下:

$ ./rename.sh
Renaming hack.jpg to image-1.jpg
Renaming new.jpg to image-2.jpg
Renaming next.png to image-3.png

該腳本重命名了當前目錄下所有的.jpg和.png文件,新文件名採用形如image-1.jpg、image-2.jpg、image-3.png、image-4.png的格式。

5.1.2 將文件名中的空格替換成字符 “_”

$ rename 's/ /_/g' *

's/ /_/g’用於替換文件名,而 * 是用於匹配目標文件的通配符,它也可以寫成 *.txt
或其他通配符模式。

5.1.3 轉換文件名的大小寫

$ rename 'y/A-Z/a-z/' *
$ rename 'y/a-z/A-Z/' *

5.1.4 將所有的.mp3文件移入給定的目錄

$ find path -type f -name "*.mp3" -exec mv {} target_dir \;

5.1.5以遞歸的方式將所有文件名中的空格替換爲字符"_"

$ find path -type f -exec rename 's/ /_/g' {} \;

六、拼寫檢查與詞典操作

6.1 詞典文件

目錄/usr/share/dict/中包含了一些詞典文件。所謂“詞典文件”就是包含了單詞列表的文本文
件。我們可以利用它來檢查某個單詞是否在詞典之中。

$ ls /usr/share/dict/
american-english british-english

爲了檢查給定的單詞是否爲詞典單詞,可以使用下面的腳本:

#!/bin/bash
#文件名: checkword.sh
word=$1
grep "^$1$" /usr/share/dict/british-english -q
if [ $? -eq 0 ]; then
echo $word is a dictionary word;
else
echo $word is not a dictionary word;
fi

這個腳本的用法如下:

$ ./checkword.sh ful
ful is not a dictionary word
$ ./checkword.sh fool
fool is a dictionary word

在grep中,^標記着單詞的開始,$標記着單詞的結束①,-q選項 禁止grep產生任何輸出。

6.2 aspell

用拼寫檢查命令aspell來覈查某個單詞是否在詞典中:

#!/bin/bash
#文件名: aspellcheck.sh
word=$1
output=`echo \"$word\" | aspell list`
if [ -z $output ]; then
echo $word is a dictionary word;
else
echo $word is not a dictionary word;
fi

當給定的輸入不是一個詞典單詞時,aspell list命令會生成輸出,否則不產生任何輸出。
-z用於確認$output是否爲空。

6.3 look

look命令可以顯示出以特定字符串起始的行。你可以用它在日誌文件中查找以特定日期爲
首的記錄,或是在詞典中查找以特定字符串開頭的單詞。
look默認會搜索/usr/share/dict/words,你也可以給出文件供其搜索:

$ look word

或者使用

$ grep "^word" filepath

例如:

$ look android
android
android's
androids

在/var/log/syslog中找出以特定日期起始的日誌記錄

$look 'Aug 30' /var/log/syslog

七、利用並行進程加速命令執行

如果多個文件需要生成校驗和,我們可以使用下面的腳本來運行md5sum的多個實例:

#/bin/bash
#文件名: generate_checksums.sh
PIDARRAY=()
for file in File1.iso File2.iso
do
	md5sum $file &
	PIDARRAY+=("$!")
done
wait ${PIDARRAY[@]}

運行腳本後,可以得到如下輸出:

$ ./generate_checksums.sh
330dcb53f253acdf76431cecca0fefe7 File1.iso
bd1694a6fe6df12c3b8141dcffaf06e6 File2.iso

輸出結果和下面命令的結果一樣:

md5sum File1.iso File2.iso

我們利用了Bash的操作符&,它使得shell將命令置於後臺並繼續執行腳本。這意味着一旦循環結束,腳本就會退出,而md5sum進程仍在後臺運行。爲了避免這種情況,我們使用!PIDBash!來獲得進程的PID,在Bash中,!保存着最近一個後臺進程的PID。我們將這些PID放入數組,然後使用wait命令等待這些進程結束。

八、檢查目錄以及其中的文件與子目錄

8.1 生成目錄的樹狀視圖

下面來查看目錄/var/log的樹狀視圖:

$ cd /var/log
$ find . -exec sh -c 'echo -n {} | tr -d "[:alnum:]_.\-" | \
tr "/" " "; basename {}' \;

生成如下輸出:

mail
	statistics
gdm
	::0.log
	::0.log.1
cups
		error_log
		access_log
	... access_l

-exec選項創建了一個子shell,在這個子shell中使用echo命令將文件名發送給tr命令的
stdin。這裏用到了兩個tr命令。第一個tr刪除了所有的字母數字字符、連字符(-)、下劃線(_)和點號(.),只將路徑中的斜線(/)傳入第二個tr,後者將這些斜線全部轉換成空格。最後,利用basename命令去掉文件名前的路徑部分並將結果顯示出來。

8.2 生成文件及子目錄的彙總信息

下面的命令可以獲得當前目錄下文件的彙總信息:

for d in `find . -type d`;
do
echo `find $d -type f | wc -l` files in $d;
done

如果在/var/log下執行該腳本,會生成如下輸出:

103 files in .
17 files in ./cups
0 files in ./hp
0 files in ./hp/tmp
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章