詳解shell指令碼(七)——玩轉檔案
批量重新命名和移動檔案
#!/bin/bash #filename: rename.sh #desc: rename .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 's/ /_/g' * #把檔名中的空格替換為字元'_'
拼寫檢查與詞典操作
#!/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
在grep中,^ 標記著單詞的開始,$ 標記著單詞的結束。-q 禁止產生任何輸出。
並行程序加速命令
#!/bin/bash PIDARRAY=() for file in File1.iso File2.so do md5sum $file & PIDARRAY+=("$!") done wait ${PIDARRAY[@]}
我們利用了Bash的操作符&,它使得shell將命令置於後臺並繼續執行指令碼。這意味著一旦迴圈結束,指令碼就會退出,而md5sum命令仍在後臺執行。為了避免這種情況,我們使用!來獲得程序的PID,在Bash中,!儲存著最近一個後臺程序的PID。我們這些PID放入陣列,然後使用wait命令等待這些程序結束。
文字檔案的交集與差集
comm A.txt B.txt -3 | sed 's/^\t//'
#A,B 檔案的求差
在生成統一輸出時,sed命令通過管道獲取comm的輸出。它刪除行首的 \t字元。sed中的s表示替換(substitute)。/^\t/ 匹配行前的 \t(^是行首標記)。//(兩個/操作符之間沒有任何字元)是用來替換行首的\t的字串。如此一來,就刪除了所有行首的\t。
查詢並刪除重複檔案
# !/bin/bash
# 檔名: remove_duplicates.sh
# 用途: 查詢並刪除重複檔案,每一個檔案只保留一份
ls -lS --time-style=long-iso | awk 'BEGIN {
getline; getline;
name1=$8; size=$5
}
{
name2=$8;
if (size==$5)
{
"md5sum "name1 | getline; csum1=$1;
"md5sum "name2 | getline; csum2=$1;
if ( csum1==csum2 )
{
print name1; print name2
}
};
size=$5; name1=name2;
}' | sort -u > duplicate_files
cat duplicate_files | xargs -I {} md5sum {} | sort | uniq -w 32 | awk '{ print"^"$2"$" }' | sort -u > duplicate_sample
echo Removing..
comm duplicate_files duplicate_sample -2 -3 | tee /dev/stderr | xargs rm
echo Removed duplicates files successfully.
ls -lS對當前目錄下的所有檔案按照檔案大小進行排序,並列出文件的詳細資訊。awk讀取ls -lS的輸出,對行列進行比較,找出重複檔案。
我們將檔案依據大小排序並列出,這樣大小接近的檔案就會排列在一起。識別大小相同的檔案是我們查詢重複檔案的第一步。接下來,計算這些檔案的校驗和。如果校驗和相同,那麼這些檔案就是重複檔案,將被刪除。
第1行輸出告訴我們檔案數量,這個資訊在本例中沒什麼用處。我們用getline讀取第1行,然後丟棄。由於需要對每一行及其下一行來比對檔案大小,因此用getline讀取長檔案列表的第一行,並存儲檔名和大小(它們分別是第8列和第5列)。這樣我們就先得到了一行。接下來,awk進入{}語句塊(在這個語句塊中讀取餘下的文字行),讀取到的每一行文字都會執行該語句塊。它將當前行中讀取到的檔案大小與之前儲存在變數size中的值進行比較。如果相等,那就意味著兩個檔案至少在大小上是相同的,隨後再用md5sum執行進一步的檢查。
在awk中,外部命令的輸出可以用下面的方法讀取:
“cmd”| getline
隨後就可以在0中獲取命令的輸出,在1,2,…n中獲取命令輸出中的每一列。我們將檔案的md5sum儲存在變數csum1和csum2中。變數name1和name2儲存檔案列表中位置連續的檔名。如果兩個檔案的校驗和相同,那它們肯定是重複檔案,其檔名會被打印出來。
我們需要從每組重複檔案中找出一個檔案,這樣就可以刪除其他副本了。計算重複檔案的md5sum,從每一組重複檔案中打印出其中一個。這是通過-w 32比較每一行的md5sum(md5sum輸出中的前32個字元,md5sum的輸出通常由32個字元的雜湊值和檔名組成),然後找出那些不相同的行。這樣,每組重複檔案中的一個取樣就被寫入duplicate_sample。
現在需要將duplicate_files中列出的、且未包含在duplicate_sample之內的全部檔案刪除。這些檔案由comm命令負責打印出來。我們可以使用差集操作來實現(參考3.3節)。
comm通常只接受排序過的檔案。所以,在重定向到duplicate_files和duplicate_sample之前,首先用sort -u作為一個過濾器。
tee命令在這裡有一個妙用:它在將檔名傳遞給rm命令的同時,也起到了print的作用。tee將來自stdin的行寫入檔案,同時將其傳送到stdout。我們也可以將文字重定向到stderr來實現終端列印功能。/dev/stderr是對應於stderr(標準錯誤)的裝置。通過重定向到stderr裝置檔案,來自stdin的文字將會以標準錯誤的形式出現在終端中。
charttr 設定檔案
chattr +i file
#檔案設定為不可修改
檔案統計資訊
#! /bin/bash
if [ $# -ne 1];
then
echo "Usage is $0 basepath";
exit
fi
path=$1
declare -A statarray;
while read line;
do
ftype=`file -b "$line" | cut -d, -f1`
let statarray["$ftype"]++;
done < < (find $path -type f -print)
echo ===============File types and counts ================
for ftype in "$(!statarray[@])";
do
echo $ftype : ${statarrat["$ftype"]}
done
在指令碼中聲明瞭一個關聯陣列statarray,這樣可以用檔案型別作為陣列索引,將每種檔案型別的數量存入陣列。每次遇到一個檔案型別,就用let增加計數。find命令以遞迴的方式獲取檔案路徑列表。指令碼中的ftype=’file -b “$line”’使用file命令獲得檔案型別資訊。選項-b告訴file命令只打印檔案型別(不包括檔名)。輸出的檔案型別資訊包含很多細節,比如影象編碼以及解析度(如果是影象檔案的話)。對於這些細節我們並不感興趣,我們只需要基本的資訊就夠了。各種細節資訊是由逗號分隔的,例如:
$ file a.out -b
ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped
我們只需要從上面這些細節中提取ELF 32-bit LSB executable。因此我們使用cut -d,-f1,指明以逗號作為定界符,並且只打印第一個欄位。
done<<(find $path –type f –print);是一段很重要的程式碼。它的執行邏輯如下:
while read line;
do something
done < filename
我們不用filename,而是用find命令的輸出。
<(find $path -type f -print)等同於檔名。只不過它用子程序輸出來代替檔名。注意,第一個<用於輸入重定向,第二個<用於將子程序的輸出裝換成檔名。在兩個<之間有一個空格,避免shell將其解釋為<<操作符。
${!statarray[@]}用於返回一個數組索引列表。
diff 生成目錄的差異資訊
diff -Naur directory1 directory2
-N: 將所有確實的檔案視為空檔案
-a: 將所有檔案視為文字檔案
-u:生成一體化輸出
-r: 遍歷目錄下的所有檔案
tail 終端持續更新
tail -f growing_file
tail -f /var/log/messages
dmsg | tail -f
#假設我們正在讀取一個不斷增長的檔案,程序Foo一直在向該檔案追加資料,那麼tail -f 就會一直執行到程序Foo結束
PID=$(pidof Foo)
tail -f file --pid $PID
我們經常會執行dmeg 檢視核心的環形緩衝區訊息,要麼是除錯USB裝置,要麼是檢視sdX(X是對應於SCSI磁碟的sd裝置的次序列號)。tail -f也可以加入一個睡眠間隔 -s,這樣我們就可以設定監視檔案更新的時間間隔。