Make a sound in zsh when a command is not found

How can I get zsh to play a sound when the command is not found?

Ex: I have the alias vpn to open my VPN from the terminal (zsh). If sometimes by mistake I type cpn instead of vpn, I get the following output:

me@iMac ~ % cpn
zsh: command not found: cpn

I know how to play a sound from terminal, in general. How do I integrate it so that there is a sound whenever the command is not found?

The specific sound I want to play is available at System Preferences > Sound > Sound Effects > Funky.


Solution 1:

First, some background on command execution in zsh is helpful:

If no external command is found but a function command_not_found_handler exists the shell executes this function with all command line arguments. The return status of the function becomes the status of the command. If the function wishes to mimic the behaviour of the shell when the command is not found, it should print the message ‘command not found: cmd’ to standard error and return status 127. Note that the handler is executed in a subshell forked to execute an external command, hence changes to directories, shell parameters, etc. have no effect on the main shell.


So, there are two requirements now:

  1. I need to have a command_not_found_handler defined;
  2. zsh will have to load this function every time it starts.

The second point can be taken care of by defining the function in ~/.zshrc file. For the first point I used the following definition:

function command_not_found_handler() {
        osascript -e beep&echo "zsh: command not found: $1"&
        return 127;
}

Breakdown of the function body:

  1. osascript -e beep&: This command produces the default beep sound of macOS. It's the same sound as the one that plays in System Preferences > Sound > Sound Effects > Funky. The actual location where it is present in macOS is /System/Library/Sounds/Funk.aiff. I have added & at the end of this to execute the sound in parallel. Otherwise the next part of the code will be delayed in execution.
  2. echo "zsh: command not found: $1"&: This is the default action that zsh takes when a bad command is executed. Since the goal is to produce the sound and have the error output on the terminal code, this has to be here. Again, I have added & at the end of this to execute the sound and this message echo in parallel.
  3. return 127;: This, as mentioned in the quoted text at the top of the answer, is the expected status code when a bad command is executed. While the method worked for me without it, it is a good programming habit to return what is expected.

Copy pasting the method in ~/.zshrc file and then sourcing it again makes zsh aware of this function. Now when I call cpn instead of my actual alias vpn, my terminal behaves more consistently with the rest of the macOS and produces the same sound as it does in other GUI interactions, and produces an error message on the terminal.


Credit for this answer goes to @glennjackman's comment.