shell 迷思
Posted on September 28, 2023 (Last modified on June 19, 2024) • 4 min read • 1,801 wordsshell 的一些总结体会
不知道使用 shell 多久了,印象里只是觉得贴合系统层面的定时任务,各种系统操作小工具,都和它密不可分。 有段时间,总觉得对 shell 的认知还比较粗浅,想继续往上通过书籍等去弥补空缺。无它,觉得需要总结和思考,继而内化一种学习能力,突破一些瓶颈。
## 头部定义全局变量 aa, 并在 test 函数中定义局部变量 aa, 执行并打印当前 aa 的输出值。
root@agub20 /tmp $ cat tt.sh
#!/bin/bash
aa=123
function test() {
local -r aa=456
echo $aa
}
test
----
root@agub20 /tmp $ pwd;ll -h|grep tt.sh
/tmp
-rw-r--r-- 1 root root **** tt.sh
起初最早接触 shell 的时候,并不太关心 shell 脚本的执行,比如:
sh /dir1/dir2/xxx.sh
/dir1/dir2/xxx.sh
./dir1/dir2/xxx.sh
"/dir1/dir2/xxx.sh"
cd /tmp; bash xxx.sh
他们到底有啥区别,那个时候,可能也不太能注意到这么仔细。只知道 shell 脚本执行有很多种方式,自己刚开始用的最多的是 ./xxx.sh 再到后面几乎只用 bash。
## shellcheck 检查 tt.sh 的语法是通过的。
root@agub20 /tmp $ shellcheck tt.sh ## apt install shellcheck
root@agub20 /tmp $
root@agub20 /tmp $ sh /tmp/tt.sh
/tmp/tt.sh: 4: Syntax error: "(" unexpected
root@agub20 /tmp $ /tmp/tt.sh
-bash: /tmp/tt.sh: Permission denied
root@agub20 /tmp $ "/tmp/tt.sh"
-bash: /tmp/tt.sh: Permission denied
root@agub20 /tmp $ cd /tmp;bash tt.sh
456
其实你会发现,shell bash 执行的兼容性是最高的,有时候为了提高 tt.sh 脚本的兼容性,需要给 tt.sh 添加一些权限,解释器也需要修改。
root@agub20 /tmp $ cat tt.sh
#!/usr/bin/env bash
## function: xxxx
## author: xxx
## last update: xxx
NUM_AA=123
function test() {
local -r NUM_AA=456
echo $NUM_AA
}
test
-----
root@agub20 /tmp $ chmod +x tt.sh ; ll -h|grep tt.sh
-rwxr-xr-x 1 root root **** tt.sh*
然后在测试一下
## shellcheck执行,检查 tt.sh 没有语法错误。
root@agub20 /tmp $ shellcheck tt.sh
root@agub20 /tmp $
root@agub20 /tmp $ sh /tmp/tt.sh
/tmp/tt.sh: 4: Syntax error: "(" unexpected
root@agub20 /tmp $ /tmp/tt.sh
456
root@agub20 /tmp $ "/tmp/tt.sh"
456
root@agub20 /tmp $ cd /tmp;bash tt.sh
456
其实你会发现,bash 执行的兼容性,始终是最好的。bash 解释器的变更,也会让脚本在不同的操作系统下的执行兼容性会更好。
#!/bin/bash ---> #!/usr/bin/env bash
当 shell 单脚本内部代码有几百行的时候,就不得不面对需要对变量进行控制,不得不使用 local、readonly 等等功能,防止变量污染。
始终 sh xxx.sh
或者 sh /dir1/dir2/xx.sh
执行会有报错。那 sh 和 bash 执行有啥区别么?
bash 拥有更多的功能和扩展,使其更强大和灵活。相比之下,sh 是一种更基本的 Shell,具有较少的功能。在脚本执行兼容性上,差距就立马体现了。
就像 Ubuntu/Debian 下 apt 更加用户友好,提供更简单的命令和更好的依赖处理,从而取代 apt-get 一样。
在社区 shell 脚本代码内部,更常见的是嵌套 "/dir1/dir2/xx.sh"
或者 /dir1/dir2/xx.sh
。
在通过阅读社区 kubernetes 下的 shell 脚本代码的时候,学习到了很多小细节。
比如获取判断当前系统下的 docker 命令,你是会用 which docker
还是 type -p docker
?
DOCKER_OPTS=${DOCKER_OPTS:-""}
其中 :-
是啥意思?
还有 +=
在shell中用在什么场景下,会比较合适?
docker pull xxx/xxx/xxx:tag
中 xxx/xxx/xxx
和 tag
变量拼接的时候,命令用是用数组变量还是字符串变量?
IFS的用法等等。诸如此类,各种各样的,配合 ChatGpt 的代码解释及搜索引擎查阅补充,发现对 shell 的很多小知识点,感觉到特别的神奇。也从中发现了自己在以往代码中的不足。 比如 declare、set、wait、return 一般场景想是不会想到使用的,或者说自己并不太清楚,到底在什么场景下适当使用?何时用命令全写还是缩写?
像是 function 参数在如下的代码内部不使用,功能也是可以实现的。加上它不只是告诉你这个是个函数,同时也是给代码加个标签,方便后续的维护。以前在写函数的时候,function 默认直接就是不写的,那个时候想着为什么要写,写不写对执行也没影响。
function test() {
***
}
类似函数加标签的还有使用 ::
参数,当工程代码较大的时候,为了区分脚本的执行逻辑和关系,添加标签,可以让代码更好读和维护。
这个部分,之前是从来不知道的。看到 vscode 编辑器上,原作者的该条记录是4年前时,才发现其实有很多东西,都还不清楚。
好似每个阶段,对 shell 的认知,都会有一些新的理解。
function kube::build::docker_push() {
***
}
到最后,为了代码的鲁棒性,需要在当下知道如何才是合适的,而不仅仅只是实现。 只为了清楚知道自己写的东西,对后面的影响,清楚自己当下在干什么。像是一把达摩利斯之剑,在脑海中悬着。
早期在 crontab 中,忽略了 root 用户下 bin 的执行路径。类似 iptables、 kubectl、ping 等命令的 binary 的执行路径并不在意的。 后面翻过几次车,才知道不同任务下的执行环境的变量是有区别的。root 下 bash 手动执行成功的内容,放在 crontab 下执行,配置不当,会提示 command not found。
通过如下命令,
GET_NUM=`xxx /dir1/dir2/xxx.sh|grep xxx|wc -l`
捕获结果的时候,对于异常捕获缺少一些认知,其实在赋予 declare -i GET_NUM,
代码的健壮性会更好一些,而非只是单纯的通过 if, elif 等方式过滤。搞清楚了 cp 、mv 、rm 在实际操作使用的细微区别,还有输入空 :>
等一些特殊操作,都大有裨益。
当你在代码上,不知道如何进一步如何提升使用技巧、如何区别适当场景下的代码使用的时候,不妨看看自己感兴趣的,活跃社区的代码。 沉下心来,看懂这些前辈,为什么这么写。相信从中你会有不小的收获。