I understand that since |
initiates a new process for the command(s) after the pipe, any shell command of the form cmd | cd newdir
(where cmd
does not change the current working directory) will leave the original process's working directory unchanged. (Not to mention that this is a bit silly since cd
doesn't read input from stdin.)
However, on my machine (a CentOS 6 box, using bash
, ksh
, or zsh
), it appears that the following command also fails to change directories:
cd newdir | cat
(Please ignore how silly it is to pipe output to cat here; I'm just trying to make a simple example.)
Why is this? Is there a way around this problem? Specifically, I'm trying to write an alias that uses popd
but catches the output, discards stdout, and re-outputs stderr.
(For the curious, this is my current, non-working alias: popd 2>&1 >/dev/null | toerr && lsd
. Here, toerr
just catches stdin, outputs it to stderr, and returns the number of lines read/printed. lsd
is a directory-name-and-contents printer that should only execute if the popd
is successful. The reason I'm sending stderr to stdout, piping it, catching it, and re-outputting it on stderr is just to get it colored red using stderred, since my shell session isn't loaded with LD_PRELOAD, so Bash built-ins such as popd
don't get the red-colored stderr.)
In bash
, dash
and ash
, each command in a pipeline runs in a subshell.
In zsh
, ksh
, and bash
with shopt -s lastpipe
, all except the last command in the pipeline run in subshells.
Since cd
-- as well as variables, shell options, ulimits and new file descriptors -- only affects the current process, their effects will not affect the parent shell.
Examples:
# Doesn't change directory
cd foo | cat
pwd
# Doesn't set $bar on default bash (but does on zsh and ksh)
echo foo | read bar
echo "$bar"
# Doesn't change the ulimit
ulimit -c 10000 2>&1 | grep "not permitted"
ulimit -c
The same also applies to other things that generate subshells. None of the following will change the directory:
# Command expansion creates a subshell
echo $(cd foo); pwd
# ( .. ) creates a subshell
( cd foo ); pwd
# Backgrounding a process creates a subshell
cd foo & pwd
To fix it, you have to rewrite your code to run anything that affects the environment in the main shell process.
In your particular case, you can consider using process substitution:
popd > /dev/null 2> >(toerr) && lsd
This has the additional benefit of only running lsd
when popd
is successful, rather than when toerr
is successful like your version does.