4

Recent releases of popular linux distributions have chosen to not install any python command by default. This makes it incredibly difficult to write portable scripts that works on both older and newer systems.

The smelly options:

  • rewriting the scripts at packaging or install time
  • forcing the user to call the script specifically with the versioned python interpreter
  • expecting the user will have run update-alternatives or similar

Is there a way to write a shebang #! line that uses only generally installed standard linux standard tools and can run either python3,python2, or python ?

I want something like a fictional --choices argument to the env command

#!/usr/bin/env --choices python3,python2,python

But of course that doesn't exist.

Mark Borgerding
  • 8,117
  • 4
  • 30
  • 51
  • 1
    It's not hard to write a command which does that, but no, nothing standard exists. I don't think anybody would want to write new scripts which work portably across Python 2 and Python 3 any longer; those projects which still maintain Python 2 compatibility will typically have a `consele_scripts` section in their `setup.py` which takes care of installing a suitable wrapper for the target system where you are installing the package. – tripleee Aug 11 '20 at 15:16
  • triplee, your comment is far from helpful. You are saying that no one needs to do what I'm asking or they do one of the smelly options. It is possible to write 2/3 compatible code *except* for the darn first line of the script! That is an abomination against all things unixish. – Mark Borgerding Aug 11 '20 at 15:42
  • (Sorry for the mobile keyboard; `console_scripts` obviously.) – tripleee Aug 11 '20 at 15:43
  • There was a time when it made sense to maintain Python 2 compatibility but if it's not over yet, it soon will be. Been there, done that, definitely don't want to go back. The burden will grow heavier with every 3.x release; I find it frustrating when I need to keep compatibility with 3.5 already. You are locking yourself out from significant improvements at least in the libraries I care about (`subprocess`, `email`, etc). – tripleee Aug 11 '20 at 15:47
  • Perhaps see also https://stackoverflow.com/questions/18787036/difference-between-entry-points-console-scripts-and-scripts-in-setup-py for another take on "smelly". – tripleee Aug 11 '20 at 16:16

1 Answers1

3

Building on an idea at https://stackoverflow.com/a/9051635/13596037, you could do for example:

#!/bin/bash
'''':
for interpreter in python3 python2 python
do
    which $interpreter >/dev/null 2>&1 && exec $interpreter "$0" "$@"
done
echo "$0: No python could be found" >&2
exit 1
# '''


import sys
print(sys.version)
alani
  • 12,573
  • 2
  • 13
  • 23
  • Nice. Here's my massaging of it down to a one-liner plus a comment telling vim what to do. ``` #!/bin/bash find_python='''';for PY in python3 python2 python;do which $PY > /dev/null 2>&1 && exec $PY "$0" "$@";done;echo "no python";exit 1 #''' # vim: set ft=python: ``` – Mark Borgerding Aug 11 '20 at 16:06
  • @MarkBorgerding Noticing your vim directive. In my real-life use case (for a slightly different problem) I have this for emacs: `# -*- mode: Python -*-` – alani Aug 11 '20 at 16:08
  • There's a portability problem with `which` as well; maybe check if you can replace it with `command -v` or `type`, which are both POSIX-mandated. – tripleee Aug 11 '20 at 17:41
  • Also perhaps include `$0` in the `echo` error message. If you have scripts calling scripts calling scripts, it's rather bewildering when you can't see which one is failing. – tripleee Aug 11 '20 at 17:42
  • @tripleee I will include the `$0` but regarding `which`, I actually used `type` originally and then changed it to `which` because I was concerned that `type` could succeed if there is an alias or shell function that cannot be run with `exec`, whereas `which` will only show external commands. (If `$BASH_ENV` is set, then an rcfile could have been run even for non-interactive shells.) – alani Aug 15 '20 at 18:12
  • You can use `command` or `type -p` for that. The latter is not POSIX, but that's not a problem if you target Bash specifically (wheras the availability of a `which` command outside of Bash in not under your control; but than your shebang should say `bash`, not `sh`). – tripleee Aug 15 '20 at 18:18