What is the difference between && and | in bash script?
In p=$(cd ~ && pwd)
:
The command substitution,
$()
, runs in a subshellcd ~
changes directory to~
(your home), ifcd
succeeds (&&
) thenpwd
prints the directory name on STDOUT, hence the string saved onp
will be your home directory e.g./home/foobar
In p=$(cd ~ | pwd)
:
Again
$()
spawns a subshellThe commands on both sides of
|
run in respective subshells (and both starts off at the same time)so
cd ~
is done in a subshell, andpwd
in a separate subshellso 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, hencep
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