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 .
.
Although the manuals says:
Placing it on a pipeline would still place it on a subshell.
Since
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:Both sends parent process as the calling shell.
When you run:
OR
OR
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:
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:
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.
Quoting from the manual:
Also see Grouping Commands:
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 thecd
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 thecd
in the last statement, the behavior would have been the one you expect.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 themkdir
andcd
andpwd
go to thecat
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 itsstdout
tostdin
ofcat
. When the first of these two processes finishes and thestdout
is collected, its subprocess exits and it is like thecd
never happened because thecd
only affects the directory of the process it was running in. Thepwd
never actually changed$PWD
, it only printed that which was provided onstdin
.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 -
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:
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 firstpwd
is executed in the same subshell as thecd
so it reads the changed working directory. Thepwd
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:
There is no subshell created. The curly braces do not create a subshell. The
cd
is executed in the parent shell and bothpwd
s read the changed working directory.For more information and explanation read the fine bash manual about: