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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章