共计 4810 个字符,预计需要花费 13 分钟才能阅读完成。
大家好,我是张晋涛。
提到 Shell 大家想必不会太生疏,咱们通常认为 Shell 是咱们和零碎交互的接口,执行命令返回输入,比方 bash、zsh 等。偶然也会有人把 Shell 和 Terminal(终端)混同,但这和本文关系不大,暂且略过。
作为一名程序员,咱们可能天天都会用到 Shell,偶然也会把一些命令组织到一起,写个 Shell 脚本之类的,以便晋升咱们的工作效率。
然而在看似简略的 Shell 脚本中,可能暗藏着很深的坑。这里我先给出两段简略且类似的 Shell 脚本,大家无妨来看看这两段代码的输入是什么:
#!/bin/bash
set -e -u
i=0
while [$i -lt 6]; do
echo $i
((i++))
done
答案是只会输入一个 0。
#!/bin/bash
set -e -u
let i=0
while [$i -lt 6]; do
echo $i
((i++))
done
答案是没有任何输入,间接退出。
如果你能解释分明下面两段代码输入后果的话,那大略你能够跳过这篇文章后续的内容了。
我先来合成下这段代码中波及到的次要知识点。
变量申明
变量申明有很多种方法,然而其行为却各有不同。
咱们必须先有个根底意识:Bash 没有类型零碎,所有变量都是 string。 基于这个起因,如果是让变量进行算术运算时,不能像在其余的编程语言中那样间接写算术运算符。这会让 bash 解释为对 string 的操作,而不是对数字的操作。
间接申明
(MoeLove)➜ ~ foo=1+1
(MoeLove)➜ ~ echo $foo
1+1
间接申明最简略,但正如后面提到的,间接申明会默认当作 string 进行解决,不能在申明时进行算术运算。
declare 申明
(MoeLove)➜ ~ declare foo=1+1
(MoeLove)➜ ~ echo $foo
1+1
除去间接申明变量外,比拟罕用的办法是用 declare
来申明变量,但默认状况下,其申明的变量都是按 string 解决的,无奈进行失常的算术运算。
declare 整数属性
declare 在申明变量的时候,能够通过 -i
参数减少整数属性,当变量被赋值时,将进行算术运算。
(MoeLove)➜ ~ declare -i bar=1+1
(MoeLove)➜ ~ echo $bar
2
但要留神的是,减少整数属性后,如果将字符串赋值给它,则会呈现解析失败的状况,即:将值设置为 0:
(MoeLove)➜ ~ bar=test
(MoeLove)➜ ~ echo $bar
0
let 申明
另一种方法,咱们能够通过 let
命令进行变量的申明,这种形式容许在申明时进行算术运算,同时也反对将其余值赋值给此变量。
(MoeLove)➜ ~ let baz=1+1
(MoeLove)➜ ~ echo $baz
2
(MoeLove)➜ ~ baz=moelove.info
(MoeLove)➜ ~ echo $baz
moelove.info
while 循环
while list-1; do list-2; done
Bash 中 while 语法就是这样,在 while 关键字后是一个序列(list),能够是一个或多个表达式 / 语句,
须要留神的是,当 list-1 返回值为 0 时,list-2 总是会被执行,并且 while 语句最初的返回值是 list-2 最初一次执行的返回值,或者,如果没执行任何语句的话,则返回 0。
bash 中的算数计算
这部分的内容大家想必常会用到。我来介绍几种罕用的办法:
算术扩大
Bash 中的扩大一共有 7 种,算术扩大只是其中之一。具体而言就是通过相似 $((expression))
这样的模式,来计算表达式的值。例如:
(MoeLove)➜ ~ echo $((3+7))
10
(MoeLove)➜ ~ x=3;y=7
(MoeLove)➜ ~ echo $((x+y))
10
expr 命令
expr 是 coreutils 软件包提供的一个命令,可对表达式进行计算,或者比拟大小之类的。
(MoeLove)➜ ~ x=3;y=7
(MoeLove)➜ ~ expr $x + $y
10
# 比拟大小
(MoeLove)➜ ~ expr 2 \< 3
1
(MoeLove)➜ ~ expr 2 \< 1
0
bc 命令
按定义来说,bc 其实是一种反对任意精度和可交互执行的计算语言。它比上述提到的 expr
要弱小的多,尤其是它还反对浮点数运算。例如:
个别浮点数计算
(MoeLove)➜ ~ echo "scale=2;7/3"|bc
2.33
(MoeLove)➜ ~ echo "7/3"|bc
2
留神:scale
须要手动指定,它示意小数点后的位数。默认状况下 scale
的值为 0。
内置函数
bc 还有一些内置函数,能够不便咱们进行一些疾速的计算,比方能够利用 sqrt()
疾速的计算平方根。
(MoeLove)➜ ~ echo "scale=2;sqrt(9)" |bc
3.00
(MoeLove)➜ ~ echo "scale=2;sqrt(6)" |bc
2.44
脚本
此外,bc 还反对一种简略的语法,能够反对申明变量,编写循环和判断语句等。例如:咱们能够打印 20 以内能够被 3 整除的数:
(MoeLove)➜ ~ echo "for(i=1; i<=20; i++) {if (i % 3 == 0) i;}" |bc
3
6
9
12
15
18
bash 的调试
其实 bash shell 中并没有内置调试器。很多状况下,都是采纳反复运行加打印来进行调试。但这种形式不够高效。
这里介绍一种比拟直观的,也比拟不便的用来调试 shell 代码的方法。以下是一段示例 shell 代码。
(MoeLove)➜ ~ cat compare.sh
#!/bin/bash
read -p "请输出任意数字:" val
real_val=66
if ["$val" -gt "$real_val"]
then
echo "输出值大于等于预设值"
else
echo "输出值比预设值小"
fi
为其减少执行权限,或者应用 bash 执行:
(MoeLove)➜ ~ bash compare.sh
请输出任意数字: 33
输出值比预设值小
具体模式
通过减少 -v
选项,即可开启具体模式,用于查看所执行的命令。当然,咱们也能够通过在 shebang 上间接减少 -v
选项,或者减少 set -v
来开启此模式
(MoeLove)➜ ~ bash -v compare.sh
which () { ( alias;
eval ${which_declare} ) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@"
}
#!/bin/bash
read -p "请输出任意数字:" val
请输出任意数字: 33
real_val=66
if ["$val" -gt "$real_val"]
then
echo "输出值大于等于预设值"
else
echo "输出值比预设值小"
fi
输出值比预设值小
应用 xtrace 模式
咱们能够通过减少 -x
参数来进入 xtrace 模式,用于调试执行阶段的变量值。
(MoeLove)➜ ~ bash -x compare.sh
+ read -p '请输出任意数字:' val
请输出任意数字: 33
+ real_val=66
+ '[' 33 -gt 66 ']'
+ echo 输出值比预设值小
输出值比预设值小
辨认未定义变量
以下示例中,我成心写错一个字符。执行脚本后,你会发现没有任何报错,但后果并不是咱们预期的。这类可能是手误居多,所以咱们须要查看是否存在未绑定的变量。
(MoeLove)➜ ~ cat add.sh
#!/bin/bash
five=5
ten=10
total=$((five+tne))
echo $total
(MoeLove)➜ ~ bash add.sh
5
(MoeLove)➜ ~ bash -u add.sh
add.sh: line 4: tne: unbound variable
减少 -u
选项,能够查看变量是否未定义 / 绑定。
组合应用
以上是几种比拟常见的应用形式,当然,也能够把它进行组合应用。比方下面的变量未定义的问题,组合应用 -vu
就能够间接看到具体呈现问题的代码是什么内容了。
(MoeLove)➜ ~ bash -vu add.sh
which () { ( alias;
eval ${which_declare} ) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@"
}
#!/bin/bash
five=5
ten=10
total=$((five+tne))
add.sh: line 4: tne: unbound variable
将调试信息输入到指定文件
这里我关上了一个特定 FD 上的 debug.log
文件,留神这个 FD 须要与 BASH_XTRACEFD
配置的统一,另外我批改了 PS4
的变量内容,它的默认值是 +
看起来会比拟乱,而且没有无效信息,我通过设置 PS4='$LINENO:'
让它显示行号。
而后在须要调试的地位设置 set -x
,在完结的未知设置 set +x
,这样调试日志中就只会记录我须要调试局部的日志了。
(MoeLove)➜ ~ cat compare.sh
#!/bin/bash
exec 6> debug.log
PS4='$LINENO:'
BASH_XTRACEFD="6"
read -p "请输出任意数字:" val
real_val=66
set -x
if ["$val" -gt "$real_val"]
then
echo "输出值大于等于预设值"
else
echo "输出值比预设值小"
fi
set +x
echo "End"
(MoeLove)➜ ~ bash compare.sh
请输出任意数字: 88
输出值大于等于预设值
End
(MoeLove)➜ ~ cat debug.log
8: '[' 88 -gt 66 ']'
10: echo $'\350\276\223\345\205\245\345\200\274\345\244\247\344\272\216\347\255\211\344\272\216\351\242\204\350\256\276\345\200\274'
14: set +x
这里介绍了 通过 set 设置选项 的形式较简略,其余的比方应用 trap 加调试的形式也举荐大家去尝试下,这里就不开展了。
回到开始的问题
那咱们用刚从介绍的调试办法来执行下结尾的两个脚本,并且进行问题的解答。
第一个
(MoeLove)➜ ~ bash -xv demo1.sh
#!/bin/bash
set -e -u
+ set -e -u
i=0
+ i=0
while [$i -lt 6]; do
echo $i
((i++))
done
+ '[' 0 -lt 6 ']'
+ echo 0
0
+ ((i++))
从上述调试后果能够看到,这个脚本在输入 0
而后执行完 ((i++))
后退出。为什么呢?次要是因为在脚本顶部减少的 set -e
选项。
该选项在遇到首个 0 值的时候会间接退出。 咱们来解释下:
(MoeLove)➜ ~ i=0
(MoeLove)➜ ~ echo $((i++))
0
(MoeLove)➜ ~ echo $i
1
能够看到,执行 ((i++))
后,输入其实是 0,所以触发了 set -e
的退出条件,脚本便退出了。
第二个
(MoeLove)➜ ~ bash -xv demo2.sh
#!/bin/bash
set -e -u
+ set -e -u
let i=0
+ let i=0
第二个和第一个的最次要区别在于变量的赋值上,let i=0
的返回值是 0,所以也就会触发 set -e
的退出条件了。咱们尝试将第二个脚本批改下,再次执行:
[tao@moelove ~]$ cat demo2-1.sh
#!/bin/bash
set -e -u
let i=1
while [$i -lt 6]; do
echo $i
((i++))
done
[tao@moelove ~]$ bash demo2-1.sh
1
2
3
4
5
将 let i=0
批改成 let i=1
即可按预期执行胜利。
总结
本篇中,咱们次要聊了 bash shell 中的变量申明,循环,数学运算以及 bash shell 的调试。是否对你有所启发呢?欢送留言进行交换。
- 注:本文仅探讨 Bash Shell
欢送订阅我的文章公众号【MoeLove】