Shell Script Command Dispatching: From Risky eval to Secure case and Map Patterns
1、前言
Linux 内核与用户之间需要一个交互界面,这个界面就像包裹着内核的“壳”(Shell)。
Shell 本质上是命令解释器,它读取用户输入的命令,将其转换为系统调用,是用户与内核沟通的桥梁。最常用的 Shell 实现之一是 Bash。
Linux 系统通过 Shell 及其脚本来进行大量工作的管理和命令的执行,赋予其强大威力的同时,也引来很多隐患。有些隐患虽然早已知晓,但由于历史代码、兼容考量等原因,一直保持下来了。
在 Bash 脚本中可以通过 function 定义函数进行后续调用和分发,但实践中,可能有人会直接使用 eval 来执行,但其实这是有安全隐患,还有更好的方式。
2、$@ 与 $* 的区别
这两个变量都是 Shell 脚本的内置变量,用来表示脚本运行时所有的参数,但是如果给它加上双引号就会有所不同。
先看一个例子:
但执行测试时:
如果这两个变量不加双引号,不在单词拆分的特殊上下文中,一般没有什么区别,都会把会把带空格参数看作多个参数进行处理。
如果这两个变量加上双引号,那么 $@ 可以正常识别带空格的参数,而 $* 会把所有参数变成一个字符串。
因此,如果是为了获取参数列表后原样传递给其他命令、函数或进行循环遍历,那么更安全的方式使用 "$@" ,记得带上双引号。
只有当确实需要将所有参数变成一个字符串时,才使用 "$*" ,记得也要带上双引号。
另外,$* 可以使用内部字段分隔符IFS拼接不同字符串:
3、不建议:使用 eval 分发函数、命令
先看例子:
执行情况:
这种不加限制的执行 eval 命令,虽然省事、方便,但 eval 的根本风险在于它将字符串作为代码执行。
如果用户输入类似 print; rm -rf / 或使用了命令替换 $(cat /etc/passwd),这些被注入的恶意指令会被直接执行,导致数据泄露或系统破坏。
4、推荐(少量命令):使用 case 分发
举例:
执行情况:
5、有大量命令时:进行映射分发
举例:
运行情况:
6、PIPESTATUS:获取管道命令退出状态
PIPESTATUS 是 Bash 特有的一个内置数组变量,用于存储最近一次执行的前台管道命令中,每个独立命令的退出状态。它的主要作用是解决在使用管道时,常规变量 $? 只能获取管道中最后一个命令退出状态的问题。
它有两个特性:
- 只要执行新的命令,不管有没有使用到管道命令,其值就会被覆盖
- 只能获取前台管道命令的退出状态,对于后台运行的命令无法获取
举例:
在这个例子中,需要通过 tee 命令将日志保存,因此使用了管道命令。
为了获取命令执行后的退出状态,第一时间通过 result=${PIPESTATUS[0]} 保存起结果,可以看到后续的 PIPESTATUS 不断被刷新。
7、总结
如果使用了管道命令,通过 PIPESTATUS 来获取每一个管道命令的退出状态。
如果要进行命令分发,要谨慎使用 eval ,它安全不可控。命令较少时,使用 case,多了就使用映射分发。