我正在检查一个 shell 脚本并注意到下面的命令 - exec。exec 命令执行 cmdline,但我想知道:-/bin/bash
这里的命令是什么。
cmdline="$@"
exec ${cmdline:-/bin/bash}
答案1
代码如何工作以及原因
这里有三件主要的事情:
$@
是特殊的 shell 变量,可扩展到脚本的所有命令行参数${cmdline:-/bin/bash}
是参数扩展结构之一;如果cmdline
变量未设置或为空,则整个${}
部分将被替换为符号后面的内容-
,在本例中/bin/bash
;这有点像其他编程语言中的 if 语句或三元运算符的简写(不完全是,但足以进行比较)exec
用于生成一个将超越当前进程 PID 的进程,即简单地用脚本进程替换其中的内容${}
将所有这些放在一起,代码只需获取命令行参数并运行它们,如果脚本没有命令行参数 - 您将获得交互式bash
shell。请注意,您还可以将选项传递给exec
文档中提到的 - 比较./exec_script.sh -c env
和./exec_script.sh env
。
理论上好,实践上不好
这种方法本身可能看起来很复杂,但exec
在包装脚本- 脚本在组织一切以运行实际命令之前设置环境并检查变量。此处的区别在于包装器脚本中设置命令 - 包装器脚本通常设置环境和参数以仅运行一个特定程序。
相比之下,此脚本旨在运行用户在命令行中输入的任何内容。这有一个问题因为 shell 的工作原理- 如果命令行参数包含特殊字符,您要运行的命令可能会中断。我的意思是:
# This is how it's supposed to work
$ printf 'one%stwo' $'\t'
one two
# This is how it works with unquoted parameter expansion
$ ./exec_script.sh printf 'one%stwo' $'\t'
onetwo
想象一下,如果你尝试使用该脚本运行my_cool_command filename$'\t'with$'\t'tabs.txt
; 最好的情况下 - 命令会中断,但如果你filenamewithtab.txt
当前文件夹中还有文件,my_cool_command
则会对完全错误的文件进行操作。引用参数扩展也无济于事,因为它会中断:
$ ./exec_script.sh printf 'one%stwo' $'\t'
./exec_script.sh: line 4: exec: printf one%stwo : not found
相关文件
以下是bash
(版本 4.3)手册中有关参数扩展的相关部分:
${参数:-word}
使用默认值。如果参数未设置或为空,则替换单词的扩展。否则,替换参数的值。
“特殊参数”部分:
@ 扩展为位置参数,从 1 开始。当扩展发生在双引号内时,每个参数都会扩展为一个单独的单词。也就是说,“$@”相当于“$1”“$2”……如果双引号扩展发生在单词内,则第一个参数的扩展与原始单词的开头部分连接,最后一个参数的扩展与原始单词的末尾部分连接。当没有位置参数时,“$@”和 $@ 扩展为空(即它们被删除)。
来自“Shell 内置命令”部分:
exec [-cl] [-a 名称] [命令 [参数]]
如果指定了命令,它将替换 shell。不会创建新进程。参数将成为命令的参数。如果指定了 -l 选项,shell 会在传递给命令的第零个参数开头放置一个破折号。这就是 login(1) 所做的。-c 选项导致在空环境下执行命令。如果指定了 -a,shell 会将 name 作为第零个参数传递给执行的命令。如果由于某种原因无法执行命令,非交互式 shell 会退出,除非启用了 execfail shell 选项。在这种情况下,它返回失败。如果无法执行文件,交互式 shell 会返回失败。如果未指定命令,任何重定向都会在当前 shell 中生效,返回状态为 0。如果存在重定向错误,则返回状态为 1。
结论和想法
这个脚本本身在理论上有一个很好的想法,但在实践中却很糟糕。该exec
命令在包装器脚本中运行良好,事实上,作为 Unix & Linux 网站上的顶级用户之一, 吉尔斯提到“...exec 还节省了一些内存(和其他资源,如 PID 等),因为没有必要保留一个没有其他事情要做的额外 shell”。但在这种情况下,脚本旨在重新发明轮子并做 shell 已经做得足够好的事情 - 即运行带参数的命令。