What is the difference between && and | in bash script?

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


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.


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

Tags:

Bash