15

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
Community
  • 1
  • 1
jaap
  • 5,661
  • 2
  • 20
  • 25
  • 3
    Make your script compatible with Python 3? – Henry Keiter Sep 24 '13 at 22:55
  • Does `python2.7` exist on your system? Often the more specific version is there. – Keith Sep 24 '13 at 22:59
  • No unfortunately, on Debian I have only python2.5 and python2.6 while on Arch I have only python2.7, so I cannot really rely on other people having either one I guess. – jaap Sep 24 '13 at 23:02
  • The suggestion to make it python3 compatible actually works better than I thought. Following this write-up I managed to create some code which works for both python2 and python3 [link](http://python3porting.com/noconv.html) – jaap Sep 24 '13 at 23:14
  • Cool about refactoring, but I would still be curious to know an answer too! – beroe Sep 24 '13 at 23:20
  • So would I, this works for some simple scripts but I wouldn't like to be forced to port my larger codes. – jaap Sep 24 '13 at 23:47
  • 1
    For Debian it would be normal to create a package containing your script, that way it integrates with the target system. As per http://www.debian.org/doc/packaging-manuals/python-policy/ch-python.html, section 1.4.2 you wouldn't use `/usr/bin/env` at all. The same probably applies to Arch linux. It's more overhead, but having your script integrated into the target's packaging system is nicer for your users. – Austin Phillips Sep 25 '13 at 00:13

5 Answers5

22
#!/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...)

Community
  • 1
  • 1
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
1

Something like this:

#!/usr/bin/env python
import sys
import os
if sys.version_info >= (3, 0):
    os.execvp("python2.7", ["python2.7", __file__])
    os.execvp("python2.6", ["python2.6", __file__])
    os.execvp("python2", ["python2", __file__])
    print ("No sutable version of Python found")
    exit(2)

Update Below is a more robust version of the same.

#!/bin/bash

ok=bad
for pyth in python python2.7 python2.6 python2; do
  pypath=$(type -P $pyth)
  if [[ -x $pypath ]] ; then
    ok=$(
      $pyth <<@@

import sys 
if sys.version_info < (3, 0):
  print ("ok")
else:
  print("bad")
@@

    )
    if [[ $ok == ok ]] ; then
      break
    fi
  fi
done

if [[ $ok != ok ]]; then
  echo "Could not find suitable python version"
  exit 2
fi

$pyth <<@@
<<< your python script goes here >>>
@@
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thanks n.m., that seems to be quite close. Except that my python3 interpreter automatically starts complaining about syntax problems so it won't start. I tried enclosing everything after this by a `try: .... except SyntaxError: pass` but no luck – jaap Sep 25 '13 at 14:24
  • Yeah I've just realized Python would do the full syntax check of the entire file before executing anything. So if any syntax incompatibility exists in the script, it will be broken. See update for doing a similar thing with bash. – n. m. could be an AI Sep 25 '13 at 17:22
  • yeah thanks, I had updated my question and added something similar earlier. – jaap Sep 25 '13 at 18:42
0

I'll leave this here for future reference.

All of my own scripts are usually written for Python 3, so I'm using a modified version of Aaron McDaid's answer to check for Python 3 instead of 2:

#!/usr/bin/env sh
''''which python3 >/dev/null 2>&1 && exec python3 "$0" "$@" # '''
''''test $(python --version 2>&1 | cut -c 8) -eq 3 && exec python "$0" "$@" # '''
''''exec echo "Python 3 not found." # '''

import sys
print sys.argv
reitermarkus
  • 678
  • 1
  • 11
  • 27
0

Here is a more concise way for the highest voted answer:

#!/bin/sh
''''exec $(which python3 || which python2 || echo python) $0 $@ #'''

import sys
print(sys.argv)
print(sys.version_info)

You'll get this if none of them found:

'./test.py: 2: exec: python: not found'

Also, get rid of warnings from linter:

'module level import not at top of file - flake8(E402)'
Pamela
  • 549
  • 4
  • 7
0

In my case I don't need the python path or other information (like version).

I have an alias setup to point "python" to "python3". But, my scripts are not aware of the environment alias. Therefore I needed a solution for the scripts to determine the program name, programmatically.

If you have more than one version installed, the sort and tail are going to give you the latest version:

#!/bin/sh

command=$((which python3 || which python2 || which python) | sort | tail -n1 | awk -F "/" '{ print $NF }')

echo $command

Would give a result like: python3

This solution is original but similar to @Pamela

elvis2
  • 111
  • 1
  • 3