Python 3.8 scripts suddenly won't run, but Python commands run fine when invoked directly

Since you are able to run the script using python3 hello.py, it is evident that the issue isn't with the Python installation or the symlinks. Instead the issue is with the script itself.

The shell (here, zsh) didn't really recognize the shebang as such because of some alien characters before #. It tried to execute the whole script with the default shell/interpreter, thus producing errors.

You can use the -e option of cat to check the actual contents of the script. -e is basically the combination of v and E. v uses ^ and M- notation, and E displays $ at the end of each line.

After observing the output of cat -e hello.py, it seems the script contains a byte order mark near its shebang/hashbang which may have been inserted by some Windows software. This prevented the script from actually loading the Python interpreter, so it instead got executed by zsh, thus causing the error.

You can remove BOMs and other DOS-specific characters using dos2unix.

dos2unix hello.py  # Install using `sudo apt install dos2unix` (if not already installed)

dos2unix will take care of all Windows/DOS type characters/line endings and convert them to Unix-like ones.

Or you can also use sed to remove BOMs. Since BOMs were present in UTF-8, their hexadecimal representation is EF BB BF.

sed -i '1s/^\xEF\xBB\xBF//' hello.py

With gracious help from @Kulfy the problem seems to be some unrenderable bytes preceding the shebang in my scripts! Specifically, bytes EF BB BF preceded the bytes 23 21 which should be the start of the script(s).

How that byte order mark got there is a mystery, but at least now:

./hello.py
Hello world!