2

I recently discovered the -i argument to Python, which drops into interactive mode after the script completes. Pretty neat!

$ cat test.py
#!python3 -i

x=5
print('The value of x is ' + str(x))
$ ./test.py
The value of x is 5
>>> print(str(x+1))
6
>>> 
zsh: suspended  ./test.py

However, when I tried to copy this script to a version that terminates on completion, it fails:

$ cat test1.py
#!python3

x=5
print('The value of x is ' + str(x))
$ ./test.py
/usr/local/Cellar/python3/3.6.3/Frameworks/Python.framework/Versions/3.6/Resources/Python.app/Contents/MacOS/Python: can't open file '
x=5
print('The value of x is ' + str(x))
': [Errno 2] No such file or directory

From some further reading, I discovered that I had originally made a mistake, and #!/usr/bin/env python3 is the correct shebang.

However, I'm curious why a non-absolute path to python3 succeeds only when I give the -i flag. I guess this must be something to do with how zsh interprets non-absolute shebangs, but I don't know enough to know how to investigate that.

System setup: MacOS 10.12.6, iTerm2 3.1.6, zsh 5.2. which python3 gives /usr/local/bin/python3, and that directory is on $PATH.

Interestingly, I don't get the same behaviour on sh:

$ sh
sh-3.2$ cat test.py
#!python3

x=5
print('The value of x is ' + str(x))
sh-3.2$ ./test.py
sh: ./test.py: python3: bad interpreter: No such file or directory

I got some comments suggesting that this is something to do with CWD or permissions. python3 is not in my CWD, and both files have execute permission:

$ ls -al | grep 'py' | awk '{print $1, $10}'
-rw------- .python_history
-rwxr-xr-x test.py
-rwxr-xr-x test1.py
scubbo
  • 4,969
  • 7
  • 40
  • 71
  • Python has nothing to do with this. Your shell is responsible for shebang. – n. m. could be an AI Jul 13 '18 at 04:16
  • OK, fair enough - so, let me rephrase my question. "Why does zsh behave in such a way that a non-absolute path to python3 works as expected sometimes, but not others?" – scubbo Jul 13 '18 at 04:18
  • You are not doing your experiment correctly. Try again without changing your current working directory in process. Shebang works (1) with an absolute path and (2) with a path relative to CWD. `$PATH` or command line arguments play no role in it. – n. m. could be an AI Jul 13 '18 at 04:20
  • I have no idea what's up with test1 though. No execute permission? – n. m. could be an AI Jul 13 '18 at 04:25
  • I got the same result with or without -i. as obviously it shouldn't be a reason for locating an interpreter while executing your code. and kept my file permission 777. its your local environment, nothing to do with python – jits_on_moon Jul 13 '18 at 04:25
  • @n.m., sorry, I don't understand your comment - I didn't change my cwd, no `cd` in examples above. I added a snippet demonstrating that python3 is not on my cwd and that test1.py has execute permission – scubbo Jul 13 '18 at 04:30
  • 1
    Sorry I wasn't precise, see my answer for detailed info. – n. m. could be an AI Jul 13 '18 at 05:25

1 Answers1

2

Your kernel will not execute the script unless the interpreter is

  • specified as an absolute path, or
  • specified as a path relative to the current working directory

Then if the kernel refuses to execute the script, your shell might take over and try to execute it anyway, interpreting the shebang line according to its own rules (like finding the executable in the $PATH for example).

zsh does attempt to do this. sh does not.

However the way zsh interprets the shebang (and probably subsequent lines) is really really strange. It looks like it always expects a single argument after the command name. See what it does:

$ cat test.py 
#!python3 -b -i 

x=5
print('The value of x is ' + str(x))
$ strace -f -e execve zsh
execve("/bin/zsh", ["zsh"], 0x7ffd35c9e198 /* 78 vars */) = 0
host% ./test.py 
strace: Process 5510 attached
[pid  5510] execve("./test.py", ["./test.py"], 0x558ec6e46710 /* 79 vars */) = -1 ENOENT (No such file or directory)
[pid  5510] execve("/usr/bin/python3", ["python3", "-b -i", "./test.py"], 0x558ec6e46710 /* 79 vars */) = 0
[pid  5510] execve("/usr/lib/python-exec/python3.4/python3", ["/usr/lib/python-exec/python3.4/p"..., "-b -i", "./test.py"], 0x7fffd30eb208 /* 79 vars */) = 0
Unknown option: - 
usage: /usr/lib/python-exec/python3.4/python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...
Try `python -h' for more information.
[pid  5510] +++ exited with 2 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5510, si_uid=1000, si_status=2, si_utime=0, si_stime=0} ---
host% 
+++ exited with 2 +++

See how ["python3", "-b -i", "./test.py"] are passed as arguments. It seems highly counterintuitive to me to lump the two switches -b and -i together, but that's what zsh does. Python obviously doesn't understand this.

When there are no arguments, the exact behaviour depends on whether there is a space after the program name, but is strange in either case. Check it with strace yourself because you are not going to believe me.

It is my understanding that zsh handling of the shebang line is just buggy.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243