Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why pipe resets current working directory?

Tags:

bash

shell

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 ..

like image 312
yegor256 Avatar asked Aug 29 '13 14:08

yegor256


3 Answers

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.

like image 124
devnull Avatar answered Nov 17 '22 14:11

devnull


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.

like image 38
anubhava Avatar answered Nov 17 '22 13:11

anubhava


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 -

like image 2
manchicken Avatar answered Nov 17 '22 12:11

manchicken