Kam*_*ski 8 sh shell-script positional-parameter
我遇到了以下代码段:
sh -c 'some shell code' sh …
Run Code Online (Sandbox Code Playgroud)
(其中…表示零个或多个附加参数)。
我知道第一个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 …where foois 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而不是$1inside some shell code,它不会打扰我。会发生什么坏事?
上面的一些可能被认为是现有问题(例如这个问题)的重复(可能是跨站点重复)。我仍然没有找到旨在向想要理解的初学者解释问题的问题/答案,sh -c …以及在高质量答案中观察到的据称无用的额外论点。这个问题填补了空白。
Kam*_*ski 10
sh -c 'some shell code'直接从 shell 调用是非常罕见的。实际上,如果您在 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非常简单:
Run Code Online (Sandbox Code Playgroud)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
Run Code Online (Sandbox Code Playgroud)
您会立即知道它来自哪个外壳。如果您有很多sh -c调用,这很有用。command_string可能根本不提供“”之后的第一个参数;在这种情况下sh(字符串)将被分配给,$0如果外壳是sh,bash如果外壳是bash。因此,这些是等效的:
sh -c 'some shell code' sh
sh -c 'some shell code'
Run Code Online (Sandbox Code Playgroud)
但是,如果您需要在之后至少传递一个参数some shell code(即可能是应该分配给$1, 的参数$2,...),则无法省略将分配给 的参数$0。
如果
some shell code在一个 shell 脚本中并且我们调用了./myscript foo …,那么foo将被称为$1在脚本内部。但是sh -c 'some shell code' foo …(或bash -c …)指的foo是$0。为什么会出现差异?
解释脚本的外壳程序将脚本的名称(例如./myscript)分配给$0. 然后该名称将用于警告和错误消息。通常这种行为是完全可以的,不需要$0手动提供。另一方面,sh -c没有脚本可以从中获取名称。仍然一些有意义的名称很有用,因此提供它的方式。
如果您停止将 after 的第一个参数some shell code视为代码的(某种)位置参数,则差异将消失。如果some shell code在名为 的脚本中myscript并且您调用./myscript foo …,则等效的代码sh -c为:
sh -c 'some shell code' ./myscript foo …
Run Code Online (Sandbox Code Playgroud)
这里./myscript只是一个字符串,看起来像一条路径,但这条路径可能不存在;字符串可能首先不同。这样就可以使用相同的 shell 代码。在这两种情况下,shell 都会分配foo给$1。没有差异。
$0像的陷阱$1使用
sh -c 'some shell code' foo …where foo 是“随机”参数有什么问题?[...] 我的意思是我可以使用$0而不是$1insidesome shell code,它不会打扰我。会发生什么坏事?
在许多情况下,这会奏效。不过,也有人反对这种方法。
最明显的陷阱是您可能会从调用的 shell 中得到误导性的警告或错误。请记住,它们将从$0shell 上下文中扩展到的任何内容开始。考虑这个片段:
sh -c 'eecho "$0"' foo # typo intended
Run Code Online (Sandbox Code Playgroud)
错误是:
foo: eecho: command not found
Run Code Online (Sandbox Code Playgroud)
你可能想知道是否foo被视为命令。如果foo是硬编码且唯一的,那还不错;至少你知道这个错误与 有关系foo,所以它让你注意到这行代码。情况可能更糟:
# as regular user
sh -c 'ls "$0" > "$1"/' "$HOME" "/root/foo"
Run Code Online (Sandbox Code Playgroud)
输出:
/home/kamil: /root/foo: Permission denied
Run Code Online (Sandbox Code Playgroud)
第一反应是:我的家目录怎么了?另一个例子:
find /etc/fs* -exec sh -c '<<EOF' {} \; # insane shell code intended
Run Code Online (Sandbox Code Playgroud)
可能的输出:
/etc/fstab: warning: here-document at line 0 delimited by end-of-file (wanted `EOF')
Run Code Online (Sandbox Code Playgroud)
很容易认为有问题/etc/fstab;或者想知道为什么代码要把它解释为 here-document。
现在运行这些命令,看看当我们提供有意义的名称时错误的准确程度:
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
Run Code Online (Sandbox Code Playgroud)
some shell code与脚本中的内容不同。这与上文阐述的所谓差异直接相关。这可能根本不是问题;仍然处于某种程度的 shell-fu,您可能会喜欢一致性。
同样,在某种程度上,您可能会发现自己喜欢以正确的方式编写脚本。那么即使你可以逃脱使用$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 {} \;
Run Code Online (Sandbox Code Playgroud)
您会注意到它sh每个文件运行一个。这是次优的。
你知道-exec … \;内容替换{}某个文件名,但-exec … {} +内容替换{}有可能是多个文件名。您利用后者并引入一个循环:
find . -exec sh -c '
for f do
some shell code referring "$f"
done
' find-sh {} +
Run Code Online (Sandbox Code Playgroud)
这样的优化是好事。但如果你从这个开始:
# not exactly right but you will get away with this
find . -exec sh -c 'some shell code referring "$0"' {} \;
Run Code Online (Sandbox Code Playgroud)
并将其变成这样:
# flawed
find . -exec sh -c '
for f do
some shell code referring "$f"
done
' {} +
Run Code Online (Sandbox Code Playgroud)
那么你会引入一个错误:来自扩展的第一个文件{}不会被some shell code referring "$f". Note使用尽可能多的参数-exec sh -c … {} +运行sh,但是对此有限制,如果有很多很多文件,那么一个文件sh是不够的,另一个sh进程将被find(可能还有另一个,另一个,......)产生。对于每一个,sh您将跳过(即不处理)一个文件。
要在实践中检验这种替换字符串some shell code referring用echo,并与几个文件的目录运行生成的代码片段。最后一个片段不会打印.。
所有这些并不意味着您根本不应该使用$0in some 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'):
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
Run Code Online (Sandbox Code Playgroud)
如果sh -c 'some shell code' …在 shell 中运行,shell 将删除包含some shell code; 的单引号。然后内壳 ( sh) 将解析some shell code. 在这种情况下引用正确也很重要。您可能会发现这很有用:参数扩展和引号内的引号。