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

至此,咱们介绍了linux系统中经常使用命令的使用方法,简述了bash程序的使用方法和工做流程。在使用bash编写脚本程序时,熟练掌握这些工具的用法,每每可以达到事半功倍的效果。html

本文将经过讲述一些实例,试着探讨bash脚本编程的技巧。须要说明的是,这里的技巧是多角度寻求解决方案的思路,是创建在对各类命令和bash编程技法深入理解的基础之上的。linux

一、笔试题

先来看某公司的两个笔试题:shell

一、写脚本实现,能够用shell、perl等。在目录/tmp下找到100个以abc开头的文件,而后把这些文件的第一行保存到文件new中。编程

分析:寻找名字符合某个模式的文件能够用find,但find不能控制寻找到的文件数量,也许能够用for循环控制一下,查看文件的第一行有许多方法,能够用headsed等。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

此方案巧妙的利用了命令xargstee将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在数组扩展中的特性,命令grepsort的运用,以及进程替换的使用。

六、大量数据处理

假如须要对大量小文件进行简单的文本替换,而文件量已达到不可一次性处理的程度(好比几百万个)。
此时若是采用通常的处理办法,例如

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...

相关文章
相关标签/搜索