Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Fun fact: if you've ever had bash (or another shell) complain that a file doesn't exist, even though it's on $PATH, check if it's been cached by `hash`. If the file is moved elsewhere on $PATH and bash has the old path cached, you will get an ENOENT. The entire cache can be invalidated with `hash -r`.


you just solved a bug I couldnt explain like 6 years ago


Is this an old behavior? I would think ENOENT would invalidate the cache entry at least.


It's still a thing in bash 5.2.37.

It's just how bash works. If there's an entry in the session cache, it uses it. Since executable paths only get cached when you run a command successfully, this only happens when it gets moved from one directory in your PATH to another after you run it once, which isn't that common.

Setting PATH or calling hash -r will clear the session cache or one could run set +h which will disable it altogether.


> this only happens when it gets moved from one directory in your PATH to another after you run it once

It also happens when you have two executables in different directories and then you delete the one with the higher priority. Happens regularly for me after I uninstall a Linux Homebrew package.


Isn't cache invalidation one of the hard problems?


Sure but not doing it on ENOENT suggests they’re just being completely lazy. Not to mention that they do have the tools (eg inotify watches) to proactively remove stale entries based on HD changes. Of course I’d be careful about the proactive one as it’s really easy to screw things up more (eg 100 bash instances all watching the same PATH directories might get expensive or forgetting to only do this for interactive mode connected to a TTY)


Retested with bash 5.2.37(1)-release from Debian testing, it still does this :(

Changing $PATH does wipe it at least.


Not working, as intended


I think bash has an alias “rehash” that does the same as hash -r too. But zsh doesn’t have it, so “hash -r” has entered my muscle memory, as it works in both shells.

Edit: wrong shell, zsh has rehash, bash does not.


but zsh has "rehash"? for as long as I remember.


Bah, you’re right! I got it backwards, it’s zsh that has rehash, bash does not. And hash -r works in both.

I guess I’ve been using zsh longer than I thought, because I learned about rehash first, then made the switch to hash -r later. I started using zsh 14 years ago, and bash 20+ years ago, so my brain assumed “I learned about rehash first” must have been back when I was using bash. zsh is still “that new thing” in my head.


If there is something nice that one has and one does not have. zsh is the one that has it.


Counterpoint, /dev/udp pseudo-devices in bash.


the odd thing is, at some point I ended up with `hash -R` as muscle memory that I always type before I correct it to a lower case r, and I'm not sure why, I can't remember any shell that uses `-R`.


Ah, so that's where sudo texhash -r comes from when installing a latex package!


Unsure of in which situation, but I've had situations where a script didn't have the right shebang, and as such I had to resort to `alias --save hash='#'` to make sure the script worked.


The other typical cause is when an interpreter or library is compiled with the wrong libc version.


Wtf. TIL about hash.


Using "hash" is arguably the best way to determine if a command is available in a BASH script

e.g.

    hash java 2>/dev/null || printf "java command not available\n"


If you want to be compatible across all shells, use command -v. POSIX mandates it exists and has that returncode behaviour, whereas it doesn't mandate the hash, which or where command

    command -v java >/dev/null || echo 'no java command in path'
...and of course, if you're going to run the command anyway, and you know an invocation that does nothing and always exits with success, you can do that too. I like doing running "--version" or equivalent in CI systems, because it has the side effect of printing what actual versions were in use during the run.

    java -version || { echo >&2 'no java command in path'; exit 1; }
    git --version || { echo >&2 'no git command in path'; exit 1; }
    gcc --version || { echo >&2 'no gcc command in path'; exit 1; }


Yeah, if you're targetting POSIX shells, then "command -v" may be more reliable.

If you're targetting BASH, then "hash" is a builtin so maybe slightly quicker (not that it's likely to be an issue) and it caches the location of "java" or whatever you're looking for, so possibly marginally quicker when you do want to run the command.

Whilst running "java -version" may be useful in some scripts (my scripts often put the output into a debug function so it only runs it when I set LOG_LEVEL to a suitable value, but it writes output to a file and STDERR), you run into an issue of "polluting" STDOUT which then means that you're not going to be using your script in a pipeline without some tinkering (ironically you're putting the failure message into STDERR when you probably don't care as the script is exiting and hopefully breaking the pipeline). Also, it can take some research to figure out what invocation to use for a specific command, whereas the "hash" version can just be used with little thought.

By the way, I don't believe that ">&2" is POSIX compliant, but that's trivial to fix.


As far as I know, its POSIX compliant?

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...

    The redirection operator:

    [n]>&word

    shall duplicate one output file descriptor from another, or shall close one. If word evaluates to one or more digits, the file descriptor denoted by n, or standard output if n is not specified, shall be made to be a copy of the file descriptor denoted by word


Fair enough - I usually just target BASH and don't worry about POSIX compliance.


Holy shit




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: