至此,咱们介绍了linux系统中经常使用命令的使用方法,简述了bash程序的使用方法和工做流程。在使用bash编写脚本程序时,熟练掌握这些工具的用法,每每可以达到事半功倍的效果。html
本文将经过讲述一些实例,试着探讨bash脚本编程的技巧。须要说明的是,这里的技巧是多角度寻求解决方案的思路,是创建在对各类命令和bash编程技法深入理解的基础之上的。linux
先来看某公司的两个笔试题:shell
一、写脚本实现,能够用shell、perl等。在目录/tmp下找到100个以abc开头的文件,而后把这些文件的第一行保存到文件new中。编程
分析:寻找名字符合某个模式的文件能够用find
,但find
不能控制寻找到的文件数量,也许能够用for
循环控制一下,查看文件的第一行有许多方法,能够用head
、sed
等。segmentfault
根据以上思路写出脚本:数组
#!/bin/bash for name in `find /tmp -type f ! -empty -name 'abc*'` do head -1 $name >> new && ((i++)) [[ $i -eq 100 ]] && break done
脚本中每次成功写入文件new
中一行内容就令变量i
自增,当i增加到100时,当即结束循环。bash
另外一种方案:服务器
#!/bin/bash sed 100q <(head -qn1 $(find /tmp -type f -name 'abc*')) >new
此方案只有一条命令,也不难理解:$(find ...)
部分得到文件名列表,<(head ...)
部分获取每一个文件的第一行(<(...)
的用法请看这里),最后sed 100q ... >new
取前100行写入文件new。并发
二、写脚本实现,能够用shell、perl等。把文件b中有的,可是文件a中没有的全部行,保存为文件c,并统计c的行数。函数
问题没什么可分析的,直接的解决方案:
#!/bin/bash cat b|while read line do if ! grep -xq $line a;then echo $line >>c fi done wc -l c
脚本经过循环读取文件b中的每一行,判断该行,若是该行不属于文件a,则输出该行内容到文件c中,循环结束后用wc
统计文件c的行数。
另外一种方案:
#!/bin/bash grep -vxf a b|tee c|wc -l
此方案利用grep
的-f
选项将文件a中的每行最为匹配模式匹配文件b的内容,-v
表示不匹配,而后经过管道交给命令tee
写入文件c中,而后在经过管道将标准输出交给wc
命令统计行数。
在使用linux服务器的过程中,随着服务的长时间运行,有时会有删除服务日志的需求。因为日志文件正在被该服务所使用,并不能直接进行删除(准确说是:即便直接删除了,空间也没有获得释放,须要将服务重启),比较好的作法是利用重定向清空该文件(如:>some.log
),既释放了空间,也不用重启服务。
但当须要清空的文件较多时,手动一个一个清空文件也有许多不方便,不如将需求写成脚本。
方案1:
#!/bin/bash for log in `find /logs -name 'access_*.log'` do >$i done
脚本很容易理解,也能够用命令find /logs -name 'access_*.log' -exec cp /dev/null {} \;
。
但还是一个文件执行一次,能不能一次性执行完呢?
方案2:
#!/bin/bash find /logs -name 'access_*.log'|xargs tee
此方案巧妙的利用了命令xargs
和tee
将find找到的文件一次性清空。
假设要对一个较大文件分别给不一样的程序处理,并收集处理结果。
一般的处理的办法多是串行的处理该文件,但若是各个程序须要较长的处理时间,串行处理将不能有效的利用机器的性能,若是不一样的处理程序在后台并发运行,相似这样:cat file|command1 &
,cat file|command2 &
,cat file|command3 &
...
这样处理能充分发挥服务器性能,但它的一个问题是,若是文件较大,对内存的消耗也会很大。
一种解决方案是:
#!/bin/bash cat file|tee >(command1) >(command2) ... > >(commandN)|cat
此种方案的一个问题是,多个处理结果是随机的,若是须要处理结果是有序的(好比按命令的顺序输出),则不能知足需求。
另外,tee
命令分发的速率是恒定的,因此只能按处理命令中最慢的速率分发,它们的输出将争用同一个管道,必定条件下,有可能形成死锁。
另外一种解决方案:
#!/bin/bash #定义处理函数 f() { mkfifo p{i,o}{1,2,3} command1 < pi1 > po1 & command2 < pi2 > po2 & command3 < pi3 > po3 & tee pi{1,2} > pi3 & cat po{1,2,3} rm -f p{i,o}{1,2,3} } cat file | f
此方案利用命名管道(见这里)处理分发及汇总各命令的输出,而后经过cat依次读取处理后的结果。
咱们在描述重定向与管道的文章中讲述过一种并发方式,下面介绍另外一种。
咱们说过,命令替换
的问题是命令的当即执行而后等待结果,此时shell没法传入输入。bash使用一个称为进程替换
的功能来弥补这些不足,进程替换
其实是命令替换
和管道
的组合,和命令替换
相似,bash运行一个命令,但令其运行于后台而再也不等待其完成。关键在于Bash为这条命令打开了一个用于读和写的管道,而且绑定到一个文件名,最后展开为结果。
利用进程替换
的这一特性,能够想到另一种并发的方式:
#!/bin/bash #处理函数,假设该函数的处理结果有且只有一个值 sth_todo() { #须要对第一个参数处理的命令 some_command $1 } #文件数组,也能够是其余待处理数据 file_list=('<(sth_todo '{1..10}.log')') #展开形如:<(sth_todo 1.log) <(sth_todo 2.log) <(sth_todo 3.log) ... #收集结果并赋值给数组 read -a result <<<$(eval cat "${file_list[@]}") #输出 echo "${result[@]}"
脚本中须要注意的地方在于数组的赋值和eval
的运用(见这里)。
固然处理函数并不必定只有惟一结果,若有其余结果,只需将收集结果部分作相应更改便可。
假定有须要取两个数组的交集(或并集、差集),简单的作法无非是两个循环对比两个数组中的每一个值,取得相同的部分:
#!/bin/bash list_1=(...) list_2=(...) for i in ${list_1[@]} do for j in ${list_2[@]} do [[ "$i" == "$j" ]] && echo $i done done
再看另外一种方案:
list_1=(...) list_2=(...) #重置IFS值 IFS=$'\n' #交集 grep -xf <(echo "${list_1[*]}") <<<"${list_2[*]}" #并集 sort -u <<EOF ${ip[*]} ${ip_[*]} EOF #差集之一 grep -vxf <(echo "${list_1[*]}") <<<"${list_2[*]}" #还原IFS IFS=$' \t\n'
bash的一些特性和经常使用命令结合使用,使本来须要许多循环代码解决的问题变得“垂手可得”。但本例中,须要重点理解的是:IFS
在数组扩展中的特性,命令grep
和sort
的运用,以及进程替换
的使用。
假如须要对大量小文件进行简单的文本替换,而文件量已达到不可一次性处理的程度(好比几百万个)。
此时若是采用通常的处理办法,例如
find . -name '*.html' -exec sed -i 's/xxxx/oooo/g' {} \;
或相似的命令,显然,这样一个文件接着一个文件串行处理将花费巨大的时间成本。
对于此类问题,须要在服务器性能和时间成本上作取舍,先给出处理方案:
#!/bin/bash #取得待处理文件数组 A=($(find . -name '*.html')) #每10000个文件后台循环处理 for((i=0;i<${#A[@]};i+=10000)) do sed -i 's@xxxx@oooo@g' ${A[@]:$i:10000} & done #等待全部进程退出 wait
此方案假定服务器资源能够随意使用,只为达到时间效率最大化。若是须要控制服务器资源消耗(主要是IO性能),能够结合这一篇,控制并发的进程数量。
关于bash的文章,至此就告一段落了。
个人博客即将同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/dev...