__biancatcatcat

Bash n' Hash

The other day I was messing around with Python and pip installs on a new machine because I wanted to set up a ~super cool~ run config in PyCharm. Fun, right?

On this day, I was having a couple of issues with my PATH, and as far as I can tell, everything was fine. So it was a new problem I've never come across before in all my (fair amount!) of years as a developer. That's when I googled and learned that bash has an internal hash table. The tricksy clever thing.

From bash(1):

Bash uses a hash table to remember the full pathnames of executable files. A full search of the directories in PATH is performed only if the command is not found in the hash table.

See that? "A full search of the directories in PATH is performed only if the command is not found in the hash table."

If you didn't know about this before, you usually hit it like this, in the "woah, which which" scenario.

Here's the rundown:

# I have a script, installed globally
$ cat /usr/local/bin/hello-world
#!/usr/bin/env bash

echo "Hello world! This is my global executable!"

# Let's try it out
$ hello-world
Hello world! This is my global executable!

# Cool, it works! I'd actually like my own version of this so I can modify it, in ~/bin. 
$ cp /usr/local/bin/hello-world ~/bin
$ ls -l ~/bin
-rwxr-xr-x  1 bianca  staff  71 Mar 23 14:13 hello-world

# Cool, checking $PATH real quick
$ echo $PATH
/Users/bianca/bin:/usr/local/bin:/usr/local/sbin

# Looks good! Let me edit my script now
$ sed -i "" 's/global/local/g' /Users/bianca/bin/hello-world
$ cat /Users/bianca/bin/hello-world
#!/usr/bin/env bash

echo "Hello world! This is my local executable!"

# Nice. Testing it out...
$ hello-world
Hello world! This is my global executable!

# ...Wat. That's not right! Troubleshoot.
$ which hello-world
/Users/bianca/bin/hello-world

# Wait it's pointing to the right thing! What is this madness!
# Oh, right! hash!
$ hash
hits command  
    1 /usr/bin/which
    1 /usr/local/bin/hello-world

# Reset it
$ hash hello-world # or `hash -r` to clear everything
hello-world  
Hello world! This is my local executable!

# Success! Wow, that could have have been really annoying to debug if this example wasn't contrived.

Congrats. You're now prepared for the woah, which which scenario!

Bonus:

JSYK, zsh's hash behavior is different:

ZSH:

# ZSHELL

$ cat /usr/bin/hello-world
#!/usr/bin/env bash

echo "Hello world! This is my global executable!"

$ hello-world
Hello world! This is my global executable!

$ cp /usr/local/bin/hello-world ~/bin
$ sed -i "" 's/global/local/g' /Users/bianca/bin/hello-world
$ cat ~/bin/hello-world
#!/usr/bin/env bash

echo "Hello world! This is my local executable!"

$ hello-world
Hello world! This is my global executable!

$ hash hello-world # bash would re-read PATH here, zsh doesn't
$ hello-world
Hello world! This is my global executable!

$ hash -r # Drop the entire table

# Now it works!
$ hello-world
Hello world! This is my local executable!

# Will it work if we scrap it? (Doesn't seem like it in bash)
$ rm ~/bin/hello-world
$ hello-world
Hello world! This is my global executable!  

BASH:

# BASH

$ cat /usr/local/bin/hello-world
#!/usr/bin/env bash

echo "Hello world! This is my global executable!"

$ hello-world
Hello world! This is my local executable!

# No rebuilding tho
$ rm /Users/bianca/bin/hello-world
$ hello-world
-bash: /Users/bianca/bin/hello-world: No such file or directory

# But this works (Not in zshell)!
$ hash hello-world
$ hello-world
Hello world! This is my global executable!  
What would you like to see me write about? Comments and questions are welcome in the comments or on Twitter!