Mark Holmes

What is my PATH?

Published by Mark Holmes on November 7th, 2022

    This is the first in a series of articles I’m calling Things no one taught you that you’re just expected to know as an engineer. They’re mostly lessons I’ve learned by trial and error over the years, and I’m hoping these will be useful to someone.

    I may have missed this lesson in my Operating Systems class (which was admittedly a long time ago). Or, perhaps more likely, I didn’t know I’d need it and forgot. Either way, open your terminal and run echo $PATH; it may look something like this:

    ➜ ~ echo $PATH
    /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

    As an engineer, you’ll need to interact with your PATH on occasion. For example, you’ll see this message as the final step of the Homebrew installation:

    - Run these three commands in your terminal to add Homebrew to your PATH:
        echo '# Set PATH, MANPATH, etc., for Homebrew.' >> .zshrc
        echo 'eval "$(${HOMEBREW_PREFIX}/bin/brew shellenv)"' >> .zshrc
        eval "$(${HOMEBREW_PREFIX}/bin/brew shellenv)"

    (source)

    You don’t need to think about it often, but in case trouble arises it’s good to know what your PATH actually does.

    How do I create or modify my PATH?

    First, let’s assume we’re creating some bash script that echos “Hello, world!“.

    #!/bin/sh
    echo "Hello, World!"

    You can save it anywhere, ~/Desktop/script.sh will do fine. In order to run that script, open your terminal, change to your Desktop directory (cd ~/Desktop), and type ./script.sh. Another way is to open your terminal to your home directory (~) and type ./Desktop/script.sh. You’re executing this script at its qualified path. If you just typed script.sh, you’d get an error: command not found: script.sh. But what if we did want to run our script from anywhere just by typing script.sh?

    Let’s make this a little more interesting. Let’s move our script to a different folder and rename it. Run mkdir ~/tmp && mv ~/Desktop/script.sh ~/tmp/hello && chmod +x ~/tmp/hello. This makes a new directory at ~/tmp, moves the script to the ~/tmp folder, renames the script to hello, and modifies the script to allow it to be executed. Change back to your home directory (cd ~) and try running your script!

    ➜ ~ hello
    zsh: command not found: hello

    Well, we can’t do that just yet. That’s because our PATH variable declares the directories on our file system that have scripts that can be executed without needing each script’s full path. And there’s really nothing special about the PATH variable, it can be modified or overwritten. If we wanted to create our own environment variable, we could do that as follows:

    ➜ ~ export FOO="bar"
    ➜ ~ echo $FOO
    bar

    So how do we get our hello program to run? Let’s modify our PATH (temporarily — if we wanted these changes to stick, we’d need to add them to our shell’s startup script):

    ➜ ~ export PATH="/Users/mark/tmp:$PATH"
    ➜ ~ echo $PATH
    /Users/mark/tmp:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

    (Note: Sub in your machine’s username, which probably isn’t mark)

    And then give it a whirl:

    ➜ ~ hello
    Hello, world!

    Success! We created a script, added its directory to our PATH, and executed it from our home directory. We also used a neat little trick by using our current $PATH variable to prepend our new script’s directory!

    (And if you want to undo everything we just did, you can run rm -f ~/tmp to remove the script and simply close and reopen your terminal to get rid of the temporary PATH variable.)

    Alright, admittedly you likely won’t be looking at your PATH frequently. Maybe you’re setting up a new machine or installing new software. It’s not often, but it’s probably not never either. I was setting up asdf recently to manage my language versions and messed up the installation. My PATH was set incorrectly, causing permissions issues. I ended up setting my PATH in my ~/.zshrc file, which now looks something like this:

    # Custom path
    export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin:/usr/sbin:/sbin"

    You’ll notice that when I run echo $PATH, I get this:

    ➜ ~ echo $PATH
    /Users/mark/.asdf/shims:/Users/mark/.asdf/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin:/usr/sbin:/sbin

    That’s because I source asdf.sh later in my ~/.zshrc file, which makes its own addition to the PATH variable:

    # Source asdf
    . $HOME/.asdf/asdf.sh

    I bring this up because it’s not uncommon; you won’t always be setting your PATH variable by hand. If you do encounter weird behavior, you may need to check what scripts in your ~/.zshrc file (or you shell’s equivalent) are causing PATH mutations.


    One final thought here. Have you ever thought about what running npm (or equivalent programs) actually does? You may have noticed the first path in my PATH, /Users/mark/.asdf/shims. What happens if we list the files in that directory?

    ➜ ~ ls -al ~/.asdf/shims
    drwxr-xr-x  32 mark  staff  1024 Nov  7 18:51 .
    drwxr-xr-x  31 mark  staff   992 Nov  4 20:35 ..
    -rwxr-xr-x   1 mark  staff   136 Nov  7 18:51 elixir
    -rwxr-xr-x   1 mark  staff   126 Nov  7 18:48 erl
    -rwxr-xr-x   1 mark  staff   133 Nov  7 18:51 iex
    -rwxr-xr-x   1 mark  staff   133 Nov  7 18:51 mix
    -rwxr-xr-x   1 mark  staff   128 Nov  4 20:35 node
    -rwxr-xr-x   1 mark  staff   127 Nov  4 20:35 npm
    -rwxr-xr-x   1 mark  staff   127 Nov  4 20:35 npx

    I omitted a few results here for brevity’s sake, but this should look pretty familiar to our hello script from earlier!

    So with /Users/mark/.asdf/shims in our PATH, we can run any of the scripts provided by the languages we installed using asdf.

    To say this another way, if we didn’t have that in our path, we couldn’t just run npm start, we’d have to run /Users/mark/.asdf/shims/npm start, and that would be really annoying. Thankfully, PATH has us covered here.

    Happy Hug a Bear Day!