12

Let's take the two lines below which give us two different results.

p=$(cd ~ && pwd) ; echo $p
p=$(cd ~ |  pwd) ; echo $p

How do the two differ?

  • @heemayl Thanks. May you extract the differences on my case? Thanks. – Nam G VU Nov 14 '16 at 5:31
  • 1
    Hint: The commands on either side of | run in subshells. – heemayl Nov 14 '16 at 5:33
21

In p=$(cd ~ && pwd):

  • The command substitution, $(), runs in a subshell

  • cd ~ changes directory to ~ (your home), if cd succeeds (&&) then pwd prints the directory name on STDOUT, hence the string saved on p will be your home directory e.g. /home/foobar

In p=$(cd ~ | pwd):

  • Again $() spawns a subshell

  • The commands on both sides of | run in respective subshells (and both starts off at the same time)

  • so cd ~ is done in a subshell, and pwd in a separate subshell

  • so you would get only the STDOUT from pwd i.e. from where you run the command, this can be any directory as you can imagine, hence p will contain the directory name from where the command is invoked, not your home directory

  • What's the purpose of the pipe between the commands? cd ~ doesn't produce any output, and pwd doesn't read any input. – Barmar Nov 17 '16 at 0:48
  • The second command is essentially equivalent to (cd ~);p=$(pwd) isn't it? – Barmar Nov 17 '16 at 0:49
  • @Barmar Yes, it's what OP has used, and I'm just explaining it for him. – heemayl Nov 17 '16 at 4:07
6

The core issue is how the operators && and | connect the two commands.

The && connects the commands via the exit code. The | connects the two commands via the file descriptors (stdin, stdout).

Lets simplify first. We can remove the assignment and write:

echo $(cd ~ && pwd)
echo $(cd ~ | pwd)

We can even remove the command execution sub-shell to analyze this:

$ cd ~ && pwd
$ cd ~ | pwd

&&

If we change the prompt to show the directory where the commands are executed, something like PS1='\w\$ ', we will see this:

/tmp/user$ cd ~ && pwd
/home/user
~$ 
  • The command cd ~ changed the "present directory" to the home of the actual user that is executing the command (/home/user).
  • As the result of the command was successful (exit code 0), the next command after the && is executed
  • And the "present working directory" is printed.
  • The running shell has changed its pwd to ~ as is shown by the prompt of ~$.

If the change of directory were unsuccessful (exit code not 0) for some reason (directory doesn't exist, permissions block reading the directory) the next command will not be executed.

Example:

/tmp/user$ false && pwd
/tmp/user$ _ 

The exit code of 1 from false prevents the execution of the next command.

Thus, the exit code of "command 1" is what affects the "command 2".

Now, the effects of the whole command:

/tmp/user$ echo $(cd ~ && pwd)
/home/user
/tmp/user$ _

The directory was changed, but inside a sub-shell $(…), the changed directory is printed /home/user, but is immediately discarded as the sub-shell closes. The pwd returns to be the initial directory (/tmp/user).

|

This is what happens:

/tmp/user$ cd ~ | pwd
/tmp/user
/tmp/user$ _

The meta-character | (not a true operator) signals the shell to create what is called a "Pipe", (in bash) each command on each side of the pipe (|) are set inside each own sub-shell, first the right side command, then, the left one. The input file descriptor (/dev/stdin) of the right command is connected to the output descriptor (/dev/stdout) and then both commands are started and left to interact. The left command (cd -) has no output, and, also, the right command (pwd) accepts no input. So, each one runs independently inside each own sub-shell.

  • The cd ~ changes the pwd of one shell.
  • The pwd prints the (completely independent) pwd of the other sub-shell.

The changes on each shell are discarded when the pipe ends, the external sub-shell has not changed the pwd.

That's why the two commands are connected only by "file descriptors".
In this case, there is nothing sent, and nothing read.

The whole command:

$ echo "$(cd ~ | pwd)"

Will just print the directory where the command was executed.

5

I'm not sure if you meant '|' or '||' in your second case.

'|' in a shell pipes the output of one command to the input of another - a common use case is something like: curl http://abcd.com/efgh | grep ijkl i.e. run a command, and use another command to process the output of a command.

In the example you give, it is fairly non-nonsensical, as 'cd' typically does not generate any output, and 'pwd' does not expect any input.

'&&' and '||' are partner commands though. They are designed to be used the same way as logical "and" and "or" operators in most languages. However, the optimisations that are performed give them a specific behaviour that is a shell programming paradigm.

To determine the result of a logical "and" operation, you only need to evaluate the second condition if the first condition succeeds - if the first condition fails, the overall result will always be false.

To determine the result of a logical "or" operation, you only need to evaluate the second condition if the first condition fails - if the first condition succeeds, the overall result will always be true.

So, in the shell, if you have command1 && command2 command2 will only be executed when command1 has completed and returned a successful result code. If you have command1 || command2 command2 will be executed when command1 completes if command1 returns a failure code.

Another common paradigm is to have command1 be a test command - this generates a single line if/then statement - for example:

[ "$VAR" = "" ] && VAR="Value if empty"

Is a (long winded) way of assigning a value to a variable if it is currently empty.

There are many examples of the use of this process elsewhere on Stack Exchange

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.