一、调试脚本
调试功能是每一种编程语言都应该实现的重要特性之一,当出现一些始料未及的情况时,用它来生成脚本运行信息。调试信息可以帮你弄清楚是什么原因使得程序发生崩溃或行为异常。每位系统程序员都应该了解Bash提供的调试选项。
shell脚本调试不需要什么特殊的工具。bash自带了一些调试选项。具体选项包含:
-x : 在执行时显示参数和命令;
+x:禁止调试
-v:当命令行进行读取时显示输入;
+v:禁止打印输入。
在shell脚本启动时或者在脚本内都可以添加这些调试选项。测试脚本debug.sh,代码如下所示。
#!/bin/bash
for i in {1..6};
do
echo $i
done
echo "Script executed"
直接运行脚本: ./debug.sh。 结果如图:
在脚本内添加调试选项,使用set 命令。例如:
要开启-x选项,则在脚本内容中添加命令:set -x ,对应的set +x 是关闭调试。
#!/bin/bash
for i in {1..6};
do
set -x
echo $i
done
echo "Script executed
"
现在要看debug.sh脚本执行的调试信息,就不需要使用bash -x ./debug.sh执行了。直接./debug.sh 就可以看到调试信息。结果:
还有一种更便捷的方法,就是在脚本开头添加-xv选项:
#!/bin/bash -xv
for i in {1..6};
do
echo $i
done
echo "Script executed”
同样现在执行./debug.sh,也可打印出调试信息。
前面介绍的调试手段是Bash内建的。它们通常以固定的格式生成调试信息。但是在很多情况下,我们需要以自定义格式显示调试信息。这可以通过传递 _DEBUG环境变量来建立这类调试风格。 请看下面的代码script.sh:
#!/bin/bash
function DEBUG()
{
[ "$_DEBUG" == "on" ] && $@ || :
}
for i in {1..10}
do
DEBUG echo $i
done
可以将调试功能置为"on"来运行上面的脚本:
_DEBUG=on ./script.sh
如果不开启调试开关,就直接执行./script。
二、函数和参数
inux shell 可以用户定义函数,然后在shell脚本中可以随便调用。
shell中函数的定义格式如下:
[ function ] funname [()]
{
action;
[return int;]
}
说明:
1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)
下面的例子hanshu.sh定义了一个函数并进行调用:
只需要使用函数名就可以调用某个函数: $ fname ; #执行函数
#!/bin/bash
function fname()
{
echo "这个我的第一个函数!!!"
}
echo "---函数开始执行---"
fname
echo "---函数执行完毕---"
结果如下图:
参数可以传递给函数,并由脚本进行访问:
fname arg1 arg2 ; #传递参数
以下是函数fname的定义。在函数fname中,包含了各种访问函数参数的方法。
#!/bin/bash
fname()
{
echo $1, $2; #访问参数1和参数2echo "$@";#以列表的方式一次性打印所有参数
echo "$*"; #类似于$@,但是参数被作为单个实体 return 0; #返回值
return 0;
}
fname 1 2 3
结果如下:
$1是第一个参数。
$2是第二个参数。
$n是第n个参数。
"$@" 被扩展成 "$1" "$2" "$3"等。
"$*" 被扩展成 "$1c$2c$3",其中c是IFS的第一个字符。
"$@" 要比"$*"用得多。由于 "$*"将所有的参数当做单个字符串,因此它很少被使用。
三、将命令序列的输出读入变量
shell脚本最棒的特性之一就是可以轻松地将多个命令或工具组合起来生成输出。一个命令的输出可以作为另一个命令的输入,而这个命令的输出又会传递至另一个命令,依次类推。
这种命令组合的输出可以被存储在一个变量中。这则攻略将演示如何组合多个命令以及如何读取其输出。
(1) 先从组合两个命令开始:
ls | cat -n > out.txt
ls的输出(当前目录内容的列表)被传给cat -n,后者将通过stdin所接收到输入内容加上行号,
然后将输出重定向到文件out.txt。
(2) 我们可以用下面的方法读取由管道相连的命令序列的输出:这种方法被称为子shell。
例如:cmd_output=$(ls | cat -n)
echo $cmd_output
另一种被称为反引用(有些人们也称它为反标记)的方法也可以用于存储命令输出。
例如:cmd_output=`ls | cat -n`
echo $cmd_output
有很多种方法可以给命令分组。来看看其中的几种:
1、利用子shell生成一个独立的进程子shell本身就是独立的进程。可以使用()操作符来定义一个子shell
pwd;
(cd /bin; ls);
pwd;
当命令在子shell中执行时,不会对当前shell有任何影响;所有的改变仅限于子shell内。例如:当用cd命令改变子shell的当前目录时,这种变化不会反映到主shell环境中。
2、通过引用子shell的方式保留空格和换行符
假设我们使用子shell或反引用的方法将命令的输出读入一个变量中,可以将它放入双引号中,以保留空格和换行符(
)。例如:
out=$(cat text.txt)
echo $out
out="$(cat tex.txt)"
echo$out
四、Read函数
read是一个重要的Bash命令,它用于从键盘或标准输入中读取文本。
我们可以使用read以交互的形式读取来自用户的输入,不过read能做的可远不止这些。任何编程语言的输入库大多都是从键盘读取输入;但只有当回车键按下的时候,才标志着输入完毕。
在有些重要情形下是没法按回车键的,输入结束与否是基于字符数或某个特定字符来决定的。
例如,在一个游戏中,当 按下 + 键时,小球就会向上移动。那么若每次都要按下 + 键,然后再按回车键来确认已经按过 + 键,这就显然太低效了。
read命令提供了一种不需要按回车键就能够搞定这个任务的方法。
你可以借助read命令的各种选项来实现不同的效果。方法如下所示:
(1) 下面的语句从输入中读取n个字符并存入变量:
read -n 2 var
echo $var
(2) 用无回显的方式读取密码:
read -s var
(3) 显示提示信息:
read -p "Enter input:" var
(4) 在特定时限内读取输入:
read -t 2 var #在2秒内将键入的字符串,且键入回车后,读入变量var
(5) 用特定的定界符作为输入行的结束:
read -d ":" var
hello:#var 被设置为 hello
(6)read命令是一个一个词组地接收输入的参数,每个词组需要使用空格进行分隔;如果输入的词组个数大于需要的参数个数,则多出的词组将被作为整体为最后一个参数接收。
read firstStr secondStr
echo "第一个参数:$firstStr; 第二个参数:$secondStr"
执行测试:
./test.sh
一 二 三 四
第一个参数:一; 第二个参数:二 三 四
五、运行命令直至执行成功
在日常工作中使用shell时,有时候命令只有满足某些条件或是某种外部事件(例如文件可以被下载)操作才能够成功执行。这种情况下,你可能希望重复执行命令,直到成功为止。
重复执行命令函数:
repeat() {while :;do $@ && return; done}
修改间隔时间后再次执行,默认会不断执行:
repeat() {while :; do sleep 30; $@ && return ; done}
举例:
#!/bin/bash
repeat()
{
while :;
do
sleep 30 ;
$@ && return ;
done
}
echo "开始计时"
echo `date`
repeat pwd
echo `date`
运行脚本,等待30S后,在执行pwd。
六、字段分隔符和迭代器
内部字段分隔符(Internal Field Separator,IFS)是shell脚本编程中的一个重要概念。在处理文本数据时,它的用途可不小。我们将会讨论把单个数据流划分成不同数据元素的定界符 (delimiter)。内部字段分隔符是用于特定用途的定界符。IFS是存储定界符的环境变量。它是当前shell环境使用的默认定界字符串。
1、查看IFS的值
echo “$IFS"
直接输出IFS是看不到值的,转化为二进制就可以看到了:
echo "$IFS"|od -b
输出结果:
0000000 040 011 012 012
0000004
"040"是空格,"011"是Tab,"012"是换行符"
" 。最后一个 012 是因为 echo 默认是会换行的。
2、IFS的默认值为空白字符(换行符、制表符或者空格)。
当IFS被设置为逗号时,shell将逗号视为一个定界符,因此变量$item在每次迭代中读取由逗号分隔的子串作为变量值。 如果没有把IFS设置成逗号,那么下面的脚本会将全部数据作为单个字符串打印出来。实例:
#!/bin/bash
data="name,sex,rollno,location"
old_IFS=$IFS
IFS=","
for item in $data;
do
echo Item: $item
done
IFS=$old_IFS
输出结果:
Item: name
Item: sex
Item: rollno
Item: location
七、比较与测试
程序中的流程控制是由比较语句和测试语句处理的。Bash同样具备多种与Unix系统级特性相兼容的执行测试的方法。我们可以用if、if else以及逻辑运算符进行测试,用比较运算符来比较数据项。除此之外,还有一个test命令也可以用于测试。
来看看用于比较和测试的各种方法:
1、if条件
if condition;
then
commands; fi
2、else if和else
if condition;
then
commands;
else if condition; then
commands;
else
commands; fi
if和else语句可以进行嵌套。if的条件判断部分可能会变得很长,但可以用逻辑运算符将它变得简洁一些:
[ condition ] && action; # 如果condition为真,则执行action;
[ condition ] || action; # 如果condition为假,则执行action。
&&是逻辑与运算符,||是逻辑或运算符。编写Bash脚本时,这是一个很有用的技巧。
1、算数比较
条件通常被放置在封闭的中括号内。一定要注意在[或]与操作数之间有一个空格。如果
忘记了这个空格,脚本就会报错。
对变量或值进行算术条件判断:
[ $var -eq 0 ] #当$var 等于0时,返回真
[ $var -ne 0 ] #当$var 为非0时,返回真
其他重要的操作符如下所示:
-gt:大于。
-lt:小于。
-ge:大于或等于。
-le:小于或等于。
可以按照下面的方法结合多个条件进行测试:
[ $var1 -ne 0 -a $var2 -gt 2 ] #使用逻辑与-a
[ $var1 -ne 0 -o var2 -gt 2 ] #逻辑或 -o
脚本实例:
#!/bin/bash
var=133
if [ $var -ne 0 ]
then
echo True
else
echo Flase
fi
很明显,结果返回True.
2、字符串比较
使用字符串比较时,最好用双中括号,因为有时候采用单个中括号会产生错误,所以最好避开它们。
可以用下面的方法检查两个字符串,看看它们是否相同。
⚠️注:注意在 = 前后各有一个空格。如果忘记加空格,那就不是比较关系了,而变成了赋值语句。
[[ $str1 = $str2 ]]:当str1等于str2时,返回真。也就是说,str1和str2包含的文本是一模一样的。
[[ $str1 == $str2 ]]:这是检查字符串是否相等的另一种写法。也可以检查两个字符串是否不同。
[[ $str1 != $str2 ]]:如果str1和str2不相同,则返回真。
[[ $str1 > $str2 ]]:如果str1的字母序比str2大,则返回真。
[[ $str1 < $str2 ]]:如果str1的字母序比str2小,则返回真。
[[ -z $str1 ]]:如果str1包含的是空字符串,则返回真。
[[ -n $str1 ]]:如果str1包含的是非空字符串,则返回真。
脚本实例:
#!/bin/bash
str1=abcd
str2=dfgh
if [[ $str1 = $str2 ]]
then
echo True
else
echo Flase
fi
很明显,结果返回Flase.
使用逻辑运算符 && 和 || 能够很容易地将多个条件组合起来。 见实例:
#!/bin/bash
str1=abcd
str2=dfgh
if [[ $str1 = $str2 ]] && [[ $str1 != $str2 ]]
then
echo True
else
echo Flase
fi
test命令可以用来执行条件检测。用test可以避免使用过多的括号。见实例:
if [ $var -eq 0 ]; then echo "True"; fi
也可以写成:
if test $var -eq 0 ; then echo "True"; fi
3、文件系统相关测试
我们可以使用不同的条件标志测试不同的文件系统相关的属性。
[ -f $file_var ]:如果给定的变量包含正常的文件路径或文件名,则返回真。
[ -x $var ]:如果给定的变量包含的文件可执行,则返回真。
[ -d $var ]:如果给定的变量包含的是目录,则返回真。
[ -e $var ]:如果给定的变量包含的文件存在,则返回真。
[ -c $var ]:如果给定的变量包含的是一个字符设备文件的路径,则返回真。
[ -b $var ]:如果给定的变量包含的是一个块设备文件的路径,则返回真。
[ -w $var ]:如果给定的变量包含的文件可写,则返回真。
[ -r $var ]:如果给定的变量包含的文件可读,则返回真。
[ -L $var ]:如果给定的变量包含的是一个符号链接,则返回真。
脚本实例:
#!/bin/bash
fpath="/etc/passwd"
if [ -e $fpath ]; then
echo File exists;
else
echo Does not exist;
fi
以上,未完待续~~