shell调试技巧
shell脚本虽然不像高级语言那样有专门的调试工具和调试机制,但是前辈们仍然想出了一些办法来进行shell脚本的错误检测。
trap
shell脚本在执行的时候会产生三伪信号(不是操作系统发出的信号)。我们可以使用trap捕获信号然后进行shell的调试。
shell伪信号的产生:
信号 | 产生条件 |
---|---|
EXIT | 正常退出 |
ERR | 返回非零状态码 |
DEBUG | 命令执行之前 |
trap的使用: trap commaond sig1,sig2,…
注:command使用单引号”’”包围的。
例子:
code:
#!/bin/bash
trap 'echo "the $LINENO line before executing, a is $a"' DEBUG
time=0
a=1
while (( $time<6 ))
do
let "a=a*2"
let "time++"
done
output:
$ ./two.sh
the 3 line before executing, a is
the 4 line before executing, a is
the 5 line before executing, a is 1
the 7 line before executing, a is 1
the 8 line before executing, a is 2
the 5 line before executing, a is 2
the 7 line before executing, a is 2
the 8 line before executing, a is 4
the 5 line before executing, a is 4
the 7 line before executing, a is 4
the 8 line before executing, a is 8
the 5 line before executing, a is 8
the 7 line before executing, a is 8
the 8 line before executing, a is 16
the 5 line before executing, a is 16
the 7 line before executing, a is 16
the 8 line before executing, a is 32
the 5 line before executing, a is 32
the 7 line before executing, a is 32
the 8 line before executing, a is 64
the 5 line before executing, a is 64
这种调适技巧针对递归的作用是有限的:
code
trap 'echo "the $LINENO line before executing, a is $a, b is $b"' DEBUG
a=120
b=250
gcd(){
if [ $b -eq 0 ]; then
return $a
else
t=$a
a=$b
b=`expr $t % $b`
gcd $a $b
fi
}
gcd $a $b
echo "gcd is "$
执行代码
$ ./trap.sh
the 3 line before executing, a is , b is
the 4 line before executing, a is 120, b is
the 15 line before executing, a is 120, b is 250
the 16 line before executing, a is 10, b is 0
gcd is 10
试试ERR:
code:
#!/bin/bash
trap 'echo "focus on $LINENO,"$string' ERR
string="here is a error"
ipconfig
执行脚本:
(我们将标准错误输出重定向到文件file中)
$ ./trap.sh >&2 2>file
focus on 4,here is a error
$ cat file
./trap.sh: line 4: ipconfig: command not found
tee
有关tee的解释是这样的: tee - read from standard input and write to standard output and files
tee读入数据,然后产生两支分流,一支是标准输出,另一支输出数据到文件。
一个例子:
查阅本机eth0的mac地址。
它可以用这条语句实现:
cat /etc/sysconfig/network-scripts/ifcfg-eth0 |grep HWADDR |cut -d= -f2
但是,这看起来有些复杂。。。
我们可以使用tee来跟踪流。
cat /etc/sysconfig/network-scripts/ifcfg-eth0 | tee -a txt| grep HWADDR |tee -a txt | cut -d= -f2 | tee -a txt
运行结果:
$ ./tee.sh
00:0c:29:e8:97:1b
$ cat txt
DEVICE=eth0
HWADDR=00:0c:29:e8:97:1b
TYPE=Ethernet
UUID=c5857400-a96d-451d-8983-10a52193427c
ONBOOT=no
NM_CONTROLLED=yes
BOOTPROTO=dhcp
IPADDR=192.168.137.99
NETMASK=255.255.0.0
IPV6INIT=no
USERCTL=no
HWADDR=00:0c:29:e8:97:1b
00:0c:29:e8:97:1b
调试钩子
调试钩子的思想有些像C中的条件编译。当我们需要进行中间变量的输出时打开它,否则关闭它。
shell中的调试钩子的格式一般是这样的:
DEBUG(){
if [ "$DEBUG" = "true" ]; then
...
fi
}
还是那个2^6的例子:
DEBUG(){
if [ "$DEBUG" = "true" ]; then
echo "time is $time, a is $a"
fi
}
time=0
a=1
while (( $time<6 ))
do
let "a=a*2"
let "time++"
DEBUG
done
执行脚本:
$ export DEBUG="true"; ./debug.sh
time is 1, a is 2
time is 2, a is 4
time is 3, a is 8
time is 4, a is 16
time is 5, a is 32
time is 6, a is 64
shell选项
选项名称 | 简写 | 意义 |
---|---|---|
noexec | n | 语法检验,不执行命令 |
xtrace | x | 执行每一条命令之前将命令打印出来 |
没有 | c… | 从…读取命令 |
直接使用指令sh也就是使用bash - GNU Bourne-Again SHell
他们的手册查询结果是一样的。
[edemon@CentOS ~]$ man sh > sh.txt
[edemon@CentOS ~]$ man bash > bash.txt
[edemon@CentOS ~]$ diff bash.txt sh.txt
[edemon@CentOS ~]$
例子:
echo I start to run....
if [ 1 -lt 2 ] then
echo yes
fi
[edemon@CentOS ~]$ ./err.sh
I start to run....
./err.sh: line 4: syntax error near unexpected token `fi'
./err.sh: line 4: `fi'
[edemon@CentOS ~]$ bash -n err.sh
err.sh: line 4: syntax error near unexpected token `fi'
err.sh: line 4: `fi'
[edemon@CentOS ~]$ sh -n err.sh
err.sh: line 4: syntax error near unexpected token `fi'
err.sh: line 4: `fi'
仍然以2^6的我例子来做说明:
#!/bin/bash
trap 'echo "the $LINENO line before executing, a is $a"' DEBUG
time=0
a=1
while (( $time<6 ))
do
let "a=a*2"
let "time++"
done
add -x
to run our program:
在普通语句前面加上’+’,变量已经更新。
在trap debug前面加上“++”
$ bash -x trap_debug.sh
+ trap 'echo "the $LINENO line before executing, a is $a"' DEBUG
++ echo 'the 3 line before executing, a is '
the 3 line before executing, a is
+ time=0
++ echo 'the 4 line before executing, a is '
the 4 line before executing, a is
+ a=1
++ echo 'the 5 line before executing, a is 1'
the 5 line before executing, a is 1
+ (( 0<6 ))
++ echo 'the 7 line before executing, a is 1'
the 7 line before executing, a is 1
+ let 'a=a*2'
++ echo 'the 8 line before executing, a is 2'
the 8 line before executing, a is 2
+ let time++
++ echo 'the 5 line before executing, a is 2'
the 5 line before executing, a is 2
+ (( 1<6 ))
++ echo 'the 7 line before executing, a is 2'
the 7 line before executing, a is 2
+ let 'a=a*2'
++ echo 'the 8 line before executing, a is 4'
the 8 line before executing, a is 4
+ let time++
++ echo 'the 5 line before executing, a is 4'
the 5 line before executing, a is 4
+ (( 2<6 ))
++ echo 'the 7 line before executing, a is 4'
the 7 line before executing, a is 4
+ let 'a=a*2'
++ echo 'the 8 line before executing, a is 8'
the 8 line before executing, a is 8
+ let time++
++ echo 'the 5 line before executing, a is 8'
the 5 line before executing, a is 8
+ (( 3<6 ))
++ echo 'the 7 line before executing, a is 8'
the 7 line before executing, a is 8
+ let 'a=a*2'
++ echo 'the 8 line before executing, a is 16'
the 8 line before executing, a is 16
+ let time++
++ echo 'the 5 line before executing, a is 16'
the 5 line before executing, a is 16
+ (( 4<6 ))
++ echo 'the 7 line before executing, a is 16'
the 7 line before executing, a is 16
+ let 'a=a*2'
++ echo 'the 8 line before executing, a is 32'
the 8 line before executing, a is 32
+ let time++
++ echo 'the 5 line before executing, a is 32'
the 5 line before executing, a is 32
+ (( 5<6 ))
++ echo 'the 7 line before executing, a is 32'
the 7 line before executing, a is 32
+ let 'a=a*2'
++ echo 'the 8 line before executing, a is 64'
the 8 line before executing, a is 64
+ let time++
++ echo 'the 5 line before executing, a is 64'
the 5 line before executing, a is 64
+ (( 6<6 ))
bash -c 让我们从字符串中读取命令。
$ bash -c "echo \"hello world\""
hello world
shell的内部变量:
|变量名|意义|
|LINENO|行号|
|FUNCNAME|函数数组|
|ps4|设置提示格式|
练习
codingame —— Onboarding
https://www.codingame.com/ide/6041381b0ab6222a931a6ef5a54be06f24fba59
大意:外星飞船来了,每一轮都有两只飞船向我们袭来,我们需要击毁最近的那个。
# CodinGame planet is being attacked by slimy insectoid aliens.
# <---
# Hint:To protect the planet, you can implement the pseudo-code provided in the statement, below the player.
# game loop
while true; do
# enemy1: name of enemy 1
read enemy1
# dist1: distance to enemy 1
read dist1
# enemy2: name of enemy 2
read enemy2
# dist2: distance to enemy 2
read dist2
# Write an action using echo
# To debug: echo "Debug messages..." >&2
if [ $dist1 -lt $dist2 ]; then
result=${enemy1}
else result=${enemy2}
fi
# You have to output a correct ship name to shoot ("Buzz", enemy1, enemy2, ...)
echo $result
done
codingame —— Temperatures
https://www.codingame.com/ide/6270485820e1a14078b9a510c3d65717f5ee20f
大意:给出n个气温,求出最接近0的温度。如果有一正一负都最接近0摄氏度,那么输出正整数。
注意下面字符串拆分成数组,求解整数绝对值的方法。
# Auto-generated code below aims at helping you parse
# the standard input according to the problem statement.
# n: the number of temperatures to analyse
read n
# temps: the n temperatures expressed as integers ranging from -273 to 5526
read temps
array=(${temps// / }) #(${temps//,/ })表示以逗号拆分
dis=6000
ans=0
for val in ${array[@]}; do
if [ ${val#-} -lt $dis ]; then
ans=$val
dis=${val#-}
elif [[ ${val#-} -eq $dis && $val -gt 0 ]]; then
ans=$val
fi
done
# Write an action using echo
# To debug: echo "Debug messages..." >&2
echo $ans
codingame —— ASCII Art
https://www.codingame.com/ide/627078537de52bce6cbc8fda39cbd62341ae42a
大意:
输入L,H,old_string, 英文字符表示符。
输出old_string对应的新表示符。
比如:
input
4
5
E
# ## ## ## ### ### ## # # ### ## # # # # # ### # ## # ## ## ### # # # # # # # # # # ### ###
# # # # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # # # # # # # # # # #
### ## # # # ## ## # # ### # # ## # ### # # # # ## # # ## # # # # # # ### # # # ##
# # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # ### # # # #
# # ## ## ## ### # ## # # ### # # # ### # # # # # # # # # ## # ### # # # # # # ### #
Output
###
#
##
#
###
访问字符串中的单个字符,自己暂时仅想到这样的办法:
str="ABCDEF"
echo ${#str}
for ((i=0;i<${#str};i++)); do
echo -e ${str:$i:1}'\c'
done
echo ""
[edemon@CentOS workspace]$ ./test.sh
6
ABCDEF
自己一开始使用C写了一个:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/**
* Auto-generated code below aims at helping you parse
* the standard input according to the problem statement.
**/
int main()
{
//freopen("read","r",stdin);
int L;
scanf("%d", &L);
int H;
scanf("%d", &H);
fgetc(stdin);
char T[257] = {0};
gets(T);
char str[200*1025] = {0};
int i,j,k;
for (i = 0; i < H; i++) {
char ROW[1025] = {0};
gets(ROW);
strcat(str,ROW);
}
short dex_arr[200] = {0}, len = 0, dex = 0;
for(i=0;T[i];i++){
if((T[i]>=65 && T[i]<=90) || (T[i]>=97 && T[i]<=122)){
dex = T[i]-97;
if(dex < 0) dex += 32;
}
else dex = 26;
dex_arr[len++] = dex;
}
//printf("dex len is %d, dex_arr[0] is %d\n",len,dex_arr[0]);
for(i=0;i<H;i++){
for(j=0;j<len;j++){
int s=dex_arr[j]*L;
for(k=s;k<s+L;k++){
printf("%c",str[i*27*L+k]);
}
}
puts("");
}
return 0;
}
然后再尝试用bash来写,但是有一个问题,怎么进行两个英文字符的相减操作?字母转成整数?可是怎么转?
# Auto-generated code below aims at helping you parse
# the standard input according to the problem statement.
read L
read H
read T
string=""
for (( i=0; i<H; i++ )); do
read ROW
string=$string$ROW
done
dex=()
dex_len=0
for (( i=0;${#T[@]};i++ )) do
char=${string:i:1}
#temp=`expr $char - 'A' ` #---> how can we sub two letters?
if [ $temp -lt 0 ]; then
temp=`expr $temp + 32`
fi
dex[${dex_len}]=$temp
dex_len=`expr $dex_len + 1`
done
#echo ${dex[@]}
for (( i=0;i<H;i++ )); do
for (( j=0;j<dex_len;j++ )); do
s=`expr $j * L`;
for (( k=s;k<s+L;k++ )); do
echo -e ${string[`expr $i * 27 * $L + $k`]}'\c'
done
done
echo ""
done
打印九九乘法表
for (( i=1;i<10;i++ )); do
for (( j=1;j<=i;j++ )); do
echo -e $j" * "$i" = "`expr $j \* $i`"\t\c"
done
echo ""
done
在test下创建30个目录,并赋予所有者读、写、可执行执行的权限,同组其他人员读、可执行权限,其他人员读的权限。
for (( i=1;i<=30;i++ ));do
mkdir -p ./test/dir$i
chmod -R 754 ./test/dir$i
done
ls -l ./test