共计 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】