29

I am trying to get cd to accept a directory name redirected to it from another command. Neither of these methods work:

$ echo $HOME | cd
$ echo $HOME | xargs cd

This does work:

$ cd $(echo $HOME)

Why does the first set of commands not work, and are there others that also fail this way?

Jhonathan
  • 3,605

6 Answers6

35

cd is not an external command - it is a shell builtin function. It runs in the context of the current shell, and not, as external commands do, in a fork/exec'd context as a separate process.

Your third example works, because the shell expands the variable and the command substitution before calling the cd builtin, so that cd receives the value of ${HOME} as its argument.

POSIX systems do have a binary cd - on my FreeBSD machine, it's at /usr/bin/cd, but it doesn't do what you think. Calling the binary cd causes the shell to fork/exec the binary, which does indeed change its working directory to the name you pass. However, as soon as it does so, the binary exits, and the forked/exec'd process disappears, returning you to your shell, which is still in the directory it was in before you started.

D_Bye
  • 13,977
  • 3
  • 44
  • 31
23

cd does not read standard input. That is why your first example does not work.

xargs needs a command name, that is, a name of an independant executable. cd needs to be a shell built-in command and would have no effect (other than verifying that you can change to that directory and the potential side effects it may have like for automountable directories) if it were an executable. That is why your second example does not work.

mouviciel
  • 1,235
4

In addition to the correct answers already given: If you run bash and want to find out what a "command" like cd is you can use type

$ type cd
cd is a shell builtin

or why not:

$ type time
time is a shell keyword

while for example gnu time normally is included in your favorite distribution already:

$ which time
/usr/bin/time

Okey okey you get the idea, then what the heck is type?

$ type type
type is a shell builtin

Here's a bash manual snippet:

       type [-aftpP] name [name ...]
          With no options, indicate how each name would be interpreted  if  used  as  a
          command name.  If the -t option is used, type prints a string which is one of
          alias, keyword, function, builtin,  or  file  if  name  is  an  alias,  shell
          reserved word, function, builtin, or disk file, respectively.  If the name is
          not found, then nothing is printed, and an exit status of false is  returned.
          If  the -p option is used, type either returns the name of the disk file that
          would be executed if name were specified as a command  name,  or  nothing  if
          ‘‘type  -t  name’’ would not return file.  The -P option forces a PATH search
          for each name, even if ‘‘type -t name’’ would not return file.  If a  command
          is  hashed,  -p  and -P print the hashed value, not necessarily the file that
          appears first in PATH.  If the -a option is used,  type  prints  all  of  the
          places  that  contain  an  executable  named name.  This includes aliases and
          functions, if and only if the -p option is  not  also  used.   The  table  of
          hashed  commands  is  not  consulted when using -a.  The -f option suppresses
          shell function lookup, as with the command builtin.  type returns true if any
          of the arguments are found, false if none are found.
3molo
  • 189
  • 1
  • 6
4

In addition to the existing good answer, it is also worth mentioning that a pipe forks a new process, which has its own separate working directory. Therefore, trying to do this, won't work:

echo test | cd /

Thus, you won't be in the / folder after the shell returns from this command.

Mark Rejhon
  • 141
  • 1
  • All the commands in a pipeline run in different processes, so in a | b, even if a and b are builtin, at least one then doesn't run in the shell process, but there's no guarantee which which one it is. For instance in AT&T ksh, zsh or bash -O lastpipe, b is run in the current shell process, so your code would take you to / there. – Stéphane Chazelas Jun 22 '13 at 21:01
0

As others have said, it won't work because cd is a shell builtin command, not an external program, so it doesn't have any standard input you can pipe anything into.

But, even if it worked, it wouldn't do what you wish: a pipe spawns a new process and redirects the standard output of the first command into the standard input of the second one, thus only the new process would change its current working directory; this couldn't affect the first process in any way.

Massimo
  • 181
  • 2
    Your second paragraph is right. But re the first paragraph: why do you assume that shell builtins have no stdin? read is usually (always?) a shell builtin. It is true that cd ignores stdin, but this isn't due to its being a builtin. – dubiousjim Oct 06 '12 at 22:46
-2

Another option are backticks, which place the stdout of one command as a command line argument of a second command, and are more portable than $(...). For example:

cd `echo $HOME`

or more generally;

cd `anycommand -and whatever args`

Note that the use of backticks depends on the shell for executing the command and substituting the output on the command line. Most shells support it.

Seth Noble
  • 117
  • 4
  • 3
    The OP already stated, in his/her question, that $(...) works. I don't think it's good advice to recommend backticks instead, since they have much more convoluted quoting rules and are generally more error-prone. (See §3.5.4 "Command Substitution" in the Bash Reference Manual.) – ruakh Oct 05 '12 at 17:01
  • $() is nice where supported, but backticks are more widely supported across different shells and systems. But I should rephrase this as "another option". – Seth Noble Oct 05 '12 at 17:07
  • Different shells, yes; but different "systems"? Are there really any shells that will support $(...) on one system but not on another?? – ruakh Oct 05 '12 at 17:14
  • 4
    -1, it does not answer the question at all. – Bernhard Oct 05 '12 at 17:17
  • 3
    @ruakh &Seth: All POSIX shells support $(…). Systems without a POSIX shell (i.e. with a genuine Bourne shell) would be extremely old. Even systems where /bin/sh is a Bourne shell and you need another path such as /usr/xpg4/bin/sh to get a POSIX shell are rare nowadays. Recommending backticks to anyone who doesn't administer ancient unix boxen professionally is doing them a disservice. – Gilles 'SO- stop being evil' Oct 05 '12 at 18:21
  • If you are writing scripts intended for cross-platform operation (the OP did not specify), then you need to consider that there are a lot of ancient unix boxen still in commercial service. There is also the environment to consider: even if a shell supports $(...), the calling environment may not. For example, that syntax can be problematic in a Makefile. Again, I'm not saying this is the best solution, so perhaps I should have posted this as a comment rather than an answer. But one should not ignore cross-platform, cross-environment alternatives. – Seth Noble Oct 08 '12 at 15:32