常用 Bash Shell 整理

2014 年 1 月 26 日

bash脚本的参数

手工的处理方式:

  • $0: 指脚本自己, 例如 test.sh.
  • $1: 第一个参数, 依次类推 $2, $3 ...
  • $#: 参数的个数.
  • $@: 所有的参数, 是一个数组.
  • $*: 所有的参数, 与$@不一样的是它是一个字符串.

一个简单的使用sample:

for arg in "$@"
do
    echo $arg
done

getopts的处理方式:

(这里只讨论短选项, 长选项需要getopt, 会复杂一些).

while getopts "a:bc" arg
do
    case $arg in
        a) echo -e "argument a: $OPTARG\n" ;;
        b) echo -e "argument b\n" ;;
        c) echo -e "argument c\n" ;;
        ?) echo -e "usage: ./test <-a **> <-b> <-c>" ;;
    esac
done

上面这个sample中:

  • "a:bc"代表了-a需要参数, b和c是无参数的选项.
  • case ? 代表了任何不识别的选项.

获取当前脚本目录

function get_pwd()
{
    echo "$( cd "$( dirname "$0" )" && pwd )";
}

遍历嵌套文件夹

function loop_dir()
{
    dir="$1";
    for sub in $dir
    do
        if [ -d "$sub" ]; then
            loop_dir $sub;
        else
            # do something
        fi
    done
}

expect的典型用法

只有 spawn 的内容才能 expect 捕获到, expect 的内容包括了: 终端的输出, EOF 和超时.

一个自动登陆的 sample:

#!/usr/bin/expect

set timeout 30
spawn ssh root@192.168.1.199 -p 22

expect {
    "(yes/no)?" {send "yes\r"}
}
expect {
    "Password:" {send "****\r"}
}
expect {
    "#" {send "cd /home/software\r"}
}

# 等待交互,如果此处不用interact,就会自动退出了
interact

再看一个 bash 中的 sample:

function scp_target()
{
    scp_src="$1";
    scp_dst="$2";
    scp_dst_passwd="$3";
    expect -c "set timeout -1;
        spawn scp -p -o StrictHostKeyChecking=no -r $scp_src $scp_dst;
        expect "*assword:*" { send $scp_dst_passwd\r\n; };
        expect eof {exit;}; "
}

进程锁

通过flock实现进程锁, 尤其在执行shell脚本时比较实用, 具体可以参考man page.

一个典型用法如下:

# non-block (fail exit with code 1), exclusive lock
flock -xn /tmp/test.lock -c test.sh

# non-block, wait 5 seconds
flock -n -w 5 /tmp/test.lock -c test.sh

绑定cpu

在某些情况下, 可以通过绑定server进程到固定的cpu, 减少context切换, 一定程度上提高性能. 这个时候需要用到taskset.

taskset的具体用法可以参照man page, 一个常见的sample如下:

taskset -c 0,2,4-6 -p 14285

定时任务

如果没有root权限, 除了crontab之外, 还可以手夯脚本来实现定时任务, 如下面的例子:

#/bin/sh
while [ 1 -eq 1 ]; do
    date >> /tmp/date.tmp
    # sleep 的单位是秒
    sleep 300
done;

数学计算

shell的数学计算, 个人最常用的有三种:

  • (())方式, 支持简单运算;
  • expr方式, 支持简单运算, 需要注意的是: expr的"|", "&", "<", "<=", ">=", ">", "*" 运算符需要做转义;
  • awk方式, 可以进行浮点运算;
#/bin/sh

var=1;

#  (())方式
echo $((var + 1));

#  expr方式, 因为转义符就是间隔号, 间隔号转义自己打印不出来, 蛋疼……
echo ``expr $var + 1``;

#  awk方式
echo "$var 1" | awk '{print($1 + $2)}';

批量修改文件名

其实是正则+sed 的应用, sample 如下:

#/bin/sh
find . -name "*target_from*" | sed 's/\(.*\)\(target_from\)\(.*\)/mv \1\2\3 \1target_to\3/' | sh

合并文件中的多行

有个 Win 风格的文本文件(\r\n换行), 想合并其中 i, i+1, i+2 行, 可以这么操作:

sed 'N;N;s/\r\n/ /g' source

统计网络连接来源

其实是 awk + sort + uniq 的应用:

#/bin/sh
netstat -anp | grep ESTABLISHED | awk '{print $5}' | awk -F ':' '{print $1}' | sort | uniq -c | sort -r

AWK

awk是一个很强大的工具, 具体的细节就不多说了, 直接贴一段代码.

#!/bin/sh

# 从日志文件中获取最后n次记录
function get_last_n()
{
    input=$1
    lastn=$2
    output=$3

    # do reverse
    awk '
        BEGIN {
            FS = "|"
            sum = 0
        }
        {
            if (NR != 1 && NR != 2)
            {
                data[sum] = $0
                sum ++
            }
        }
        END {
            for (i=sum-1; i>=0; i--)
            {
                print data[i]
            }
        }
    ' $input > $input.reverse

    # sort first n
    awk -v lastn="$lastn" '
        BEGIN {
            FS = "|"
            num = 0
        }
        {
            if (record[$2] < lastn)
            {
                result[num] = $0
                num ++
                record[$2] ++
            }
        }
        END {
            for (i=num-1; i>=0; i--)
            {
                print result[i]
            }
        }
    ' $input.reverse | sort  > $output

    rm $input.reverse
}