Assign and use of a variable in the same subshell

2020-02-07 01:22发布

问题:

I was doing something very simple like: v=5 echo "$v" and expected it to print 5. However, it does not. The value that was just set is not available for the next command.

I recently learnt that "In most shells, each command of a pipeline is executed in a separate SubShell". However, in this case both commands are being executed in the same subshell.

Why does this happen? Is there any way to have this working?

Full example:

$ v=1
$ v=5 echo "$v"
1                   # I expected 5!

回答1:

Let's look to the POSIX specification to understand why this behaves as it does, not just in bash but in any compliant shell:


2.10.2, Shell Grammar Rules

From rule 7(b), covering cases where an assignment precedes a simple command:

If all the characters preceding '=' form a valid name (see the Base Definitions volume of IEEE Std 1003.1-2001, Section 3.230, Name), the token ASSIGNMENT_WORD shall be returned. (Quoted characters cannot participate in forming a valid name.)

[...]

Assignment to the NAME shall occur as specified in Simple Commands.

Thus, parsing this assignment is required for a POSIX-compliant shell.


2.9.1, Simple Commands

  1. Redirections shall be performed as described in Redirection.

  2. Each variable assignment shall be expanded for tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal prior to assigning the value.

[...]

If no command name results, variable assignments shall affect the current execution environment. Otherwise, the variable assignments shall be exported for the execution environment of the command and shall not affect the current execution environment (except for special built-ins). If any of the variable assignments attempt to assign a value to a read-only variable, a variable assignment error shall occur. See Consequences of Shell Errors for the consequences of these errors.

Thus: An assignment given in part of the prefix to a simple command must be exported, and must not impact the "current shell environment", unless the shell being invoked is a special built-in. Moreover, these steps shall follow redirections, which by nature must occur late in the command invocation process.


2.12, Shell Execution Environment

Utilities other than the special built-ins (see Special Built-In Utilities) shall be invoked in a separate environment that consists of the following. The initial value of these objects shall be the same as that for the parent shell, except as noted below.

[...]

Variables with the export attribute, along with those explicitly exported for the duration of the command, shall be passed to the utility environment variables


Thus: These variables are expanded by the subshell after fork and before exec'ing the command being invoked, and must -- by specification -- impact the child's environment alone.


Now, for some different behavior:

v=5 sh -c 'echo "$v"'

...benefits from the sh instance creating shell variables from its environment variables (as required in section 2.5.3 of the POSIX specification) on startup.


It's worth noting, by the way, that the syntax you're asking about is for assignment within a simple command, as opposed to assignment within a subshell. You can control assignment in a subshell involved in a pipeline like so:

{ v=5; echo "$v"; } | somecommand ...

...which puts the assignment into the subshell running the first component of the pipeline (if your shell is indeed running that component in a subshell, which is undefined behavior inasmuch as POSIX is concerned; from the spec: "as an extension, however, any or all commands in a pipeline may be executed in the current environment").



回答2:

Put simply, the "$v" is evaluated before the command is called while prepending "v=5" in front of the command modifies the environment of the command you're running.

As Charles Duffy said, you can add an intermediate 'sh' process that will evaluate the variable with a similar syntax but you probably want to do something a bit more elaborate and it'd be useful to know what if you still have troubles with it.



回答3:

v=5 echo $v

will add v=5 into the environment variables and then execute echo $v. The $v refers to the shell variable v which you have set to 1 in the first line.

Adding the semi colon v=5;echo $v sets the shell variable to 5 and then executes the command after the semi-colon ie echo $v and produces 5.

Try this command:

v=5 env|less

and look at the environment.