bash 当我运行 `sh -c "command"` 时是否创建了一个子 shell、一个新的 shell 或这些都没有?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23951346/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 10:34:25  来源:igfitidea点击:

Is there a subshell created when I run `sh -c "command"`, a new shell or none of these?

bashcommand-line

提问by Radu R?deanu

In bash, when I run the following command:

在 bash 中,当我运行以下命令时:

sh -c "command"

is there created a subshell and then the commandis executed?

有没有创建一个子shell然后command执行?

My guess is that the command will run in the current shell, but I'm not sure. This guess come from the fact that I already tested using the following commands:

我的猜测是该命令将在当前 shell 中运行,但我不确定。这个猜测来自我已经使用以下命令测试过的事实:

echo $BASHPID, $BASH_SUBSHELL

and

sh -c "echo $BASHPID, $BASH_SUBSHELL"

and the results are the same. But this could be a little misleading as someone told me because the variables may be substituted before the command is executed. Is this the truth?

结果是一样的。但这可能有点误导,因为有人告诉我,因为在执行命令之前可能会替换变量。这是事实吗?

采纳答案by Hastur

I think that with examples it's possible to understand the situation
(in my case shis a symbolic link to /bin/dash).
I did this tests for sh:

我认为通过示例可以理解这种情况
(在我的情况下sh是指向 的符号链接/bin/dash)。
我做了这个测试sh

echo $$ ; sh -c 'echo $$ ; sh -c '"'"'echo $$'"'"' '
16102
7569
7570

Three different PID, three different shell. (If there are different shells, there is not a subshellspawn).

三种不同PID,三种不同shell。(如果有不同的外壳,则不会产生外壳)。



In a similar way for BASH

以类似的方式为 BASH

echo $$ $BASHPID, $BASH_SUBSHELL ; bash -c 'echo $$  $BASHPID $BASH_SUBSHELL  ; bash -c '"'"'echo  $$ $BASHPID $BASH_SUBSHELL '"'"' '
16102 16102, 0
10337 10337 0
10338 10338 0

Three different $BASHPIDno different $BASH_SUBSHELL(see note below for differences between $$and $BASHPID).
If we were in a subshellthat do not require to be reinitialized, then $$and $BASHPIDshould be different.
In the same way $BASH_SUBSHELLis not incremented, it is always 0. So 2 clues to say again that no new subshellare spawned, we have only new shells.

三个不同$BASHPID没有不同$BASH_SUBSHELL(见下面的注释$$和之间的区别$BASHPID)。
如果我们在一个不需要重新初始化的子 shell中,那么$$$BASHPID应该是不同的。
以同样的方式$BASH_SUBSHELL不递增,它总是0。所以 2 条线索再次说明没有产生新的外壳,我们只有新的外壳



From man bash(4.2.45(1)-release) I report some pertinent parts about when a subshellis spawned:

man bash(4.2.45(1)-release)我汇报一下一些相关部件时,子shell是催生

  1. Each command in a pipelineis executed as a separate process (i.e., in a subshell).

  2. If a command is terminated by the control operator &, the shell executes the command in the background in a subshell.The shell does not wait for the command to finish, and the return status is 0.

    Commands separated by a ; are executed sequentially; the shell waits for each command to terminate in turn. The return status is the exit status of the last command executed. ...

  3. (list )list is executed in a subshellenvironment
    { list; } list is simply executed in the current shell environment.

  4. A coprocessis a shell command preceded by the coprocreserved word. A coprocess is executed asynchronously in a subshell...

  5. $Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.

  1. 管道中的每个命令都作为单独的进程执行(即在子外壳中)。

  2. 如果命令由控制运算符&终止,shell将在子 shell 的后台执行该命令shell 不等待命令完成,返回状态为 0。

    以 ; 分隔的命令 依次执行;shell 依次等待每个命令终止。返回状态是最后执行的命令的退出状态。...

  3. (list )list 在子shell环境中执行
    { list; list 只是在当前 shell 环境中执行。

  4. 协进程是由前面外壳命令coproc保留字。协进程在子shell中异步执行......

  5. $扩展为shell的进程 ID 。在 ()子shell 中,它扩展为当前shell的进程ID ,而不是子shell

Notes:

注意事项

  • BASHPIDExpands to the process ID of the current bash process. This differs from $$under certain circumstances, such as subshells that do not require bash to be re-initialized.
  • BASH_SUBSHELLIncremented by one each time a subshell or subshell environment is spawned. The initial value is 0.

  • For the differences between the use of single quote ''an double quote ""you can see this question. Let we remember only that if you write the commands within double quote""the variables will be evaluated via parameter expansion from the original shell, if extquoteis enabled as it is by default from shopt.(cfr. 4.3.2 The shopt builtinin the Bash Reference Manual)

    *extquote*If set, $'string' and $"string" quoting is performed within ${parameter} expansions enclosed in double quotes. This option is enabled by default.

  • BASHPID扩展到当前 bash 进程的进程 ID。这与$$某些情况下不同,例如不需要重新初始化 bash 的子 shell。
  • BASH_SUBSHELL每次产生子外壳或子外壳环境时增加 1。初始值为 0。

  • 对于单引号''和双引号的使用区别""你可以看这个问题。让我们只记住,如果您在双引号内编写命令,""则变量将通过来自原始shell 的参数扩展进行评估,如果extquote被启用,因为它是从shopt. (参见 Bash 参考手册中的4.3.2 内置的 shopt

    *extquote*如果设置,$'string' 和 $"string" 引用在 ${parameter} 用双引号括起来的扩展中执行。默认情况下启用此选项。

For further references you may find useful e.g.

对于进一步的参考,您可能会发现有用,例如

回答by Henk Langeveld

I would say not.

我会说不是。

A subshell is typically reserved for any instance where the shell process needs to perform an operation in an isolated environment, but with access to (a copy of) the current state of the shell, including all global and local variables.

子外壳通常保留给外壳进程需要在隔离环境中执行操作的任何实例,但可以访问外壳的当前状态(的副本),包括所有全局和局部变量。

Examples:

例子:

  • Pipelines: echo x | read y
  • Command substitution: line=$( read < file && echo "$REPLY" )
  • 管道: echo x | read y
  • 命令替换: line=$( read < file && echo "$REPLY" )

In most cases, this would result in the shell process forking itself.

在大多数情况下,这会导致 shell 进程自己分叉。

In recent updates of ksh93, some subshells may not actually fork a new process.

在 ksh93 的最近更新中,一些子 shell 可能实际上不会派生一个新进程。

The crux of the matter is that a subshell is always created implicitly.

问题的关键在于子shell 总是隐式创建的。

Running sh -c ...will create a newshell process, which will discard most of the state and start from scratch. That means that normal (local, global) shell variables are gone. Naturally, the new shell will have a copy of all exported variables.

运行sh -c ...将创建一个新的shell 进程,该进程将丢弃大部分状态并从头开始。这意味着普通的(本地的、全局的)shell 变量已经消失了。自然地,新 shell 将拥有所有导出变量的副本。



A different interpretation of the question could be Does the -coption fork a new process for running ...?Answer: No. It doesn't. However, certain commands passed into the -cargument may require the shell to spawn a subshell, just as when they'd be part of a script, or typed interactively.

对该问题的不同解释可能是-c选项是否分叉了一个新的运行进程...答案:不。它没有。但是,传递到-c参数中的某些命令可能需要 shell 生成子 shell,就像它们是脚本的一部分或交互式键入一样。

回答by konsolebox

I made a check of it as well and no, I don't think it (-c) summons a subshell. If we refer to shitself, the yes shis a shell that gets summoned, but not if it's about a subshell within shitself. We can verify this by running a command like:

我也对它进行了检查,不,我不认为它 ( -c) 会调用子外壳。如果我们提到sh它自己,yessh是一个被召唤的外壳,但如果它是关于sh自身内部的子外壳,则不是。我们可以通过运行如下命令来验证这一点:

# bash -c 'pstree -p; echo Value of PPID in Callee: $PPID; echo Callee PID: $BASHPID / $$'; echo "Caller PID: $$"
bash(28860)───bash(17091)───pstree(17092)
Value of PPID in Callee: 28860
Callee PID: 17091 / 17091
Caller PID: 28860

As you can see the called shell (17091) and the caller shell (28860) are both connected directly as child-parent. There's nothing in between them. $BASHPIDand $$are even the same, in which case should be different if you're on a subshell. This just tells that there's no subshell summoned when calling commands with -c.

如您所见,被调用的外壳 (17091) 和调用者外壳 (28860) 都作为子父直接连接。他们之间什么都没有。$BASHPID$$甚至相同,其中,如果你在一个子shell情况下应该是不同的。这只是说明在使用-c.

There's only one special behavior to this, and that is when summoning a single external binary e.g.:

对此只有一种特殊行为,那就是在召唤单个外部二进制文件时,例如:

# bash -c 'pstree -p'; echo "Caller PID: $$"
bash(28860)───pstree(17124)
Caller PID: 28860

There bash saves itself from forking and decided to just directly exec() the only command. You might think that perhaps bash always does that to the last command if the command refers to an external executable binary, but no it doesn't:

bash 避免了分叉并决定直接 exec() 唯一的命令。您可能会认为,如果命令引用外部可执行二进制文件,那么 bash 可能总是对最后一个命令执行此操作,但事实并非如此:

# bash -c 'echo Value of PPID in Callee: $PPID; echo Callee PID: $BASHPID / $$; pstree -p'; echo "Caller PID: $$"
Value of PPID in Callee: 28860
Callee PID: 17128 / 17128
bash(28860)───bash(17128)───pstree(17129)
Caller PID: 28860

Now about

现在关于

echo $BASHPID, $BASH_SUBSHELL

and

sh -c "echo $BASHPID, $BASH_SUBSHELL"

and the results are the same.

回声 $BASHPID, $BASH_SUBSHELL

sh -c "echo $BASHPID, $BASH_SUBSHELL"

结果是一样的。

It should be the same if echo $BASHPID, $BASH_SUBSHELLis executed in the same shell since "echo $BASHPID, $BASH_SUBSHELL"is first expanded by the shell that interprets the command before it's passed as an argument to sh. If BASHPIDis let's say 28860and BASH_SUBSHELL0, then the expanded value of "echo $BASHPID, $BASH_SUBSHELL"is 'echo 28860, 0'in which case the command would actually be sh -c 'echo 28860, 0'. The proper way to this actually is to use a single quote to allow interpretation only within the context of the new called shell: sh -c 'echo $BASHPID, $BASH_SUBSHELL', although I'm not really sure if the command itself would be helpful for testing.

如果echo $BASHPID, $BASH_SUBSHELL在同一个 shell 中执行,它应该是相同的,因为"echo $BASHPID, $BASH_SUBSHELL"在将命令作为参数传递给sh. 如果BASHPID是假设28860BASH_SUBSHELL0,然后扩展后的值"echo $BASHPID, $BASH_SUBSHELL"'echo 28860, 0'在这种情况下,命令实际上是sh -c 'echo 28860, 0'。正确的方法实际上是使用单引号允许仅在新调用的 shell: 的上下文中进行解释sh -c 'echo $BASHPID, $BASH_SUBSHELL',尽管我不确定该命令本身是否有助于测试。

So basically what I'm saying is that the test echo $BASHPID, $BASH_SUBSHELL+ sh -c "echo $BASHPID, $BASH_SUBSHELL"doesn't prove anything if -csummons a subshell or not and that the guy who told you that it could mislead since the variables may be substituted is correct.

所以基本上我要说的是,测试echo $BASHPID, $BASH_SUBSHELL+sh -c "echo $BASHPID, $BASH_SUBSHELL"并不能证明是否-c调用子外壳程序,并且告诉您它可能会误导您的人是正确的,因为变量可能会被替换。

Nevertheless, my own test showed that Bash really doesn't summon a subshell with it (-c).

尽管如此,我自己的测试表明 Bash 确实没有用它 ( -c) 来调用子shell 。

回答by slayedbylucifer

Check this:

检查这个:

$ name=foo
$ echo $name
foo
$ sh -c "echo $name"
foo
$ sh -c 'echo $name'

$ 

You need 'in stead of "in your command. Else $namewill get evaluated to foobefore execution

你需要'代替"你的命令。否则$namefoo在执行前评估为

And to answer your question, yes, it does create/spawn a new shell.

并回答您的问题,是的,它确实创建/生成了一个新外壳。

回答by Chris

sh will spawn a subshell. But the subshell id stays the same

sh 将产生一个子shell。但是子外壳 ID 保持不变

21934 pts/0    Ss     0:00 -bash
21963 pts/0    S      0:00  \_ /usr/bin/sudo -u root -H /bin/bash -c  export AUDITUSER=ch002854; cd $HOME && exec -a '-bash' /bin/bash
22031 pts/0    S      0:00      \_ -bash
 2969 pts/0    S      0:00          \_ sleep 1000
 2993 pts/0    S      0:00          \_ sleep 1000
 3726 pts/0    R+     0:00          \_ ps af

With sh -c it will just run the command. This will reinitialize your environment variables and therefore it resets $BASH_SUBSHELL to its default 0.

使用 sh -c 它只会运行命令。这将重新初始化您的环境变量,因此它将 $BASH_SUBSHELL 重置为其默认值 0。

# sh -c 'echo $BASHPID, $BASH_SUBSHELL'
12671, 0

While with `` or () you can create a subshell

使用 `` 或 () 可以创建子shell

# (echo $BASHPID, $BASH_SUBSHELL)
13214, 1

回答by F. Hauri

Regardless bash

不管bash

Running this simple line will show you a lot:

运行这个简单的行会告诉你很多:

$ echo $$;sh -c 'echo $$;ps axfw | grep -B2 $$'
14152
12352
14147 ?        S      0:00 xterm
14152 pts/4    Ss     0:00  \_ bash
12352 pts/4    S+     0:00      \_ sh -c echo $$;ps axfw | grep -B2 $$
12353 pts/4    R+     0:00          \_ ps axfw
12354 pts/4    S+     0:00          \_ grep -B2 12352

This is clearly a childbut what's a subshell?

这显然是一个孩子,但什么是子外壳

Well, my current running shell pid is 14152

好吧,我当前运行的 shell pid 是 14152

$ echo $$ $BASHPID $BASH_SUBSHELL 
14152 14152 0
$ (echo $$ $BASHPID $BASH_SUBSHELL)
14152 12356 1
$ ( (echo $$ $BASHPID $BASH_SUBSHELL) )
14152 12357 2

Well nice: BASHPIDis a dynamicvariable which take alway the value of executing pid:

很好:BASHPID是一个动态变量,它始终采用执行 pid 的值:

$ echo $BASHPID | sed $a$BASHPID | sed $a$BASHPID
12371
12372
12373

Hmmm where is my current working pid?

嗯,我当前的工作 pid 在哪里?

$ sed "$a$BASHPID $$" < <(echo "$BASHPID $$"| sed "$a$BASHPID $$")
12386 14152
12387 14152
14152 14152

Well I've find them!

好吧,我找到了!

$ echo $BASHPID $$;(echo $BASHPID $$;ps axfw | grep -B3 $BASHPID)
14152 14152
12469 14152
14152 pts/4    Ss     0:00  \_ bash
12469 pts/4    S+     0:00      \_ bash
12470 pts/4    R+     0:00          \_ ps axfw
12471 pts/4    S+     0:00          \_ grep -B3 12471

Conclusion

结论

When you

当你

$ echo $BASHPID $$ $BASH_SUBSHELL;(echo $BASHPID $$ $BASH_SUBSHELL)
14152 14152 0
12509 14152 1

use parenthesis or pipe(|), you will create subshellwhich is a forked child of a running shell.

使用括号或管道( |),您将创建外壳,它是正在运行的外壳的分叉子外壳。

But when you

但是当你

$ echo $BASHPID $$ $BASH_SUBSHELL;bash -c 'echo $BASHPID $$ $BASH_SUBSHELL'
14152 14152 0
12513 12513 0

You will burn a child, running a shellinterpreter, so it's a subshell, but not linked to his parent.

你会烧掉一个child,运行一个shell解释器,所以它是一个subshel​​l,但没有链接到他的父母。

Nota: If the child is not linked to his parent, they use same I/O files descriptors. So if you close your window (pts/4in my run), you will send a SIGHUPto all process using them.

注意:如果孩子没有链接到他的父母,他们使用相同的 I/O 文件描述符。因此,如果您关闭窗口(pts/4在我的运行中),您将向所有使用它们的进程发送一个SIGHUP

回答by Dave Hildebrandt

In this answer I'm thinking of subshell the same way as I think of a subprocess.

在这个答案中,我以与思考子流程相同的方式思考子外壳。

First off, don't be confused by variable expansion. If you write a variable in double quotes, it will be expanded by the calling shell, not the sh or its -c command. So

首先,不要被变量扩展所迷惑。如果你用双引号写一个变量,它将被调用的 shell 扩展,而不是 sh 或其 -c 命令。所以

sh -c "echo $$" 

gives you the PID of the invoking shell, because it expands $$ before it invokes sh, whereas

为您提供调用 shell 的 PID,因为它在调用 sh 之前扩展 $$,而

sh -c 'echo $$' 

gives you the PID of the sh command that has been invoked, because the single quotes tell the invoking shell to pass the string $$ to sh unchanged.

为您提供已调用的 sh 命令的 PID,因为单引号告诉调用 shell 将字符串 $$ 传递给 sh 不变。

That said, the general answer to your question is:

也就是说,您的问题的一般答案是:

  1. The sh command is definitely a subshell, invoked by the parent shell when it sees 'sh'
  2. When you note that the argument to -c is treated by it as a shell script, then you see that you'll get a grand-subshell of the invoked sh, whenever a regular shell script would get one.
  1. sh 命令绝对是一个子 shell,当它看到 'sh' 时由父 shell 调用
  2. 当您注意到 -c 的参数被它视为一个 shell 脚本时,您就会看到您将获得所调用 sh 的一个大子shell,只要常规的 shell 脚本会得到一个。

More on item 2: Some shell builtins create subshells; others do not. The sh man page talks about this; check out the "if" builtin, or constucts that use backquotes ``. Usage of pipes also causes subshells. You can also deliberately force a subshell by enclosing commands in parentheses. However, enclosing commands in braces {} does NOT of itself cause a subshell.

关于第 2 项的更多信息:一些 shell 内置函数创建子 shell;其他人没有。sh 手册页谈到了这一点;检查“if”内置函数,或使用反引号``的构造函数。管道的使用也会导致子壳。您还可以通过将命令括在括号中来故意强制使用子 shell。但是,将命令括在大括号 {} 中本身不会导致子shell。

sh is designed to run commands, so in most cases you will have subshells. Notable exceptions are the 'case' builtin, variable assignment, option setting, and the . command.

sh 旨在运行命令,因此在大多数情况下,您将拥有子 shell。值得注意的例外是 'case' 内置函数、变量赋值、选项设置和 . 命令。