『速记』三连休,Linux Dash Shell学习心得
为什么想学一门 Linux Shell 语言?
-
想实现一个极简版本同时又有高度可移植性的 CI/CD 实现。gitlab ci 和 Jenkins的 Pipeline固然很强大;但是对于项目本身来说,XCode 自带的命令行和Android的Gradle编译命令,已经足够使用了。如果项目更复杂点,fastlane可能会是一个更好的选择;但是对于眼下的项目来说,只需要能在另一台机器上,远程编译就好了。每多一层封装,学习和维护成本就会增加;出错的可能性也会增加。
-
Linux shell应用非常广泛。App Build,AWS CLI,Docker中,多少都会用到一些。在不得不使用 Shell脚本维护的地方,如果对shell本身有了解,必然会事半功倍。
-
Linux shell 历史久远,有持久的生命力。这一点很重要。我可不想学个东西,过几年就没法用了。作为 Linux内核的基础构成,我相信,即使仅凭 Shell知识,也能找个糊口的工作。– 不够,单靠 Shell,估计就业面不太够。
-
有一些潜在的Server运维工作要做。公司团队小,养不起一个单独的运维。现在不准备,临时抱佛脚,可能就来不及了。
-
Linux Shell比较符合我对 “脚本”的预期。零前摇,所想即所写,所写即所得。高度的可移植性,写一次,到处跑,一直跑。近乎零依赖,写完拷贝到其他机器上就行。相比而下,nodejs的 node_modules的大体积,webpack编译流的高强度前摇学习成本,真的让人望而却步。如果未来有不便于 “脚本化” 的复杂逻辑,我也会优先用 Java 实现,而不是优先考虑 nodejs或者python. 我最近用 Java 写过一个小工具,写完只需要把生成的 jar 包复制到目标服务器上,真的太舒服了。相比之下,我们另一个 nodejs 工具,已经成了“不忍直视” 的老古董了:频繁的 nodejs升级,不稳定的三方库,各种时髦的 “语法”。。。与时俱进,是一个值得推崇的品质。但是,客观现实是,并不是每个有IT依赖的公司,都有充足的资源去跟进每个时代的变化。所以,我现在做技术选型,逐渐趋于保守。在能满足需要的前提下,尽可能减少不必要的中间依赖,尽可能选择相对稳定的服务或者技术。过一种受限的平淡的生活,还是追求自由,每天在各种新奇bug之间,当救火队员。。。冷暖自知。。。我知道有很多评测,但是 “时间”,始终比 “人” 更有说服力。
为什么选择 Dash, 而不是 bash 或者 zsh?
-
我最近比较青睐于debian。debian的默认Shell是 dash.
-
我不是专业运维,命令行的使用场景,并不多。所以,我不想继续深入接触 zsh. 事实上,zsh的插件,在低配Server上会引起卡顿;最近初始化Server时,我已经很少主动安装配置 zsh了。
-
bash5.0拥有更多的功能,但是一些语法上的细微差异,导致:dash脚本,几乎零修改就可以用到 bash里;但是 bash,如果想改成 dash,极有可能要做某些或大或小的修改。当然,这并不是重点。我的核心Point是:如果我无法用 Dash快速写的脚本,大概率应该考虑单独使用某个高级语言来重新编写,而不是改用另一个更强大的脚本。
-
归根结底,还是对shell脚本的定位的问题。我希望能写一些简单的胶水代码,或者做一些简单的小工具。真正牵涉到业务的,至少我肯定是优先考虑某个高级语言来实现,不然后续的可维护性,可能会出问题。
学习心得:一些很基础,但是自己以前没意识到,将来可能很有价值的东西
-
写到最前:我不是专业的Linux系统开发工程师,部分术语描述,可能不尽准确。如有疑问或者与自己预期不符合的地方,请务必查询所使用shell的对应版本的官方文档。
-
如果想明确用 dash,需要明确修改下文件开头。不然在不同的系统上,运行结果,会有点随机了。
#!/bin/dash
-
有很多直接操作标准输入和标准输出的逻辑。这是我以前看 Shell脚本感觉很吃力很困惑的核心原因之一。我总是下意识地认为 “标准输入” 类似于 “函数参数”,“标准输出”类似于“函数的返回值”。事实是,在shell中,这两种机制是同时存在的。相对于普通的高级语言中的程序开发,shell中有大量的直接基于标准输入/输出的操作。算不上复杂,但是在意识到这一点之前,真的让我非常困惑。标准输入/输出,是流式操作,而且是可以随时进行的流式操作;这和阻塞式地参数/返回值模式,有着根本的区别。当我试图从 【参数/返回值】的角度来观察某些三方库的 shell脚本时,真的是感觉难以捉摸,完全看不懂潜在的数据流动方向。
-
读取命令的命名化的命令参数,有专门的内置方法的。不用引入任何三方库。参考:https://stackoverflow.com/a/16496491
-
我发现一个调试第三方工具命令细节的方法。比如:某个命令,在 VSCode终端可以执行,但是在系统终端就是不行。显而易见,VSCode肯定默认自动做了某些操作,这时用上这个命令,就可以快速对比差异了:
export -p
- 显示进程间依赖关系的这个命令,也非常使用。特别是查一些不敢轻易乱动的历史代码的调用关系时。
pstree
- 赋予文件可执行权限的命令,非常常用。在dockfile或者其他第三方shell脚本中,也经常看到:
chmod +x ./a.sh
- 将标准输出转变为参数,传递给下一个命令的写法,也很实用。在我最初混淆了 “标准输入” 和 “命令参数” 两个概念时,也查了好大一会儿,以为是自己的 shell写的语法不对,导致无法接收前一个命令的 “标准输出”。事实是, pipline 是将前一个命令的 “标准输出” 与 后一个命令的 “标准输入” 相连接。这和通常高级语言中的基于 “返回值-》参数”的链接语法,完全不一样。我已经验证了,Linux的 pipeline可以做到流式执行,即:两个命令,本质上是一起执行的;但是可以基于 “标准输入/输出”,实现 “协同”。这和通常接触的阻塞式的 “pipeline”,完全不一样。细细想来,日常接触的 “pipeline” 概念,几乎都是 “阻塞式”的,后一个命令,必须等待前一个命令的结束。我认为这种阻塞式的 “pipeline”,称为 “模板”,更合适些吧。当然,两者的核心差异在于:阻塞式的“pipeline”,会有更大的内存和CPU需求;而非阻塞式的 “pipeline”,可以运行在较低甚至极低的硬件环境中。换言之:如果硬件性能足够,“阻塞式”和“非阻塞式”的Pipleline,可能不会有太大的处理效率的提升;但是如果硬件受限,“阻塞式”的pipeline将不得不根据硬件限制,引入分批处理的补充优化。
./a.sh | xargs ./b.sh
- 用两个命令,来给shell执行,增加更多的调试信息。我原来以为是要额外实现的,没想到只是两个设置项而已。Linux shell 本质上是文本处理和宏替换,在初期还不熟悉替换规则前,能直接看到具体的 宏替换的大致过程和结果,对调试和定位问题,是必然非常有帮助的。
# The shell writes its input to standard error as it is read. Useful for debugging
set -v
# Write each command to standard error (preceded by a ‘+ ’) before it is executed. Useful for debugging.
set -x
- 抛出异常的方法,也挺有趣的。没啥黑科技。我原以为会有某个类似 throw 的关键字。事实上,就是直接往 “标准异常输出” 里直接写入自己想抛出的错误信息。显而易见,这些异常信息,也会先于 命令的返回操作,提前被能处理 “标准异常” 的另一个命令所接收和处理. 参照:https://stackoverflow.com/questions/30078281/raise-error-in-a-bash-script
echo "Error!" 1>&2
exit 42
- 设置仅在将要执行的命令中才能使用的环境变量,也比预想中要简单。而所谓的环境变量,不过是键值对。这个特性不太起眼,但是对于我后续要写的 CI 脚本却非常使用。因为我需要屏蔽某些敏感配置项的可访问的作用域。
$ SOME_KEY='SOME-VALUE' ./a.sh
SOME-VALUE
$ echo $SOME_KEY
-
脚本中经常见到的0,1,2,分别表示:标准输入,标准输出,标准异常。一个不起眼,但是解决了我好多困惑的知识点。 详见:https://stackoverflow.com/a/7082184
-
输出多行文本,是有专门的语法的。
# 【End-of-message】可以换成任何东西,但是要开头和结尾的要一起换一下。
cat <<End-of-message
-------------------------------------
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
-------------------------------------
End-of-message
-
{ cmd } 要比 (cmd) 快一些。因为前者是在当前shell执行,后者是在子shell里执行。不过这是理论上,具体不同的shell实现,应该有不同的优化的。
-
local可以定义局部变量。目前乍看之下,shell的变量作用域有点类似于JS的词法作用域。没太细究。如果我需要深刻基于作用域来写一些复杂的控制逻辑,大概率是该考虑是不是要换高级语言,系统写一写了。
-
shell里,可以直接进行数学计算的,不过写法略有些不便:
echo $((1+1))
x=1
echo $x
x=$((x+1))
echo $x
- 需要交互式输入的命令,也可以很简单地实现自动输入特定的信息。这在写自动化脚本时,会非常有用。参见: https://stackoverflow.com/a/30564202
read -p "who am i?" NAME <<EOF
AUTO_MAN
EOF
echo $NAME
- shell 中有一组判断文件类型和文件是否存在的极简风格的类似 谓词的东西。经常见到,初看会很困惑,因为乍一看好像是一个缺失了 函数名 的不完全的写法。参见: https://stackoverflow.com/a/638980
if [ -f ./hello.sh ]; then
echo "File found!"
fi