五.shell脚本的跟踪与调试
最近在看《鸟哥的LINUX私房菜 基础学习篇》,目前看到了shell脚本这一章,打算在这里简单记录一下这一整章的学习过程和相关的知识点。
参考资料:《鸟哥的LINUX私房菜 基础学习篇》第四版 第十二章
实验环境: Ubuntu18.04
这一小节是shell脚本系列的最后一节内容。在前面的四个小节里,我们学习了编写shell脚本的基本语法,利用之前的知识可以编写复杂的脚本。在实践中,我们常常需要对脚本进行调试,寻找出错的位置和原因。在这一节里,我们学习如何跟踪和调试shell脚本。
在执行脚本前,我们可以直接使用bash的相关参数判断脚本是否有问题。
bash [ -nvx ] scripts.sh
#参数说明:
-n: 不执行脚本,只检查语法错误(没有错误时不显示任何信息)
-v: 在执行前,将脚本的内容显示到屏幕上
-x:将使用到的脚本内容显示在屏幕上
- 简单的示例
# + 符号后面的内容为脚本内容,通过显示使用到的脚本内容可以方便的知道当前执行到哪一个指令
(base) laifeng@laifeng-X6:~/bin$ bash -x show_animal.sh
+ for animal in dog cat elephant
+ echo 'There are dogs...'
There are dogs...
+ for animal in dog cat elephant
+ echo 'There are cats...'
There are cats...
+ for animal in dog cat elephant
+ echo 'There are elephants...'
There are elephants...
到这里,我们已经了解了关于shell脚本的全部内容。下面通过一个实际的脚本,来了解之前的内容都是怎样在实际中被运用的。
下面的脚本来自于github中tf-fater-rcnn-master中的脚本文件,如果是做计算机视觉领域相关领域的同学肯定听说过目标检测网络faster rcnn。为了方便,一些指令的功能已经进行了注释。
- 示例:test_faster_rcnn.sh
#!/bin/bash
#设置bash的输入和输出的环境
set -x #-x 在命令执行前,会显示命令内容(前面有++符号)
set -e #-e 告诉bash如果任何语句的执行结果不是true则应该退出,防止错误像滚雪球般变大导致一个致命的错误
export PYTHONUNBUFFERED="True"
GPU_ID=$1
DATASET=$2
NET=$3
array=( $@ )#定义数组,数组的元素为执行脚本时输入的参数
len=${#array[@]} #固定用法,获取数组元素的个数
EXTRA_ARGS=${array[@]:3:$len} #取下标为3开始的所有元素
EXTRA_ARGS_SLUG=${EXTRA_ARGS// /_} #变量替换 ${value//pattern/string}将value中与pattern匹配的部分替换成string,这里将 替换成_
#case分支判断
case ${DATASET} in
pascal_voc)
TRAIN_IMDB="voc_2007_trainval"
TEST_IMDB="voc_2007_test"
ITERS=70000
ANCHORS="[8,16,32]"
RATIOS="[0.5,1,2]"
;;
pascal_voc_0712)
TRAIN_IMDB="voc_2007_trainval+voc_2012_trainval"
TEST_IMDB="voc_2007_test"
ITERS=110000
ANCHORS="[8,16,32]"
RATIOS="[0.5,1,2]"
;;
coco)
TRAIN_IMDB="coco_2014_train+coco_2014_valminusminival"
TEST_IMDB="coco_2014_minival"
ITERS=490000
ANCHORS="[4,8,16,32]"
RATIOS="[0.5,1,2]"
;;
*)
echo "No dataset given"
exit
;;
esac
LOG="experiments/logs/test_${NET}_${TRAIN_IMDB}_${EXTRA_ARGS_SLUG}.txt.`date +'%Y-%m-%d_%H-%M-%S'`" #'命令' 执行命令相当于$(命令)
exec &> >(tee -a "$LOG")
echo Logging output to "$LOG"
set +x #关闭,与set -x对应
if [[ ! -z ${EXTRA_ARGS_SLUG} ]]; then #-z 判断字符串是否为空,为空时返回true;
NET_FINAL=output/${NET}/${TRAIN_IMDB}/${EXTRA_ARGS_SLUG}/${NET}_faster_rcnn_iter_${ITERS}.ckpt
else
NET_FINAL=output/${NET}/${TRAIN_IMDB}/default/${NET}_faster_rcnn_iter_${ITERS}.ckpt
fi
set -x
if [[ ! -z ${EXTRA_ARGS_SLUG} ]]; then
CUDA_VISIBLE_DEVICES=${GPU_ID} time python ./tools/test_net.py \
--imdb ${TEST_IMDB} \
--model ${NET_FINAL} \
--cfg experiments/cfgs/${NET}.yml \
--tag ${EXTRA_ARGS_SLUG} \
--net ${NET} \
--set ANCHOR_SCALES ${ANCHORS} ANCHOR_RATIOS ${RATIOS} \
${EXTRA_ARGS}
else
CUDA_VISIBLE_DEVICES=${GPU_ID} time python ./tools/test_net.py \
--imdb ${TEST_IMDB} \
--model ${NET_FINAL} \
--cfg experiments/cfgs/${NET}.yml \
--net ${NET} \
--set ANCHOR_SCALES ${ANCHORS} ANCHOR_RATIOS ${RATIOS} \
${EXTRA_ARGS}
fi
在这个脚本中可能使用到了一些我们不知道的指令,但是通过查阅相关指令的功能,可以发现整个脚本还是很简单的。我们慢慢的来分析。
首先,#!/bin/bash
是第一节内容,脚本的开头指定使用的shell程序。接下的是一些对bash的设置,可以先不管。
然后是GPU_ID=$1
DATASET=$2
NET=$3
这是第二节中关于脚本的默认参数的内容,2...是执行脚本时传入的参数。
接下来又是一堆看不懂的指令,没关系,不影响继续阅读。
我们看接下来的一大段,不就是case...esac条件判断,也是很简单的内容。
case ${DATASET} in
pascal_voc)
TRAIN_IMDB="voc_2007_trainval"
...
;;
pascal_voc_0712)
TRAIN_IMDB="voc_2007_trainval+voc_2012_trainval"
...
;;
coco)
TRAIN_IMDB="coco_2014_train+coco_2014_valminusminival"
...
;;
*)
echo "No dataset given"
exit
;;
esac
最后的两大段,就是简单的if...else..fi条件判断。
if [[ ! -z ${EXTRA_ARGS_SLUG} ]]; then #-z 判断字符串是否为空,为空时返回true;
NET_FINAL=output/${NET}/${TRAIN_IMDB}/${EXTRA_ARGS_SLUG}/${NET}_faster_rcnn_iter_${ITERS}.ckpt
else
NET_FINAL=output/${NET}/${TRAIN_IMDB}/default/${NET}_faster_rcnn_iter_${ITERS}.ckpt
fi
...
整个脚本看完,是不是感觉还是很简单的?当然,其中的一些没见过的指令还是需要一个个耐心的查阅相关资料,但是整体来说,shell脚本还是很简单而功能强大的。
想要了解更多关于shell脚本的内容?欢迎参考Linux shell脚本 :)