读《Linux Shell脚本攻略》第2章笔记

  • A+
Categories:Shell

1. cat (concatenate)
#标准输入和输入文件的内容拼接在一起
echo ‘Text through stdin’ | cat – file.txt

压缩空白行
#将多个空行压缩成单个空格行,如下:
[root@server1 ~]# cat multi_blanks.txt
line1

line2

line3

line4
[root@server1 ~]# cat -s multi_blanks.txt
line1

line2

line3

line4

#用tr移除空白行
[root@server1 ~]# cat multi_blanks.txt | tr -s ‘\n’
line1
line2
line3
line4

制表符显示为^|
单从视觉上很难将制表符同连续的空格区分开。而在用Python之类的语言编写程序时,将将制表符和空格用于代码缩进,具有特殊含义,并进行区别对待。因
此,若在应该使用空格的地方误用了制表符的话,就会产生缩进错误。仅仅在文本编辑器中进行查看是很难发现这种错误的。cat有这个特性,可以将制表符重点
标记出来。该特性对排除缩进错误非常有用。用cat命令的-T选项能够将制表符标记成^|。例如:
[root@server1 test]# cat file.py
def function();
var = 5
next = 6
third = 7
[root@server1 test]# cat -T file.py
def function();
^Ivar = 5
^I^Inext = 6
^Ithird = 7

2. 录制与回放终端会话
[root@server1 test]# script -t 2> timing.log -a output.session
Script started, file is output.session
[root@server1 test]# ls
2  a1  a2  a3  file.py  ifs.sh  jd2.sh  jd.sh  multi_blanks.txt  output.session  set ff=unix  timing.log
[root@server1 test]# exit
exit
Script done, file is output.session
两个文件被当做script命令的参数。其中一个文件timing.log用于存储时序信息,描述每一个命令在何时运行;另一个文件
output.session用于存储命令输出。-t选项用于将时序数据导入stderr。2>则用于将stderr重定向到
timing.log。
借助这两个文件:timing.log(存储时序信息)和output.session(存储命令输入信息),我们可以按照下面的方法回放命令执行过程:
[root@server1 test]# scriptreplay timing.log output.session  #按播放命令序列输出

script命令同样可以用于建立可在多个用户直接进行广播的视频会话。
打开两个终端,Terminal1和Terminal2
1)在Terminal1中输入以下命令:
[root@server1 test]# mkfifo scriptfifo

2)在Terminal2中输入以下命令:
[root@server1 test]# cat scriptfifo

3)在Terminal1中输入以下命令:
[root@server1 test]# script -f scriptfifo
Script started, file is scriptfifo
[root@server1 test]# commands
Terminal1就成为了广播员,而Terminal2则成为了听众,不管你在Terminal1中输入什么内容,它都会在Terminal2或者使用了cat scriptfifo命令的终端都会实时播放.
如果需要结束会话,输入exit并按回车键,退出

3. find
列出当前目录及子目录下所有的文件和文件夹
find bash_path -print
bash_path:查找路径
-print:使用’\n’作为定界符打印每一个匹配的文件名(路径)
-print0: 使用’\0′作为定界符打印每一个匹配的文件名(路径)
-name参数:根据文件名或正则表达式匹配搜索,-iname:忽略字母大小写

如果想匹配多个条件中的一个,可以采用or条件操作:
find . \(-name “*.txt” -o -iname “*.pdf” \) -print

-path:参数可以使用通配符来匹配文件路径或文件。-name总是用给定的文件名进行匹配,-path则将文件路径作为一个整体进行匹配。

-regex:和-path类似,只不过-regex是基于正则表达式来匹配文件路径的。正则表达式是通配符的高级形式。如:[a-z0-9]+@[a-z0-9]+.[a-z0-9]+(Email地址形式,+指明在它之前的字符类中的字符可以出现一次或多次)
-iregex:忽略正则表达式的大小写

!:否定参数

-maxdepth:最大深度  #find命令向下的最大深度限制为1(-maxdepth 1)
-mindepth:最小深度
-maxdepth和-mindepth应该作为find的第3个参数出现。如果作为第4个或之后的参数,就可能会影响到find的效率,因为它不得不进
行一些不必要的检查。例如,如果-maxdepth作为第4个参数,-type作为第三个参数,find首先会找出符合-type的所有文件,然后在所有
匹配的文件中再找出符合指定深度的那些。但是如果反过来,目录深度作为第三个参数,-type作为第四个参数,那么find就能够在找到所有符合指定深度
的文件后,再检查这些文件的类型,这才是最有效的搜索顺序。

时间戳(timestamp)
-atime(访问时间):用户最近一次访问文件的时间。
-mtime(修改时间):文件内容最后一次被修改的时间。
-ctime(变化时间):文件元数据(metadata,例如权限或所有权)最后一次改变的时间。
-atime、-mtime、-ctime可作为find的时间参数。单位是天。这些整数值通常还带有-或+;-表示小于,+表示大于。
-amin(访问时间)、-mmin(修改时间)、-cmin(变化时间),单位分钟。

-newer:指定一个用于比较时间戳的参考文件,然后找出比参数文件更长的修改时间的所有文件。
find命令的时间戳操作出来选项对编写系统备份和很有帮助。

基于文件大小的搜索
find . -type f -size +2k #大于2k的文件
find . -type f -size -2k #小于2k的文件
find . -type f -size 2k  #等于2k的文件
除了k单位,还有其他文件大小单位b(块,512字节)、c(字节)、w(字,2字节)、k(千字节)、M(兆字节)、G(吉字节)

删除匹配的文件
-delete可以用来删除find查找到的匹配文件
例如:删除当前目录下所有的.swp文件:
find . -type f -name “*.swp” -delete

基于文件权限和所有权的匹配
find . -type f -perm 644 -print   #打印当前目录下权限为644的文件
find . -type f -name “*.sh” -perm 755 -user root -print  #找出.sh结尾用户名为root权限为755的文件

-exec
find命令可以借助选项-exec与其他命令进行结合。
find . -type f -user root -name “*.swp” -exec rm -rf {} \;
{}是一个特殊的字符串,与-exec选项结合使用。对于每一个匹配的文件,{}会被替换成相应的文件名。
-exec能够同printf结合来生成有用的输出信息。例如:
# find . -type f -name “*.swp” -exec printf “Text file:%s\n” {} \;
Text file:./1.swp
Text file:./2.swp
Text file:./3.swp

find跳过特定的目录
在搜索目录并执行某些操作的时候,有时为了提高性能。需要跳过一些子目录。例如,程序员会在git所管理的开发源码树中查找特定的文件,源代码层级结构总
是会在每个子目录中包含一个.git目录(.git存储每个目录相关的版本控制信息)。因为与版本控制相关的目录对我们而言并没有什么用处,所以没必要去
搜索这些目录。
# find . \( -name “.git” -prune \) -o \( -type f -print \)
\( -name “.git” -prune \)的作用是用于进行排除,它指明了.git目录应该排除掉,而\( -type f -print \)指明了需要执行的动作。这些动作需要被放置在第二个语句块中

4. xargs
可以处理stdin标准输入并将其转换成特定命令的命令行参数。还可以将单行或多行文本输入转换成其他格式,例如单行变多行或是多行变单行。
将多行输入转换成单行输出
[root@server1 test]# cat example.txt
1 2 3 4 5 6
7    8 9 10
11 12
[root@server1 test]# cat example.txt | xargs
1 2 3 4 5 6 7 8 9 10 11 12

将单行输入转换成多行输出
[root@server1 test]# cat example.txt | xargs | xargs  -n 2
1 2
3 4
5 6
7 8
9 10
11 12

-d选项为输入指定一个定制的定界符
# echo “splitXsplitXsplitXsplit” | xargs -d X
split split split split

可以结合-n参数
# echo “splitXsplitXsplitXsplit” | xargs -d X -n 2
split split
split split

读取stdin,将格式化参数传递给命令
[root@server1 test]# cat args.txt
arg1
arg2
arg3
[root@server1 test]# cat args.txt | xargs -n 1 sh ./cecho.sh
arg1 #
arg2 #
arg3 #
[root@server1 test]# cat args.txt | xargs -n 2 sh ./cecho.sh
arg1 arg2 #
arg3 #
[root@server1 test]# cat args.txt | xargs sh ./cecho.sh
arg1 arg2 arg3 #
在上面的例子中,我们直接为特定的命令(例如cecho.sh)提供命令行参数。这些参数都只源于args.txt文件。

-I指定一个替换字符串,与xargs结合使用时,对于每一个参数,命令都会被执行一次。
[root@server1 test]# cat args.txt | xargs -I {} sh ./cecho.sh -p {} -l
-p arg1 -l #
-p arg2 -l #
-p arg3 -l #
-I {}指定了替换字符串。对于每一个命令参数,字符串{}会被从stdin读取到的参数所替换。使用-I的时候,命令就似乎是在一个循环中执行一样。如果有三个参数,那么命令就会连同{}一起被执行三次,而{}在每一次执行中都会被替换为相应的参数。

结合find使用xargs
# find . -tpye f -name “*.txt” -print | xargs rm -f
这样做很危险。可能会删除不必要的文件。我们没法预测分隔find命令输出结果的定界符是’\n’还是’
‘。很多文件名都可能包含空格符,而xargs很可能会误认为它们是定界符(例如,hell
text.txt会被xargs误认为hell和text.txt2个文件)
只要我们把find的输出作为xargs的输入,就必须将-print0与find结合使用,以字符null来分隔输出。
用find匹配并列出所有.txt文件,然后用xargs将这些文件删除:
# find . -tpye f -name “*.txt” -print0 | xargs -0 rm -f
这样就可以删除所有.txt文件。xargs -0将\0作为输入定界符。

统计源码目录中所有C程序文件的行数
# find src_path -type f -name “*.c” -print0 | xargs -0 wc -l

结合stdin,巧妙运用while语句和子shell
[root@server1 test]# cat example.txt
1 2 3 4 5 6
7    8 9 10
11 12
[root@server1 test]# cat example.txt | (while read arg; do cat $arg;done) 等同于# cat example.txt | xargs -n 1 | xargs -I {} cat {}
cat: 1: No such file or directory
echo22222222222
cat: 3: No such file or directory
cat: 4: No such file or directory
cat: 5: No such file or directory
cat: 6: No such file or directory
cat: 7: No such file or directory
cat: 8: No such file or directory
cat: 9: No such file or directory
cat: 10: No such file or directory
cat: 11: No such file or directory
cat: 12: No such file or directory
在while循环中,可以将cat $arg替换成任意数量的命令,这样我们就可以对同一个参数执行多项命令。我们也可以借助管道,将输出传递给其他命令。这个技巧能适用于各种问题环境。子shell内部的多个命令可作为一个整体来运行。

5. tr
tr可以对来自标准输入的字符进行替换、删除以及压缩。可以将一组字符变成另一组字符,因而通常也被称为转换(translate)命令
tr只能通过stdin(标准输入),而无法通过命令行参数来接受输入。
tr [options] set1 set2
将来自stdin的输入字符从set1映射到set2,并将其输出写入stdout。set1和set2是字符类或字符集。如果两个字符集的长度不相等,
那么set2会不断重复其最后一个字符,直到长度与set1相同。如果set2的长度大于set1,那么在set2中超过set1长度的那部分字符则全部
被忽略。

用tr删除字符
选项-d,可以通过指定需要被删除的字符集合,将出现在stdin中的特定字符清除掉:
[root@server1 test]# echo “Hello 123 world 345″ | tr -d ’0-9′
Hello  world

字符集补集-c
从输入文本中将不再补集中的所有字符全部删除
[root@server1 test]# echo hello 1 char 2 next 4 | tr -d -c ’0-9 \n’
1  2  4

用tr压缩字符
-s选项可以压缩输入中重复的字符:
[root@server1 test]# echo “GNU is     not UNIX. Recursive right ?” | tr -s ‘ ‘
GNU is not UNIX. Recursive right ?

[root@server1 test]# cat sum.txt
1
2
3
4
5
[root@server1 test]# cat sum.txt | echo $[$(tr '\n' '+' ) 0]
15
tr将’\n’替换成’+',因为我们得到了字符串”1+2+3+4+5+”,但是在字符串的尾部多加了一个操作符+。为了抵消这个多出的操作符,于是追加一个0.

字符类
tr可以像使用集合一样使用各种不同的字符类,如下:
alnum:字母和数字
alpha:字母
cntrl:控制(非打印)字符
digit:数字
graph:图形字符
lower:小写字母
print:可打印字符
punct:标点符号
space:空白字符
upper:大写字母
xdigit:16进制字符

6. 排序、单一与重复
sort、uniq
按数字进行排序
# sort -n file.txt

按逆序进行排序
# sort -r file.txt

按月份进行排序(按照一月、二月、三月……这样的顺序)
sort -M months.txt

根据捡或列进行排序
[root@server1 test]# cat data.txt
1 mac     2000
2 winxp   4000
3 bad     1000
4 linux   1000
-k指定了排序应该按照哪一个键(列号)来进行。
[root@server1 test]# sort -nrk 1 data.txt   # -nr表示按第一列数字逆序排序,
4 linux   1000
3 bad     1000
2 winxp   4000
1 mac     2000

[root@server1 test]# sort -k 2 data.txt
3 bad     1000
4 linux   1000
1 mac     2000
2 winxp   4000
留意用于按照数字顺序进行排序的选项-n。就依据字母表排序和依据数字排序,sort命令对于字母表排序和数字排序有不同的处理方式。因此,如果要采用数字顺序排序,应该明确的给出-n选项

uniq
uniq命令通过消除重复内容,从给定输入中(stdin或命令行参数文件)找出单一的行。他也可以用来找出输入中出现的重复行。uniq只能用于排过序的数据输入,因此,uniq要么使用管道,要么将排过序的文件作为输入,并总是以这种方式与sort命令结合起来使用。
[root@server1 test]# cat sorted.txt
bash
hack
foss
hack
hack
[root@server1 test]# uniq sorted.txt
bash
hack
foss
hack
[root@server1 test]# sort sorted.txt | uniq
bash
foss
hack
[root@server1 test]# sort -u sorted.txt
bash
foss
hack
[root@server1 test]# sort sorted.txt | uniq -u #只显示唯一的行(在输入文件中没有出现重复的行)
bash
foss
[root@server1 test]# sort sorted.txt | uniq -c  #统计各行在文件中出现的次数
1 bash
1 foss
3 hack

[root@server1 test]# sort sorted.txt | uniq -d  #找出文件中重复的行
hack

-s指定可以跳过前N个字符
-w指定用于比较的最大字符数
[root@server1 test]# cat data3.txt
u:01:gnu
d:04:linux
u:01:bash
u:01:hack
我们需要使用醒目的字符作为唯一的键。可以通过忽略前2个字符(-s 2),并使用-w选项(-w 2)指定用于比较的最大字符数的方式来选定该键。
[root@server1 test]# sort data3.txt | uniq -s 2 -w 2
d:04:linux
u:01:bash

57页-s -w -z

用uniq生成字符串样式
例如:
有一个包含重复字符的字符串,如何才能知道每个字符在字符串中出现的次数,并依照下面的格式输出字符串?
输入:ahebhaaa
输出:4a1b1e2h
# echo ahebhaaa | sed ‘s/[^.]/&\n/g’ | sed ‘/^$/d’ | sort | uniq -c | tr -d ‘ \n’
sed ‘s/[^.]/&\n/g’:在每个字符后追加一个换行符,使得每行只出现一个字符。这让我们可以用sort命令对字符进行排序。sort命令只能用于有换行符分隔的记录上
sed ‘/^$/d’:最后一个字符会被sed替换成”字符+\n”,因此会多出一个换行符并最后形成空行。这个命令就是用来删除最后的空行
tr -d ‘ \n’:将输入中的空格和换行符删除,生成所要求输出的格式

7. 分隔文件和数据
[root@server1 test]# dd if=/dev/zero bs=100k count=1 of=data.file
[root@server1 test]# split -b 10k data.file
[root@server1 test]# ls
data.file  xaa  xab  xac  xad  xae  xaf  xag  xah  xai  xaj
上面的命令将data.file分隔成多个文件,每一个文件的大小为10k。这些文件以xaa、xab、xac这样方式命名。这意味着它们都有一个字母后缀。如果想以数字为后缀,可以另外使用-d参数。此外,使用-a -length指定后缀长度。
[root@server1 test]# split -b 10k data.file -d -a 4
[root@server1 test]# ls
data.file  x0000  x0001  x0002  x0003  x0004  x0005  x0006  x0007  x0008  x0009
上面data.file分隔后文件的前缀都有”x”。我们也可以通过提供一个前缀名以使用我们自己设定的文件名前缀。
[root@server1 test]# split -b 10k data.file -d -a 4 split_file
[root@server1 test]# ls
data.file       split_file0001  split_file0003  split_file0005  split_file0007  split_file0009
split_file0000  split_file0002  split_file0004  split_file0006  split_file0008
如果不想按照数据块大小,而是需要根据行数来分隔文件的话,可以使用-l no_of——lines行数
[root@server1 test]# split -l 10 data.file

csplit:能够根据指定的条件和字符串匹配选项对log文件进行分隔,是split工具的一个变体,split只能够根据数据大小或行数分隔文件,而csplit可以根据文本自身的特点进行分隔。是否存在某个单词或文本内容都可以作为分隔文件的条件。
[root@server1 test]# 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
这个命令说明如下:
/SERVER/    用来匹配某一行,分隔过程即从此开始
/[REGEX]/   表示文本样式。包括从当前行(第一行)直到(但不包括)包含”SERVER”的匹配行
-n          指定分割后的文件名后缀的数字个数
-s          使命令进入静止模式,不打印其他信息
{*}         表示根据匹配重复执行分割,直到文件末尾为止。可以用{整数}的形式来指定分割执行的次数
-f          指定分割后的文件名前缀
-b          指定后缀格式。例如”%02d.log”,类似于C语言中printf的参数格式。在这里的文件名=前缀+后缀=server+%02d.log。
因为分割后的第一个文件没有任何内容(匹配的单词就位于文件的第一行中),所以我们删除了server00.log。

根据扩展名切分文件名
借助%操作符可以轻松将名称部分从”名称.扩展名”这种格式的文件名中提取出来
[root@server1 ~]# URL=www.linuxeye.com
[root@server1 ~]# echo ${URL%.*}   
#删除所匹配的字符串,%属于非贪婪(non-greedy)操作。它从右到左边找出匹配通配符的最短结果。%%操作符与%相似,但行为模式却是贪婪的
(greedy),这意味着它会匹配符合条件的最长的字符串
www.linuxeye
[root@server1 ~]# echo ${URL%%.*}
www
[root@server1 ~]# echo ${URL#*.}    #删除所匹配的字符串,#操作符与%类似,不过求职方向是从左向右。##操作符则用×.从左向右执行贪婪匹配。
linuxeye.com
[root@server1 ~]# echo ${URL##*.}
com
因为文件名中可能包含多个’.'字符,所以相对于#,##更适合于从文件名中提取扩展名,##执行的是贪婪匹配,因而总是能准确的提取出扩展名。

8. 交互输入自动化
[root@server1 test]# cat interactive.sh
#!/bin/bash
read -p “Enter number:” no;
read -p “Enter name:” name;
echo You have entered $no ,$name;
[root@server1 test]# echo -e “1\nhello\n” | sh interactive.sh
You have entered 1 ,hello
[root@server1 test]# echo -e “1\nhello\n” > input.data
[root@server1 test]# cat input.data
1
hello
[root@server1 test]# sh interactive.sh < input.data
You have entered 1 ,hello

expect实现自动化
参考://blog.linuxeye.com/shell/204.html
spawn        参数指定需要自动化哪一个命令
expect        参数提供需要等待的消息
send           是要发送的消息
expect eof  指明命令交互结束

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: