可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
First script:
$ { mkdir dir; cd dir; pwd; } | cat; pwd;
./dir
.
Second script:
$ { mkdir dir; cd dir; pwd; }; pwd;
./dir
./dir
Why this | cat
has an effect on current directory? And how to solve it? I need the first script to work exactly as the second one. I don't want cat
to change my current directory back to .
.
回答1:
Quoting from the manual:
Each command in a pipeline is executed in its own subshell (see
Command Execution Environment).
Also see Grouping Commands:
{}
{ list; }
Placing a list of commands between curly braces causes the list to be
executed in the current shell context. No subshell is created.
回答2:
When you run:
{ mkdir -p dir; cd dir; pwd; } | cat; pwd
OR
{ mkdir -p dir; cd dir; pwd; } | date
OR
{ mkdir -p dir; cd dir; pwd; } | ls
You are running group of commands on LHS of pipe in a sub-shell and hence change dir
isn't reflected in current shell after both commands (LHS and RHS) complete.
However when you run:
{ mkdir -p dir; cd dir; pwd; }; pwd;
There is no pipe in between hence all the commands inside curly braces and pwd
outside curly brace run in the current shell itself hence you get changed directory.
PS: Also note that this line:
( mkdir -p dir; cd dir; pwd; )
Will also not change the current directory in current shell because commands inside square brackets execute in a sub shell whereas curly braces are just used for grouping.
回答3:
It's not that the pipe goes back to the directory, it's that you've made the first command (prior to the semicolon) applicable only to the cat
command. You're essentially piping the output of the subprocess of the mkdir
and cd
and pwd
go to the cat
process.
For example:
{ mkdir dir; cd dir; pwd; } | cat; pwd;
First expands into two processes: 1) { mkdir dir; cd dir; pwd; } | cat;
and 2) pwd
The first process expands into two processes, { mkdir dir; cd dir; pwd; }
which then sends its stdout
to stdin
of cat
. When the first of these two processes finishes and the stdout
is collected, its subprocess exits and it is like the cd
never happened because the cd
only affects the directory of the process it was running in. The pwd
never actually changed $PWD
, it only printed that which was provided on stdin
.
To resolve this issue (assuming I understand what you are trying to do) I would change this to:
{ mkdir dir; cd dir; pwd; }; pwd; cd -
回答4:
The behavior of pipeline commands regarding variables is implementation defined.
You happen to use bash
which choose to put every component in a background shell so the cd
effect is lost in the main shell.
Should you have run ksh
which choose to keep the last element of a pipeline in the current shell and should you have put the cd
in the last statement, the behavior would have been the one you expect.
$ bash
$ { mkdir -p dir; cd dir; pwd; } | { cat; mkdir -p dir; cd dir; pwd ; } ; pwd;
/tmp/dir
/tmp/dir
/tmp
$ ksh
$ { mkdir -p dir; cd dir; pwd; } | { cat; mkdir -p dir; cd dir; pwd ; } ; pwd;
/tmp/dir
/tmp/dir
/tmp/dir
回答5:
Although the manuals says:
{ list; }
Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created.
Placing it on a pipeline would still place it on a subshell.
{ list; } | something
Since
Each command in a pipeline is executed in its own subshell (see Command Execution Environment).
The commands in { }
itself would not be placed on a subshell but the higher context of it itself would be that's why it would still be the same.
As a test running ( )
and { }
would just be the same:
# echo "$BASHPID"
6582
# ( ps -p "$BASHPID" -o ppid= ) | cat
6582
# { ps -p "$BASHPID" -o ppid=; } | cat
6582
Both sends parent process as the calling shell.
回答6:
The pipe does not reset the current working directory. The pipe creates subshells, in bash one for each side of the pipe. The subshell also does not reset the current working directory. The subshell contains the changes to the environment in itself and does not propagate them to the parent shell.
In your first script:
$ { mkdir dir; cd dir; pwd; } | cat; pwd;
The cd
is executed inside the left subshell of the pipe. The working directory is changed only in that subshell. The working directory of the parent shell is not changed. The first pwd
is executed in the same subshell as the cd
so it reads the changed working directory. The pwd
at the end is executed in the parent shell so it reads the working directory of the parent shell which was not changed.
In your second script:
$ { mkdir dir; cd dir; pwd; }; pwd;
There is no subshell created. The curly braces do not create a subshell. The cd
is executed in the parent shell and both pwd
s read the changed working directory.
For more information and explanation read the fine bash manual about:
- pipelines
- execution environment
- grouping