Would have been nice to mention the type builtin, at least for bash, that helps newcomers distinguish between different kinds of commands:
$ type cat
cat is /usr/bin/cat
$ type cd
cd is a shell builtin
$ type ls
ls is aliased to `ls -Fh'
Wow, I’ve been using Unix for most of my computing life (30 years?) and I didn’t know about type.
It is great to find duplicates in your PATH: type - all
Shows you all places where exists
I use which as opposed to type and it seems to do the exact same thing.
You should use type instead. More than you ever wanted to know on why:
Interesting. As a long time DOS user, I expected type to behave like cat. I typically use which as if it is just returning the first result from whereis, e.g. xxd $(which foo) | vim -R -. I didn’t know about the csh aliases, because the last time I used csh was in the nineties when I thought that since I use C, surely csh is a better fit for me than something whose name starts with a B, which clearly must be related to BCPL.
xxd $(which foo) | vim -R -
I did not know about type and after knowing about it for 15 seconds now I almost completely agree with you. The only reason you could want to use which is to avoid complicating the readlink $(which <someprogram>) invocation on guix or nixos systems. That is; which is still useful in scripts that intend to use the path, type has an output of the form <someprogram> is <path to someprogram>.
readlink $(which <someprogram>)
<someprogram> is <path to someprogram>
Edit: OK I followed a link from the article to some stackoverflow that goes through the whole bonanza of these scripts and I think whereis <someprogram> is probably better than readlink $(which <someprogram>).
@ilmu type -p will return just the path.
Two problems with whereis: 1) it’s not available everywhere, and 2) it can return more than one result, so you have to parse its output. So for that use case I’ll probably stick with which until someone points me at a simple program that does the same thing without the csh aliases.
Interesting. In fish shell, type gives you the full definition of the function for built-ins that are written in fish, and builtin -n lists all the bultins. There’s a surprising about of fish code around the cd builtin.
When training junior Linux users you’ll often have to dig into that when someone cannot “cd ” because they get a permission denied. They inevitably tend to try “sudo cd ”. Of course it does not work, for the reason mentioned in the article, e.g. “cd is not an executable”. It’s always a fun moment in my experience since it leads to the very same explanation exposed in the article.
On a POSIX-compatible system, it is: https://unix.stackexchange.com/questions/50058/what-is-the-point-of-the-cd-external-command
This might be misleading: The cd you normally use is still the shell command, not the cd program.
It’s not more misleading than the title of the article TBH.
Why ? The cd you use everyday isn’t a program, and it does matter.
Exactly, but the title implies that there is no cd program, which is untrue. “cd is probably not a program” or “cd is not only a program” is more accurate and less misleading imo.
How new is this? When I left the DOS world decades ago now I remember having to learn chdir instead of cd and adding an alias for cd. No idea what platform that was, could have been a linux or sunos or Digital Unix. Is my memory faulty or was cd really added later?
Until Unix V7 chdir was used indeed.
One niggle about the Unnecessary cd in shell scripts section, if you’re working with Makefiles (yes, I know, not a script per say), then the pattern the OP is railing against could be needed, as each command in a Makefile’s rule is stateless, so you’d may want to set a dir macro or env. Just wanted to point out this little exception found in the real world.
“So why does it make a difference whether cd is a builtin or a standalone executable? Let me give three examples.”
To those 3 examples, I’d like to add a fourth one: it’s harder for other processes launched from the shell to do a cd for the user. Broot, for example, must be launched by a special script or command to change the current directory of the user ( https://dystroy.org/broot/install-br/ ). This isn’t always obvious for users who wonder why broot wants to install a shell function.
It’s also not universally true that the current working directory is a kernel-level abstraction on POSIX systems. It’s entirely possible to implement it in userspace and either do the path expansion for relative paths in userspace or (on more modern systems) use openat and friends with the file descriptor of the current working directory.
Linux shells are often in an exciting half-way state to this because the working directory in userspace and the kernel may not match. When you cd to a symlink, the kernel’s idea of the current working directory is the target of the link, but the userspace view is the source. When you cd .., bash (at least, probably other shells) uses the userspace view to go back to the path that you expected. You can see this mismatch in this simple example:
$ cd /tmp/
$ mkdir foo
$ ln -s foo bar
$ cd bar
$ stat /proc/self/cwd # Look at the kernel's view of current working directory
File: /proc/self/cwd -> /tmp/foo
$ pwd # Look at the userspace view of the current working directory
$ echo $PWD # Look at the userspace view of the current working directory another way
If I were designing a POSIX system from scratch, I’d make the current working directory a purely userspace abstraction. There’s no need for the kernel to know about it (especially not now that openat exists) and if there’s only one copy of it then it can’t get out of sync with itself.