问题
我遇到了以下代码片段:
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 …
,那么在脚本中将foo
被称为。但是(或)被称为。为什么会有差异?$1
sh -c 'some shell code' foo …
bash -c …
foo
$0
使用“随机”参数有什么问题?特别是
sh -c 'some shell code' foo …
:foo
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 …
,以及在高质量答案中观察到的所谓无用的额外论点。这个问题填补了这一空白。
答案1
初步说明
直接从 shell 调用 的情况并不常见sh -c 'some shell code'
。实际上,如果您在 shell 中,那么您可能会选择使用相同的 shell(或其子 shell)来执行some shell code
。sh -c
从另一个工具(如 )中调用 的情况很常见find -exec
。
尽管如此,这个答案的大部分内容都详细说明了sh -c
表示为独立命令(它是),因为主要问题完全取决于sh
。 后来,find
在看起来有用和/或有教育意义的地方使用了几个例子和提示。
基本答案
sh
中的第二个是什么sh -c 'some shell code' sh …
?
它是一个任意字符串。其目的是提供一个有意义的名称,用于警告和错误消息。这里是,sh
但可能是foo
,shell1
或special purpose shell
(正确引用以包含空格)。
Bash 和其他符合 POSIX 标准的 shell 在 方面以相同的方式工作-c
。虽然我发现POSIX 文档由于引用太过正式,这里不便多做介绍,但其中一段摘录man 1 bash
却十分简单:
bash [options] [command_string | file]
-c
如果-c
存在选项,则从第一个非选项参数 读取命令command_string
。如果 之后有参数command_string
,则将第一个参数分配给$0
,并将任何剩余参数分配给位置参数。分配给$0
设置 shell 的名称,该名称用于警告和错误消息。
在我们的例子中some shell code
是 ,command_string
而第二个是“之后的第一个参数”。它在 的上下文中sh
被分配给。$0
some shell code
这样,错误就来自sh -c 'nonexistent-command' "special purpose shell"
:
special purpose shell: nonexistent-command: command not found
并且您立即知道它来自哪个 shell。如果您有多次sh -c
调用,这将非常有用。“”之后的第一个参数command_string
可能根本没有提供;在这种情况下,如果 shell 是,则sh
(string) 将分配给,如果 shell 是。因此,这些是等效的:$0
sh
bash
bash
sh -c 'some shell code' sh
sh -c 'some shell code'
但是如果您需要在之后传递至少一个参数some shell code
(即可能是应该分配给$1
、$2
、... 的参数),那么就无法省略将分配给 的参数$0
。
差异?
如果
some shell code
在 shell 脚本中是 并且我们调用./myscript foo …
,那么在脚本中将foo
被称为。但是(或)被称为。为什么会有差异?$1
sh -c 'some shell code' foo …
bash -c …
foo
$0
通常,解释脚本的 shell 会将脚本的名称(例如./myscript
)作为参数数组中的第零项,稍后可用作$0
。然后该名称将用于警告和错误消息。通常这种行为完全没问题,无需手动提供$0
。另一方面,sh -c
没有脚本可从中获取名称。仍然有一些有意义的名称是有用的,因此需要提供它。
some shell code
如果您不再将 之后的第一个参数视为代码的(某种)位置参数,差异就会消失。如果some shell code
在名为 的脚本中myscript
,并且您调用./myscript foo …
,则等效代码sh -c
将是:
sh -c 'some shell code' ./myscript foo …
这里./myscript
只是一个字符串,看起来像是路径,但这个路径可能不存在;字符串可能一开始就不同。这样就可以使用相同的 shell 代码。shell 将在两种情况下分配foo
。$1
没有差异。
$0
类似治疗的陷阱$1
使用
sh -c 'some shell code' foo …
foo 为“随机”参数有什么问题? […] 我的意思是我可以使用$0
而不是$1
insidesome shell code
,这并不困扰我。会发生什么不好的事情?
在很多情况下,这种方法是有效的。不过也有人反对这种方法。
最明显的陷阱是您可能会从调用的 shell 收到误导性警告或错误。请记住,它们将以
$0
shell 上下文中扩展为的内容开头。请考虑以下代码片段:sh -c 'eecho "$0"' foo # typo intended
错误是:
foo: eecho: command not found
您可能想知道是否
foo
将其视为命令。如果是硬编码的并且唯一,情况就没那么糟糕了foo
;至少您知道错误与有关foo
,因此它会将您的注意力引向这一行代码。情况可能会更糟:# as regular user sh -c 'ls "$0" > "$1"/' "$HOME" "/root/foo"
输出:
/home/kamil: /root/foo: Permission denied
第一反应是:我的主目录怎么了?另一个例子:
find /etc/fs* -exec sh -c '<<EOF' {} \; # insane shell code intended
可能的输出:
/etc/fstab: warning: here-document at line 0 delimited by end-of-file (wanted `EOF')
人们很容易认为其中存在错误
/etc/fstab
;或者疑惑为什么代码要将其解释为此处的文档。现在运行这些命令并查看当我们提供有意义的名称时错误的准确性:
sh -c 'eecho "$1"' "shell with echo" foo # typo intended sh -c 'ls "$1" > "$2"/' my-special-shell "$HOME" "/root/foo" find /etc/fs* -exec sh -c '<<EOF' find-sh {} \; # insane shell code intended
some shell code
与脚本中的内容并不完全相同。这与上面详述的所谓差异直接相关。这可能根本不是问题;但在某种程度上,您可能会欣赏一致性。同样,在某种程度上,您可能会发现自己喜欢以正确的方式编写脚本。那么,即使您可以使用
$0
,您也不会这样做,因为这不是应该的工作方式。如果您要传递多个参数,或者如果事先不知道参数的数量,而您需要按顺序处理它们,那么使用
$0
其中一个参数不是一个好主意。在设计上与或$0
不同。 如果使用以下一项或多项,这一事实将体现出来:$1
$2
some shell code
$#
$0
– 不考虑位置参数的数量,因为$0
它不是位置参数。$@
或$*
–"$@"
就像,这个序列中"$1", "$2", …
没有。"$0"
for f do
(相当于for f in "$@"; do
) –$0
永远不会分配给$f
。shift
(shift [n]
一般来说) – 位置参数被转移,$0
保持不变。
特别考虑这种情况:
你可以从如下代码开始:
find . -exec sh -c 'some shell code referring "$1"' find-sh {} \;
您会注意到它对每个文件运行一次
sh
。这不是最理想的。您知道用一个文件名
-exec … \;
替换,但可能用许多文件名替换。您利用后者并引入一个循环:{}
-exec … {} +
{}
find . -exec sh -c ' for f do some shell code referring "$f" done ' find-sh {} +
这样的优化是一件好事。但如果你从这个开始:
# not exactly right but you will get away with this find . -exec sh -c 'some shell code referring "$0"' {} \;
并将其变成这样:
# flawed find . -exec sh -c ' for f do some shell code referring "$f" done ' {} +
那么你将引入一个错误:来自扩展的第一个文件将
{}
不会被处理some shell code referring "$f"
。注意-exec sh -c … {} +
运行sh
尽可能多的参数,但这是有限的,如果文件太多,那么一个sh
是不够的,sh
将会产生另一个进程find
(可能还有另一个,另一个,……)。每次sh
你都会跳过(即不处理)一个文件。为了实际测试这一点,请用 替换字符串
some shell code referring
,echo
并在包含少量文件的目录中运行生成的代码片段。最后一个代码片段不会打印.
。所有这些并不意味着你根本不应该使用
$0
in 。你可以并且应该使用它来做它设计的目的。例如,如果你想打印(自定义)警告或错误,那么让消息以 开头。在后面提供一个有意义的名称,并享受有意义的错误(如果有),而不是模糊或误导性的错误。some shell code
$0
some shell code
$0
some shell code
最后提示:
和
find … -exec sh -c …
切勿嵌入{}
shell 代码。出于同样的原因,
some shell code
不应包含当前 shell 扩展的片段,除非您确实知道扩展的值是安全的。最佳做法是单引号括住整个代码(如上例所示,始终为'some shell code'
),并将每个非固定值作为单独的参数传递。此类参数可从内 shell 中的位置参数安全地检索。导出变量也是安全的。运行此程序并分析每个的输出sh -c …
(所需输出为foo';date'
):variable="foo';date'" # wrong sh -c "echo '$variable'" my-sh # right sh -c 'echo "$1"' my-sh "$variable" # also right export variable sh -c 'echo "$variable"' my-sh
如果你
sh -c 'some shell code' …
在 shell 中运行,shell 将删除包含 的单引号some shell code
;然后内层 shell (sh
) 将解析some shell code
。在这种情况下,正确引用也很重要。你可能会发现这很有用:参数扩展和引号内的引号。