#!/bin/bash set -eu set -o pipefail cmd="" file="" stdin=/dev/null give_python_version=True function usage { retval="$1" case "$retval" in 0) ;; *) exec 1>&2 ;; esac echo "Usage: $0 --command python-command --file python-file --ellide-python-version --stdin filename" echo echo "--stdin allows you to specify to redirect into each interpreter. It defaults to /dev/null" ( echo "--give-python-version instructs this command to output the 'version of python' implemented - nice for Pypy, Jython," echo "micropython" ) | fmt exit "$retval" } while [ "$#" -ge 1 ] do case "$1" in --ellide-python-version) give_python_version=False ;; --command) cmd="$2" shift ;; --stdin) stdin="$2" shift ;; --file) file="$2" shift ;; -h|--help) usage 0 ;; *) echo "$0: Unrecognized option: $1" 1>&2 usage 1 ;; esac shift done specified=0 if [ "$cmd" != "" ] then specified=$((specified+1)) fi if [ "$file" != "" ] then specified=$((specified+1)) fi case "$specified" in 0) echo "$0: You must specify exactly one of --command and --file" 1>&2 usage 1 ;; 1) # Good, we got exactly one, fall through ;; 2) echo "$0: You must not specify both --command and --file" 1>&2 usage 1 ;; *) echo "$0: Internal error" 1>&2 exit 1 ;; esac function run { interpreter="$1" if [ "$cmd" = "" ] then if [ "$file" = "" ] then echo "$0: Internal error 2" 1>&2 else "$interpreter" "$file" < "$stdin" fi else if [ "$file" = "" ] then if ! dash_c_understood "$interpreter" then echo skipped return 0 fi "$interpreter" -c "$cmd" < "$stdin" else echo "$0: Internal error 3" 1>&2 fi fi } function dash_c_understood { interp="$1" if ! output=$(set -eu; "$interp" -c '1' 2>&1) then # 0.9 does not understand -c return 1 fi return 0 } function get_interp_vers { interp="$1" if ! dash_c_understood "$interp" then echo unknown return 1 fi "$interp" -c ' via_platform = 0 check_sys = 0 via_sys_version_info = 0 via_sys_version = 0 test_sys = 0 try: import platform except (ImportError, NameError): # We have no platform module - try to get the info via the sys module check_sys = 1 if not check_sys: if hasattr(platform, "python_version"): via_platform = 1 else: check_sys = 1 if check_sys: try: import sys test_sys = 1 except (ImportError, NameError): # just let via_sys_version_info and via_sys_version remain False - we have no sys module pass if test_sys: if hasattr(sys, "version_info"): via_sys_version_info = 1 elif hasattr(sys, "version"): via_sys_version = 1 else: # just let via_sys remain False pass if via_platform: # This gives pretty good info, but is not available in older interpreters. Also, micropython has a # platform module that does not really contain anything. print(platform.python_version()) elif via_sys_version_info: # This is compatible with some older interpreters, but does not give quite as much info. print("%s.%s.%s" % sys.version_info[:3]) elif via_sys_version: import string # This is compatible with some older interpreters, but does not give quite as much info. verbose_version = sys.version version_list = string.split(verbose_version) print(version_list[0]) else: print("unknown") ' } function format { # shellcheck disable=SC2046 if [ $(echo "$output" | wc -l) -gt 1 ] then echo # shellcheck disable=SC2001 echo "$output" | sed 's/^/ /' else echo "$output" fi } function sort_by_version { python3 -c ' import sys import decimal interps=[] for line in (line.rstrip("\n") for line in sys.stdin): # line EG: /usr/local/cpython-3.10/bin/python fields = line.split("-") vers_and_interp=fields[1] fields2 = vers_and_interp.split("/") vers = fields2[0] fields3 = vers.partition(".") major_vers = int(fields3[0]) minor_vers = decimal.Decimal(fields3[2]) interps.append((major_vers, minor_vers, line)) interps.sort() for major_vers, minor_vers, line in interps: print(line) ' } function interpreters ( find /usr/local/cpython-*/bin/python -print 2> /dev/null | sort_by_version find /usr/local/jython-*/bin/jython -print 2> /dev/null find /usr/local/pypy-*/bin/pypy -print 2> /dev/null | sort_by_version find /usr/local/pypy3-*/bin/pypy3 -print 2> /dev/null | sort_by_version find /usr/local/micropython-*/bin/micropython -print 2> /dev/null find /usr/local/tauthon-*/bin/tauthon -print 2> /dev/null ) summary_file="/tmp/pythons.$$" # shellcheck disable=SC2064 trap "rm -f \"$summary_file\"" 0 1 15 for interpreter in $(interpreters) do case "$give_python_version" in True) # platform.python_version() is nice for this, but it doesn't go as far back in Python history as sys.version_info if vers=$(get_interp_vers "$interpreter") then vers_string=" ($vers)" else vers_string=" (unknown)" fi ;; False) vers_string="" ;; *) echo "$0: Internal error: \$give_python_version not True or False: $give_python_version" 1>&2 exit 1 ;; esac if output=$(run "$interpreter" -c "$cmd" 2>&1) then if [ "$output" = skipped ] then echo "$interpreter""$vers_string" skipped "$(format "$output")" echo skipped >> "$summary_file" else echo "$interpreter""$vers_string" good "$(format "$output")" echo good >> "$summary_file" fi else echo "$interpreter""$vers_string" 'bad ' "$(format "$output")" echo bad >> "$summary_file" fi done echo histogram < "$summary_file"