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?
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
cd ~
doesn't produce any output, and pwd
doesn't read any input.
(cd ~);p=$(pwd)
isn't it?
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
~$
cd ~
changed the "present directory" to the home of the actual user that is executing the command (/home/user
).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.
cd ~
changes the pwd of one shell.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
|
run in subshells.