Search code examples
pythonlinuxscriptingshebang

shebang env preferred python version


I have some python-2.x scripts which I copy between different systems, Debian and Arch linux. Debian install python as '/usr/bin/python' while Arch installs it as '/usr/bin/python2'. A problem is that on Arch linux '/usr/bin/python' also exists which refers to python-3.x. So every time I copy a file I have to correct the shebang line, which is a bit annoying.

On Arch I use

#!/usr/bin/env python2

While on debian I have

#!/usr/bin/env python

Since 'python2' does not exist on Debian, is there a way to pass a preferred application? Maybe with some shell expansion? I don't mind if it depends on '/bin/sh' existing for example. The following would be nice but don't work.

#!/usr/bin/env python2 python
#!/usr/bin/env python{2,}
#!/bin/sh python{2,}
#!/bin/sh -c python{2,}

The frustrating thing is that 'sh -c python{2,}' works on the command line: i.e. it calls python2 where available and otherwise python.

I would prefer not to make a make a link 'python2->python' on Debian because then if I give the script to someone else it will not run. Neither would I like to make 'python' point to python2 on Arch, since it breaks with updates.

Is there a clean way to do this without writing a wrapper?

I realize similar question have been asked before, but I didn't see any answers meeting my boundary conditions :) Conditional shebang line for different versions of Python

--- UPDATE

I hacked together an ugly shell solution, which does the job for now.

#!/bin/bash
pfound=false; v0=2; v1=6
for p in /{usr/,}bin/python*; do  
  v=($(python -V 2>&1 | cut -c 7- | sed 's/\./ /g'))
  if [[ ${v[0]} -eq $v0 && ${v[1]} -eq $v1 ]]; then pfound=true; break; fi
done
if ! $pfound; then echo "no suitable python version (2.6.x) found."; exit 1; fi
$p - $* <<EOF

PYTHON SCRIPT GOES HERE

EOF

explanation: get version number (v is a bash array) and check

v=($(python -V 2>&1 | cut -c 7- | sed 's/\./ /g'))
if [[ ${v[0]} -eq $v0 && ${v[1]} -eq $v1 ]]; then pfound=true; break; fi

launch found program $p with input from stdin (-) and pass arguments ($*)

$p - $* <<EOF
...
EOF

Solution

  • #!/bin/sh
    ''''which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" # '''
    ''''which python  >/dev/null 2>&1 && exec python  "$0" "$@" # '''
    ''''exec echo "Error: I can't find python anywhere"         # '''
    
    import sys
    print sys.argv
    

    This is first run as a shell script. You can put almost any shell code in between '''' and # '''. Such code will be executed by the shell. Then, when python runs on the file, python will ignore the lines as they look like triple-quoted strings to python.

    The shell script tests if the binary exists in the path with which python2 >/dev/null and then executes it if so (with all arguments in the right place). For more on this, see Why does this snippet with a shebang #!/bin/sh and exec python inside 4 single quotes work?

    Note: The line starts with four ' and their must be no space between the fourth ' and the start of the shell command (which...)