`sh -c 'some shell code' sh` 中的第二个 sh 是什么?

`sh -c 'some shell code' sh` 中的第二个 sh 是什么?

问题

我遇到了以下代码片段:

sh -c 'some shell code' sh …

(其中表示零个或多个附加参数)。

我知道第一个sh是命令。我知道sh -c应该执行提供的 shell 代码(即some shell code)。第二个的目的是什么sh


免责声明

类似或相关的问题有时会在正确使用答案后作为后续问题出现sh -c,提问者(或其他用户)希望详细了解答案的工作原理。或者它可能是“此代码有什么用?”这类更大问题的一部分。当前问题的目的是在下面提供规范答案。

这里涉及的主要问题、类似或相关的问题包括:

  1. sh中的第二个是什么sh -c 'some shell code' sh …
  2. bash中的第二个是什么bash -c 'some shell code' bash …
  3. find-sh里面有什么find . -exec sh -c 'some shell code' find-sh {} \;
  4. 如果some shell code在 shell 脚本中是 并且我们调用./myscript foo …,那么在脚本中将foo被称为。但是(或)被称为。为什么会有差异?$1sh -c 'some shell code' foo …bash -c …foo$0
  5. 使用“随机”参数有什么问题?特别是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而不是$1inside some shell code,这不会困扰我。会发生什么不好的事情?

上述某些问题可能被视为现有问题的重复(可能是跨站点重复)(例如这个)。我仍然没有找到一个旨在向想要理解的初学者解释这个问题的问题/答案sh -c …,以及在高质量答案中观察到的所谓无用的额外论点。这个问题填补了这一空白。

答案1

初步说明

直接从 shell 调用 的情况并不常见sh -c 'some shell code'。实际上,如果您在 shell 中,那么您可能会选择使用相同的 shell(或其子 shell)来执行some shell codesh -c从另一个工具(如 )中调用 的情况很常见find -exec

尽管如此,这个答案的大部分内容都详细说明了sh -c表示为独立命令(它是),因为主要问题完全取决于sh。 后来,find在看起来有用和/或有教育意义的地方使用了几个例子和提示。


基本答案

sh中的第二个是什么sh -c 'some shell code' sh …

它是一个任意字符串。其目的是提供一个有意义的名称,用于警告和错误消息。这里是,sh但可能是fooshell1special 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被分配给。$0some 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 是。因此,这些是等效的:$0shbashbash

sh -c 'some shell code' sh
sh -c 'some shell code'

但是如果您需要在之后传递至少一个参数some shell code(即可能是应该分配给$1$2、... 的参数),那么就无法省略将分配给 的参数$0


差异?

如果some shell code在 shell 脚本中是 并且我们调用./myscript foo …,那么在脚本中将foo被称为。但是(或)被称为。为什么会有差异?$1sh -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而不是$1inside some shell code,这并不困扰我。会发生什么不好的事情?

在很多情况下,这种方法是有效的。不过也有人反对这种方法。

  1. 最明显的陷阱是您可能会从调用的 shell 收到误导性警告或错误。请记住,它们将以$0shell 上下文中扩展为的内容开头。请考虑以下代码片段:

    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
    
  2. some shell code与脚本中的内容并不完全相同。这与上面详述的所谓差异直接相关。这可能根本不是问题;但在某种程度上,您可能会欣赏一致性。

  3. 同样,在某种程度上,您可能会发现自己喜欢以正确的方式编写脚本。那么,即使您可以使用$0,您也不会这样做,因为这不是应该的工作方式。

  4. 如果您要传递多个参数,或者如果事先不知道参数的数量,而您需要按顺序处理它们,那么使用$0其中一个参数不是一个好主意。在设计上与或$0不同。 如果使用以下一项或多项,这一事实将体现出来:$1$2some shell code

    • $#$0– 不考虑位置参数的数量,因为$0它不是位置参数。

    • $@$*"$@"就像,这​​个序列中"$1", "$2", …没有。"$0"

    • for f do(相当于for f in "$@"; do) –$0永远不会分配给$f

    • shiftshift [n]一般来说) – 位置参数被转移,$0保持不变。

    特别考虑这种情况:

    1. 你可以从如下代码开始:

      find . -exec sh -c 'some shell code referring "$1"' find-sh {} \;
      
    2. 您会注意到它对每个文件运行一次sh。这不是最理想的。

    3. 您知道用一个文件名-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 referringecho并在包含少量文件的目录中运行生成的代码片段。最后一个代码片段不会打印.

    所有这些并不意味着你根本不应该使用$0in 。你可以并且应该使用它来做它设计的目的。例如,如果你想打印(自定义)警告或错误,那么让消息以 开头。在后面提供一个有意义的名称,并享受有意义的错误(如果有),而不是模糊或误导性的错误。some shell code$0some shell code$0some 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。在这种情况下,正确引用也很重要。你可能会发现这很有用:参数扩展和引号内的引号

相关内容