shell 迷思

Posted on September 28, 2023 (Last modified on June 19, 2024) • 4 min read • 1,801 words
Share via
 
 
 
 
 
 

shell 的一些总结体会

shell 迷思
Photo by Clement Souchet on Unsplash

不知道使用 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:tagxxx/xxx/xxxtag 变量拼接的时候,命令用是用数组变量还是字符串变量?

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 在实际操作使用的细微区别,还有输入空 :> 等一些特殊操作,都大有裨益。

当你在代码上,不知道如何进一步如何提升使用技巧、如何区别适当场景下的代码使用的时候,不妨看看自己感兴趣的,活跃社区的代码。 沉下心来,看懂这些前辈,为什么这么写。相信从中你会有不小的收获。


Comments

On this page
Follow me

I'm involved in Kubernetes coding and share developer memes.