shell-调试手段&空格&数值计算

shell-调试手段/空格/

一. bash调试手段

1.1 echo/print

可以增加loglevel过滤一些log

_loglevel=2
 
DIE() {
    echo "Critical: $1" >&2
    exit 1
}
 
INFO() {
    [ $_loglevel -ge 2 ] && echo "INFO: $1" >&2
}
 
ERROR() {
    [ $_loglevel -ge 1 ] && echo "ERROR: $1" >&2
}

1.2 set -x

会在执行命令前,把调试信息打印出来

set -x
INFO "this is a info log"
ERROR "this is a error log"
set +x

效果如下:

+ INFO 'this is a info log'
+ '[' 2 -ge 2 ']'
+ echo -e '\033[32m INFO:\033[0m this is a info log'
 INFO: this is a info log
+ ERROR 'this is a error log'
+ '[' 2 -ge 1 ']'
+ echo -e '\033[33m ERR:\033[0m this is a error log'
 ERR: this is a error log
+ set +x

如果想全程打开 xtrace,可以在执行脚本的时候加 -x 参数

1.3 trap/bashdb

trap来指定各个sigspec应该执行的命令。
trap具体用法如下:

trap [-lp] [[arg] sigspec ...]

trap的内容包括以下五个方面:

内容 说明
signal kill可以发出的信号及0信号
EXIT EXIT 会在 shell 退出时执行指定的命令
RETURN 针对source和.每次引入都会触发
ERR 若当前 shell 中有命令执行返回非零值,则会执行与 ERR 相关联的命令
DEBUG 绑定后每一个命令执行之前,都会先执行 DEBUG 这个 trap

ERR和DEBUG默认只在当前的shell中有效,若想函数和子 shell 自动继承这些 trap,则可以设置 -T(DEBUG/RETURN) 和 -E(ERR)
例子:
shell退出时执行echo

#!/bin/bash
trap "echo this is a exit echo" EXIT
echo "this is a normal echo"

脚本中命令出错时,把命令打出来

#!/bin/bash
trap 'echo $BASH_COMMAND return err' ERR
echo this is a normal test
UnknownCmd

让脚本的命令单步执行

#!/bin/bash
trap '(read -p "[$0 : $LINENO] $BASH_COMMAND ?")' DEBUG
echo this is a test
i=0
while [ true ]
do
    echo $i
    ((i++))
done

常用的信号包括1,2,3,15
1----SIGHUP 挂起或父进程被杀死
2----SIGINT 来自键盘的中断<CTRL + C>
3----SIGQUIT 从键盘退出
15----SIGTERM 软终止
例子:在脚本捕捉到信号2后将会向用户提供一个选择,询问用户是否真的要退出。

#!/bin/sh
trap "my_exit" 1 2 3 15
my_exit() {
	echo -e "\nReceived interrupt..."
	echo "Do you wish to really exit ??"
	echo " Y: Yes"
	echo " N: No"
	echo -n "Your choice [Y..N] >"
	read ANS
	case $ANS in
		Y|y) exit 1;;
		N|n);;
	esac
}
echo -n "Enter your name: "
read NAME
echo -n "Enter your age: "
read AGE

## 二. BASH中的空格 c/c++中,`=`是一个操作符,空格是一个字分隔符,当一个表达式中出现操作符时,本身也起到分隔的作用,这时候分隔符可有可无。这时候`int i=1`和`int i = 1`都是正确的写法 bash中,`=`不是操作符,并没有分隔的作用,bash中var=test是一个词法单元ASSIGNMENT_WORD,`simple command`之前的变量赋值也是ASSIGNMENT_WORD,是合法的。比如
DEBUG=1 ./your_script

bash操作符:
||, &&, &, ;, ;;, |, |&, (, ), 换行, 重定向

var=test #把test赋值给变量var
var = test #运行命令var,其参数为 = 和 test
var= test #把环境变量var设为空字符,并运行命令test
var =test #运行命令var,其参数为=test

若没有操作符,则必须用空格或者其他blank character来分割一条语句中的字/命令

[1 -eq 1] # wrong, [ 是 BASH 内置命令,不是操作符
[ 1 -eq 1 ] # right

[[1 -eq 1]] # wrong, [[ 是 BASH 关键字,不是操作符
[[ 1 -eq 1 ]] # right

(echo this is a test) # right, ( 和 ) 是 BASH 操作符
{echo this is a test} # wrong, { 是 BASH 关键字,不是操作符
{ echo this is a test} # wrong, } 是 BASH 关键字,不是操作符
{ echo this is a test;} # right, ; 是 BASH 操作符

tr
http://www.tinylab.org/linux-command-tr-introduction/

三. 数值计算

3.1 一般整数运算

对某个数加 1,五种方式: []/(())/let/expr/awk

#=======方式一========
$ i=0;
$ ((i++))
$ echo $i
#=======方式二========
$ let i++
$ echo $i
#=======方式三========
$ i=`expr $i + 1`
#=======方式四========
$ i=`echo $i 1 | awk '{printf $1+$2}'`

shell查看命令类型,type

$ type let
let is a Shell builtin
$ type expr
expr is hashed (/usr/bin/expr)
$ type bc
bc is hashed (/usr/bin/bc)
$ type awk
awk is /usr/bin/awk

可见,let是shell内置命令,其他是外部命令,bc刚用过,已经加载到了hash表中
从1加到某个数

i=0;
while [ $i -lt 10000 ]
do
	((i++))
done
echo $i
#========可以替换如下内容========
let i++;
i=$(expr $i + 1)
i=$(echo $i+1|bc)
i=$(echo "$i 1" | awk '{printf $1+$2;}')

查看系统运行时间,使用time

$ time calc.sh
10000
real    0m1.319s
user    0m1.056s
sys     0m0.036s

(())运算符,let内置命令效率都比较高
expr,bc,awk效率比较低

3.2 模运算&幂运算&进制转换&ascii编码

(()),let,expr,bc,awk取模,运算符为%
[] (()),let求幂**, bc求幂^
求模 5%2

$ expr 5 % 2
$ let i=5%2
$ echo 5 % 2 | bc
$ ((i=5%2))

求幂 5**2

$ let i=5**2
$ ((i=5**2))
$ echo "5^2" | bc

8 进制转 10 进制
可以使用运算符也可以用bc,下面的例子是把8进制的11转成10进制的,bc的更方便

echo "obase=10;ibase=8;11" | bc -l
echo $((8#11))

ascii 字符编码
某些字符串以特定进制表示,可以使用od;
例如默认的分隔符 IFS 包括空格、 TAB 以及换行,可以用 man ascii 佐证

$ echo -n "$IFS" | od -c
0000000      t  n
0000003
$ echo -n "$IFS" | od -b
0000000 040 011 012
0000003

3.3 浮点运算&三角函数

let和expr都是整数计算,expr也能够处理字符串
浮点用bc和awk
求 1 除以 13,保留 3 位有效数字

echo "scale=3; 1/13"  | bc
echo "1 13" | awk '{printf("%0.3fn",$1/$2)}'

bc计算先指定小数,否则默认小数位0,awk比较灵活,通过printf就能够实现
bc -l是20位的小数

echo 1/13100 | bc -l
.00007633587786259541

余弦值转角度

export cos=0.996293; echo "scale=100; a(sqrt(1-$cos^2)/$cos)*180/(a(1)*4)" |bc -l
echo 0.996293 | awk '{ printf("%s\n", atan2(sqrt(1-$1^2),$1)*180/3.1415926535);}'

例子:有一组数据,存有某村所有家庭的人数和月总收入,找出人均月收入最高家庭

income.txt
1 3 4490
2 5 3896
3 4 3112
4 4 4716
5 4 4578
6 6 5399
7 3 5089
8 6 3029
9 4 6195
10 5 5145
#!/bin/bash
# gettopfamily.sh
[ $# -lt 1 ] && echo "please input the income file" && exit -1
[ ! -f $1 ] && echo "$1 is not a file" && exit -1
income=$1
awk '{
        printf("%d %0.2fn", $1, $3/$2);
}' $income | sort -k 2 -n -r

[ $# -lt 1 ]:要求用户至少收入一个参数,$# 是 Shell 中传入参数的个数
[ ! -f $1 ]:要求用户传入的参数是一个文件,-f的用法见 test 命令,man test
income=$1:把用户传入的参数赋值给 income 变量,并在后面作为awk的参数,即需要处理的文件
awk:用文件中的第三列除以第二列,求出月均收入,考虑到精确性,保留了两位有效数字。
sort -k 2 -n -r:这里对结果的 awk 结果的第二列 (-k 2),即月均收入进行排序,按照数字排序(-n),并按照递减的顺序排序(-r)。

3.4 随机数

获取一个随机数
srand在无参时,选取当前时间作为seed

$ echo $RANDOM
$ echo "" | awk '{srand(); printf("%f", rand());}'

随机产生一个从 0 到 255 之间的数字
对RANDOM变量缩放/awk中rand放大

$ expr $RANDOM / 128
$ echo "" | awk '{srand(); printf("%d\n", rand()*255);}'

如果要随机产生某个 IP 段的 IP 地址,该如何做呢?看例子:友善地获取一个可用的 IP 地址。

#!/bin/bash
# getip.sh -- get an usable ipaddress automatically
# author: falcon <zhangjinw@gmail.com>
# update: Tue Oct 30 23:46:17 CST 2007
# set your own network, default gateway, and the time out of "ping" command
net="192.168.1"
default_gateway="192.168.1.1"
over_time=2
# check the current ipaddress
ping -c 1 $default_gateway -W $over_time
[ $? -eq 0 ] && echo "the current ipaddress is okey!" && exit -1;
while :; do
        # clear the current configuration
        ifconfig eth0 down
        # configure the ip address of the eth0
        ifconfig eth0
                $net.$(($RANDOM /130 +2))
                up
        # configure the default gateway
        route add default gw $default_gateway
        # check the new configuration
        ping -c 1 $default_gw -W $over_time
        # if work, finish
        [ $? -eq 0 ] && break
done

获取一序列数

$ seq 5
1
2
3
4
5
$ seq 1 5
1
2
3
4
5
$ seq 1 2 5
1
3
5
$ seq -s: 1 2 5
1:3:5
$ seq 1 2 14
1
3
5
7
9
11
13
$ seq -w 1 2 14
01
03
05
07
09
11
13
$ seq -s: -w 1 2 14
01:03:05:07:09:11:13
$ seq -f "0x%g" 1 5
0x1
0x2
0x3
0x4
0x5

构造一些特定格式的链接,然后用 wget 下载这些内容:

$ for i in `seq -f"http://thns.tsinghua.edu.cn/thnsebooks/ebook73/%02g.pdf" 1 21`; \
do wget -c $i; done
$ for i in `seq -w 1 21`;do wget -c "http://thns.tsinghua.edu.cn/thnsebooks/ebook73/$i"; done

for和in结合可以简洁写法

for i in {1..5}; do echo -n "$i "; done

3.5 单词统计

统计字符串中各单词出现次数

$ wget -c http://tinylab.org
$ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c

统计出现次数最高的10个词

$ wget -c http://tinylab.org
$ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c | sort -n -k 1 -r | head -10

cat index.html: 输出 index.html 文件里的内容
sed -e "s/[^a-zA-Z]/\n/g": 把非字母的字符全部替换成空格,这样整个文本只剩下字母字符
grep -v ^$:去掉空行
sort: 排序
uniq -c:统计相同行的个数,即每个单词的个数
sort -n -k 1 -r:按照第一列(-k 1)的数字(-n)逆序(-r)排序
head -10:取出前十行
统计指定单词出现次数

#!/bin/bash
# statistic_words.sh
if [ $# -lt 1 ]; then
        echo "ERROR: you should input 2 words at least";
        echo "Usage: basename $0 FILE WORDS ...."
        exit -1
fi
FILE=$1
((WORDS_NUM=$#-1))
for n in $(seq $WORDS_NUM)
do
    shift
    cat $FILE | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | grep ^$1$ | uniq -c
done

if 条件部分:要求用户输入至少两个参数,第一个是需要统计单词的文件名,第二之后的所有参数是需要统计的单词。
FILE=$1: 获取文件名,即脚本之后的第一个字符串。
((WORDS_NUM=$#-1)):获取单词个数,即总的参数个数($#)减去那个文件名参数(1个)
for 循环部分:首先通过 seq 产生需要统计的单词个数序列, shift 是 Shell 内置变量(请通过 help shift 获取帮助 ) ,它把用户从命令行中传入的参数依次往后移动位置,并把当前参数作为第一个参数即 $1 ,这样通过 $1 就可以遍历用户所有输入的单词 ( 仔细一想,这里貌似有数组下标的味道 ) 。你可以考虑把 shift 之后的那句替换成 echo $1 测试 shift 的用法。

#!/bin/bash
# statistic_words.sh
if [ $# -lt 1 ]; then
        echo "ERROR: you should input 2 words at least";
        echo "Usage: basename $0 FILE WORDS ...."
        exit -1
fi
FILE=$1
((WORDS_NUM=$#-1))
for n in $(seq $WORDS_NUM)
do
    shift
    cat $FILE | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c | grep " $1$"
done

如果使用 grep 的 -E 选项,我们无须引入循环,而用一条命令就可以搞定:

 cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | grep -E "^tinylab$|^linux$" | uniq -c
     43 linux
    175 tinylab

或则

$ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | egrep  "^tinylab$|^linux$" | uniq -c
     43 linux
    175 tinylab

sed 命令可以直接处理文件,而无需通过 cat 命令输出以后再通过管道传递,这样可以减少一个不必要的管道操作,所以上述命令可以简化为

sed -e "s/[^a-zA-Z]/\n/g" index.html | grep -v ^$ | sort | egrep  "^tinylab$|^linux$" | uniq -c
     43 linux
    175 tinylab

统计单词还有个非常有用的命令 wc -w
factor可以产生某个数的所有素数
参考:http://www.tldp.org/LDP/abs/html/

posted @ 2016-08-15 21:47  zhangshihai1232  阅读(271)  评论(0)    收藏  举报