问题
我遇到了以下代码段:
sh -c 'some shell code' sh …
(其中…
表示零个或多个附加参数)。
我知道第一个sh
是命令。我知道sh -c
应该执行提供的 shell 代码(即some shell code
)。第二个目的是什么sh
?
免责声明
类似或相关的问题有时会在答案中正确使用后作为后续问题出现,sh -c
提问者(或其他用户)想详细了解答案的工作原理。或者它可能是“这段代码做什么?”类型的更大问题的一部分。当前问题的目的是在下面提供一个规范的答案。
此处涵盖的主要问题、类似或相关问题是:
- 中的第二个
sh
是sh -c 'some shell code' sh …
什么? - 中的第二个
bash
是bash -c 'some shell code' bash …
什么? find-sh
里面有什么find . -exec sh -c 'some shell code' find-sh {} \;
?- 如果
some shell code
在一个 shell 脚本中并且我们调用./myscript foo …
, thenfoo
将被称为$1
在脚本中。但是sh -c 'some shell code' foo …
(或bash -c …
)指的foo
是$0
。为什么会出现差异? 使用
sh -c 'some shell code' foo …
wherefoo
is a "random" 参数有什么问题?尤其是:sh -c 'some shell code' "$variable"
sh -c 'some shell code' "$@"
find . -exec sh -c 'some shell code' {} \;
find . -exec sh -c 'some shell code' {} +
我的意思是我可以使用
$0
代替$1
insidesome shell code
,它不会打扰我。会发生什么坏事?
以上某些问题可能被认为是现有问题(例如这个问题)的重复(可能是跨站点重复)。我仍然没有找到旨在向想要理解的初学者解释问题的问题/答案,sh -c …
以及在高质量答案中观察到的据称无用的额外论点。这个问题填补了空白。
初步说明
sh -c 'some shell code'
直接从 shell 调用的情况很少见。实际上,如果您在一个 shell 中,那么您可能会选择使用相同的 shell(或其子 shell)来执行some shell code
. 从另一个sh -c
工具(如find -exec
.然而,这个答案的大部分都详细说明
sh -c
为一个独立的命令(它是),因为主要问题完全取决于sh
. 稍后会在一些find
看起来有用和/或有教育意义的地方使用一些示例和提示。基本答案
这是一个任意字符串。其目的是提供一个有意义的名称以在警告和错误消息中使用。在这里,
sh
但它可能是foo
,shell1
或special purpose shell
(正确引用以包含空格)。Bash 和其他符合 POSIX 标准的 shell 在
-c
. 虽然我发现POSIX 文档过于正式,无法在此处引用,但摘录man 1 bash
如下:在我们的例子
some shell code
中command_string
,第二个sh
是“之后的第一个参数”。它被分配到$0
的上下文中some shell code
。这样的错误
sh -c 'nonexistent-command' "special purpose shell"
是:并且您立即知道它来自哪个外壳。
sh -c
如果您有很多调用,这很有用。" " 之后的第一个参数command_string
可能根本不提供;在这种情况下sh
(字符串)将被分配给$0
如果外壳是sh
,bash
如果外壳是bash
。因此这些是等价的:但是,如果您需要在之后传递至少一个参数
some shell code
(即可能应该分配给的参数$1
,$2
...),那么就没有办法省略将分配给的参数$0
。差异?
通常,解释脚本的 shell 将脚本的名称(例如
./myscript
)作为参数数组中的第 0 个条目,稍后可用作$0
. 然后该名称将用于警告和错误消息。通常这种行为是完全可以的,不需要$0
手动提供。另一方面,sh -c
没有脚本可以从中获取名称。仍然一些有意义的名称是有用的,因此提供它的方式。some shell code
如果您停止将第一个参数视为代码的(某种)位置参数,差异将消失。如果some shell code
在名为的脚本中myscript
并且您调用./myscript foo …
,则等效代码sh -c
将是:这里
./myscript
只是一个字符串,它看起来像一个路径,但这个路径可能不存在;字符串可能一开始就不同。这样就可以使用相同的 shell 代码。在这两种情况下,shell 都会分配foo
给$1
。没有差异。像对待
$0
一样的陷阱$1
在许多情况下,这将起作用。虽然有反对这种方法的论据。
最明显的缺陷是您可能会从调用的 shell 中收到误导性警告或错误。请记住,它们将从
$0
shell 上下文中扩展的任何内容开始。考虑这个片段:错误是:
您可能想知道是否
foo
被视为命令。如果foo
是硬编码且唯一的,那还不错;至少你知道这个错误与foo
. 情况可能更糟:输出:
第一反应是:我的主目录怎么了?另一个例子:
可能的输出:
很容易认为有问题
/etc/fstab
; 或者想知道为什么代码要将其解释为 here-document。现在运行这些命令,看看当我们提供有意义的名称时错误有多准确:
some shell code
与脚本中的内容不同。这与上面阐述的所谓差异直接相关。这可能根本不是问题;仍然处于某种程度的shell-fu,您可能会欣赏一致性。同样,在某种程度上,您可能会发现自己喜欢以正确的方式编写脚本。然后,即使您可以侥幸使用 using
$0
,您也不会这样做,因为这不是事情应该如何工作的方式。如果要传递多个参数,或者如果事先不知道参数的数量并且需要按顺序处理它们,那么使用
$0
其中一个参数是个坏主意。$0
设计上不同于$1
or$2
。some shell code
如果使用以下一项或多项,这一事实将表现出来:$#
– 位置参数的数量不$0
考虑,因为$0
不是位置参数。$@
或$*
-"$@"
就像"$1", "$2", …
,这个序列中没有"$0"
。for f do
(等价于for f in "$@"; do
)——$0
从不分配给$f
。shift
(shift [n]
通常)——位置参数被移动,$0
保持不变。特别考虑这种情况:
你从这样的代码开始:
您注意到它
sh
每个文件运行一个。这是次优的。您知道
-exec … \;
替换{}
为一个文件名,但可能-exec … {} +
替换{}
为多个文件名。您利用后者并引入一个循环:这样的优化是一件好事。但如果你从这个开始:
并将其变成这样:
那么你将引入一个错误:来自扩展的第一个文件
{}
不会被some shell code referring "$f"
. Note使用尽可能多的参数-exec sh -c … {} +
运行sh
,但是对此有限制,如果有很多很多文件,那么一个sh
是不够的,另一个sh
进程将由find
(可能还有另一个,另一个,......)产生。对于每个sh
,您将跳过(即不处理)一个文件。要在实践中对此进行测试,请将字符串替换为
some shell code referring
并echo
在包含少量文件的目录中运行生成的代码片段。最后一个片段不会打印.
。所有这些并不意味着您根本不应该使用
$0
insome shell code
。您可以并且应该将$0
其用于其设计用途。例如,如果您想some shell code
打印(自定义)警告或错误,则让消息以$0
. 在后面提供一个有意义的名称some shell code
并享受有意义的错误(如果有的话),而不是模糊或误导性的错误。最后提示:
find … -exec sh -c …
永远不会嵌入{}
到shell 代码中。出于同样的原因
some shell code
,不应包含由当前 shell 扩展的片段,除非您真的知道扩展的值肯定是安全的。最佳做法是单引号引用整个代码(如上面的示例中,它总是'some shell code'
)并将每个非固定值作为单独的参数传递。这样的参数可以从内壳内的位置参数中安全地检索。导出变量也是安全的。运行它并分析每个的sh -c …
输出(期望的输出是foo';date'
):如果您
sh -c 'some shell code' …
在 shell 中运行,shell 将删除包含 ; 的单引号some shell code
。然后内壳(sh
)将解析some shell code
。在这种情况下,正确引用也很重要。您可能会发现这很有用:参数扩展和引号内的引号。