大家好,我是张晋涛。
提到 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】