You’re very close.
You can do what you want with
while read line; do pushd "$line"; done < <(find "$(pwd)" -type d)
The problem with your command is that pushd
, like cd
,
must be done in the main (parent) process to be useful,
and (with some variation based on how your system is set up),
the commands in a pipeline run in subprocesses (child processes).
< <(cmd)
magically gives you a pipe
without giving you a pipeline.
This requires that you are running bash
(or maybe one of the other advanced shells?),
since POSIX doesn’t support <(cmd)
.
The xargs
approach, sadly, was doomed to failure,
because pushd
(like cd
) is a shell builtin command
(i.e., there is no program called pushd
),
and xargs
requires an external, executable program.
You can get output that (almost) looks right with this command:
$ find "$(pwd)" -type d | xargs -i sh -c 'pushd "$1"' sh
~/foo ~/foo
~/foo/bar1 ~/foo
~/foo/bar2 ~/foo
~/foo/bar3 ~/foo
but that is executing the shell as an external program,
and doing so (independently) for each directory.
This is a little closer:
$ find "$(pwd)" -type d | xargs sh -c 'for line do pushd "$line"; done' sh
~/foo ~/foo
~/foo/bar1 ~/foo ~/foo
~/foo/bar2 ~/foo/bar1 ~/foo ~/foo
~/foo/bar3 ~/foo/bar2 ~/foo/bar1 ~/foo ~/foo
executing a single shell process,
and telling it to loop over all the directories.
As you can see,
this technique is similar to your second attempt (and my answer),
and the result is the same as your second attempt —
you get a new shell process that calls pushd
four times,
and ends up with a directory stack that is five levels deep
(counting the starting directory twice) —
but that new shell process is in a subprocess,
and, by the time you see the next shell prompt, that shell process is gone.
For reference, note that this command is somewhat similar to
an answer I gave a couple of years ago.
Stéphane Chazelas discusses
the sh -c long_complex_shell_command sh
command construct
here and here.