当前位置: 首页 > Linux

SHELL(bash)脚本编程八:技巧

时间:2023-04-06 01:44:12 Linux

至此,我们介绍了linux系统中常用命令的使用,并简要描述了bash程序的使用和工作流程。在使用bash编写脚本程序时,掌握这些工具的用法,往往可以起到事半功倍的效果。本文将通过一些例子来尝试探索bash脚本的技巧。需要说明的是,这里的技巧是多角度求解的思路,是建立在对各种命令和bash编程技巧的深刻理解之上的。一、笔试题首先来看某公司的两道笔试题:1、编写脚本实现,可以使用shell、perl等。在/tmp目录下找到100个abc开头的文件,然后保存将这些文件的第一行添加到新文件中。分析:可以使用find查找文件名匹配某种模式的文件,但是find无法控制找到的文件个数。也许你可以使用for循环来控制它。查看文件首行的方法有很多,比如head和sed。根据以上思路编写脚本:#!/bin/bashfornamein`find/tmp-typef!-empty-name'abc*'`dohead-1$name>>new&&((i++))[[$i-eq100]]&&breakdone每次成功写入文件new中的一行内容脚本,变量i将递增。当i增加到100时,循环将立即结束。另一种方案:#!/bin/bashsed100q<(head-qn1$(find/tmp-typef-name'abc*'))>new这个方案只有一个命令,不难理解:$(find..)部分获取文件名列表,<(head...)部分获取每个文件的第一行(有关<(...)的用法请参见此处,最后是sed100q...>newgetsthefirst100Linesarewrittentofilenew.2.写个脚本实现,可以用shell,perl等将文件b中不在文件a中的所有行存为文件c,并计数c中的行数。问题没什么好分析的,直接解决办法是:#!/bin/bashcatb|whilereadlinedoif!grep-xq$linea;thenecho$line>>cfidonewc-lc脚本通过循环读取文件b中的行,对每一行进行判断。如果该行不属于文件a,则将该行的内容输出到文件c。循环结束后,用wc统计c文件的行数。另一种方案:#!/bin/bashgrep-vxfab|teec|wc-l这个方案是利用grep的-f选项,将文件a中的每一行匹配到文件b中的内容,-v表示不匹配,然后通过管道向命令tee写入文件c,然后将标准输出传递给wc命令以统计通过管道的行数。2、清除日志在使用linux服务器的过程中,由于服务运行时间较长,有时需要删除服务日志。由于日志文件正在被服务使用,不能直接删除(准确的说:即使直接删除,空间也没有释放,需要重启服务),不如使用重定向来清空文件(如:>some.log),不仅可以释放空间,而且不需要重启服务。但是当需要清除的文件比较多的时候,手动一个一个的清除就很不方便了。最好将需求写成脚本。方案一:#!/bin/bashforlogin`find/logs-name'access_*.log'`do>$idone脚本简单易懂,也可以使用命令find/logs-name'access_*.log'-execcp/dev/null{}\;.但是还是要执行一次的文件,可以一次性执行吗?方案二:#!/bin/bashfind/logs-name'access_*.log'|xargstee这个方案很巧妙的利用了xargs和tee命令一次性清除find找到的文件。3.分发假设一个大文件被不同的程序处理,并收集处理结果。通常的处理方式可能是串行处理文件,但是如果每个程序都需要很长的处理时间,串行处理就不能有效地发挥机器的性能。如果不同的处理程序在后台同时运行,像这样:catfile|command1&,catfile|command2&,catfile|command3&...这种处理可以充分发挥服务器的性能,但是其中一个它的问题是如果文件很大,内存消耗也会很大。一种方案是:#!/bin/bashcatfile|tee>(command1)>(command2)...>>(commandN)|cat这种方案的一个问题是多次处理结果是随机的,如果处理结果是要求有序(比如按照命令的顺序输出),不能满足要求。另外,tee命令的分发速率是恒定的,所以只能在处理命令中以最慢的速率进行分发,它们的输出会竞争同一个管道,在某些情况下可能会造成死锁。另一种方案:#!/bin/bash#定义处理函数f(){mkfifop{i,o}{1,2,3}command1po1&command2po2&command3po3&teepi{1,2}>pi3&catpo{1,2,3}rm-fp{i,o}{1,2,3}}cat文件|f该方案利用命名管道(见这里)对每个命令的输出进行处理、分发和汇总,然后通过cat顺序读取处理后的结果。4.并发我们在描述重定向和管道的文章中介绍了一种并发方式,下面介绍另一种方式。我们说命令替换的问题是命令立即执行,然后等待结果,此时shell无法传递输入。Bash使用称为进程替换的功能来弥补这些缺点。进程替换实际上是命令替换和管道的组合。与命令替换类似,bash运行一个命令,但让它在后台运行而不等待它完成。关键是Bash为这个命令打开了一个读写管道,绑定了一个文件名,最后展开为结果。利用进程替换这个特性,可以想到另一种并发方式:#!/bin/bash#Processing函数,假设这个函数的处理结果只有一个值sth_todo(){#需要处理第一个参数命令some_command$1}#文件数组,或者其他要处理的数据file_list=('<(sth_todo'{1..10}.log')')#expandedlike:<(sth_todo1.log)<(sth_todo2.log)<(sth_todo3.log)...#收集结果赋给数组read-aresult<<<$(evalcat"${file_list[@]}")#Outputecho"${result[@]}"脚本中需要注意的是数组的赋值和eval的使用(见这里)。当然,处理函数不一定只有一个结果。如果还有其他结果,只需要对采集结果部分做相应的修改即可。5.数组交集、并集、差集假设需要对两个数组进行交集(或并集、差集)。简单的方法无非就是两次循环比较两个数组中的每个值,得到相同的部分:#!/bin/bashlist_1=(...)list_2=(...)foriin${list_1[@]}doforjin${list_2[@]}do[["$i"=="$j"]]&&echo$idoneone让我们看看另一个解决方案:list_1=(...)list_2=(...)#重置IFS值IFS=$'\n'#Intersectiongrep-xf<(echo"${list_1[*]}")<<<"${list_2[*]}"#unionsort-u<