Why does pipe flush the current working directory?

First script:

$ { mkdir dir; cd dir; pwd; } | cat; pwd; ./dir . 

Second script:

 $ { mkdir dir; cd dir; pwd; }; pwd; ./dir ./dir 

Why is this | cat | cat affect the current directory? And how to solve it? I need the first script to work exactly the same as the second. I do not want cat change the current directory back to . .

+7
bash shell
source share
6 answers

Quote from manual :

Each command in the pipeline is executed in its own subshell (see the command execution environment ).

Also see Grouping commands :

{}

  { list; } 

Placing a list of commands between curly braces results in a list of commands executed in the current shell context. No lining is created.

+11
source share

It’s not that the pipe returns to the directory, so you did the first command (to the semicolon), applicable only to the cat . You essentially make the subprocess mkdir and cd and pwd output to the cat process.

For example: { mkdir dir; cd dir; pwd; } | cat; pwd; { mkdir dir; cd dir; pwd; } | cat; pwd;

First, it unfolds in two processes: 1) { mkdir dir; cd dir; pwd; } | cat; { mkdir dir; cd dir; pwd; } | cat; and 2) pwd

The first process splits into two processes { mkdir dir; cd dir; pwd; } { mkdir dir; cd dir; pwd; } { mkdir dir; cd dir; pwd; } , which then sends stdout to stdin from cat . When the first of these two processes terminates and stdout is assembled, its subprocess terminates and is similar to cd , since cd only affects the directory of the process in which it worked. pwd never changed $PWD , it only printed what was provided on stdin .

To solve this problem (assuming I understand what you are trying to do), I would change it to:

{ mkdir dir; cd dir; pwd; }; pwd; cd -

+2
source share

At startup:

 { 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 execute a group of commands on the LHS channel in the sub-shell and, therefore, the change dir does not appear in the current shell after the completion of both commands (LHS and RHS).

However, at startup:

 { mkdir -p dir; cd dir; pwd; }; pwd; 

Between them there is not a single command inside the curly braces, and pwd outside the curly braces is executed in the current shell , so you get a modified directory.

PS: Also note that this line:

 ( mkdir -p dir; cd dir; pwd; ) 

It also will not change the current directory in the current shell, because the commands inside the square brackets are executed in a subclass, while the curly braces are just used to group.

+2
source share

The behavior of pipeline commands with respect to variables is determined by the implementation.

You use bash , which you choose to place each component in the background shell, so the cd effect is lost in the main shell.

If you ran ksh , which decided to keep the last element of the pipeline in the current shell, and if you put cd in the last statement, the behavior would be what 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 
+1
source share

The pipe does not reset the current working directory. The pipe creates subshells in bash, one for each side of the pipe. A sub-sell is also not resetting the current working directory. The fake contains changes in the environment itself and does not extend to the parent shell.

In the first script:

 $ { mkdir dir; cd dir; pwd; } | cat; pwd; 

cd is executed inside the left subshell of the pipe. The working directory is changed only in this subshell. The working directory of the parent shell does not change. The first pwd runs in the same subshell as cd , so it reads the modified working directory. pwd at the end runs in the parent shell, so it reads the working directory of the parent shell that has not been changed.

In your second script:

 $ { mkdir dir; cd dir; pwd; }; pwd; 

Candlesticks not created. Curly braces do not create a subshell. cd runs in the parent shell, and both pwd read the modified working directory.

For more information and explanations, read the bash manual:

0
source share

Although the manuals say:

  { 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 conveyor will still place it on a subshell.

  { list; } | something 

FROM

 Each command in a pipeline is executed in its own subshell (see Command Execution Environment). 

Commands in { } alone will not be placed on a subshell, but its higher context will be that it will still be the same.

Since the test run ( ) and { } will be the same:

 # echo "$BASHPID" 6582 # ( ps -p "$BASHPID" -o ppid= ) | cat 6582 # { ps -p "$BASHPID" -o ppid=; } | cat 6582 

Both send the parent process as the calling wrapper.

0
source share

All Articles