93

I needed to have a directly executable python script, so i started the file with #!/usr/bin/env python. However, I also need unbuffered output, so i tried #!/usr/bin/env python -u, but that fails with python -u: no such file or directory.

I found out that #/usr/bin/python -u works, but I need it to get the python in PATH to support virtual env environments.

What are my options?

Matt Joiner
  • 112,946
  • 110
  • 377
  • 526
Eskil
  • 3,385
  • 5
  • 28
  • 32
  • 2
    You could have a look at this [SO question](http://stackoverflow.com/questions/881696/unbuffered-stdout-in-python-as-in-python-u-from-within-the-program) for a bit of info about how to do unbuffered output. – Mattias Nilsson Jul 22 '10 at 08:00

9 Answers9

44

In some environment, env doesn't split arguments. So your env is looking for python -u in your path. We can use sh to work around. Replace your shebang with the following code lines and everything will be fine.

#!/bin/sh
''''exec python -u -- "$0" ${1+"$@"} # '''
# vi: syntax=python

p.s. we need not worry about the path to sh, right?

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Ade YU
  • 2,292
  • 3
  • 18
  • 28
  • 4
    For those wondering how this works: [Why does this snippet work?](http://stackoverflow.com/q/17458528) – Martijn Pieters Aug 10 '13 at 22:06
  • 16
    the [`${1+"$@"}` hack](http://www.perl.com/doc/FMTEYEWTK/sh_dollar_at) has probably been unnecessary for at least 20 years :) – user4815162342 Oct 10 '13 at 19:44
  • 2
    The hack is unnecessary perhaps, but it doesn't do any harm does it? It's fun to know about it :-) I just learned about it today. Anyway, I think `"exec" "python" "-u" "--" "$0" "$@"` might be easier to understand - is there any flaw in it? (I think it's not compatible with the `1+` hack?) – Aaron McDaid Sep 26 '14 at 09:12
  • @AaronMcDaid Since the hack is unnecessary, your version works fine and is really much more readable! Great! – Ade YU Oct 14 '14 at 07:20
  • 1
    There is a disadvantage with my method. If you want to pass something complicated to bash, such as a string with nested `'` or `"`, then your method is more reliable. It's an interesting issue! Mine is easier to understand perhaps, but yours is more robust. Perhaps your answer should clarify that it must start with `''''exec`, and the string must end in `# '''` (with a space before the `#`). As long as we follow those rules, and don't have any extra triple-quotes `'''`, your method is perfect and flexible. – Aaron McDaid Oct 14 '14 at 09:30
  • Unfortunately this way conflicts with module's docstring. Is there any way to separate that hack and module's docstring? – Vladyslav Savchenko Aug 26 '16 at 10:57
  • 1
    @user4815162342 [Here](https://www.in-ulm.de/~mascheck/various/bourne_args/) is more context on `${1+"$@"}`. So `"$@"` should work fine on its own in most cases. – akhan Feb 27 '18 at 08:01
  • So in summary, this trick is using python's module doc string to run a mini shell script first. – akhan Jul 15 '18 at 21:33
36

This might be a little bit outdated but env(1) manual tells one can use '-S' for that case

#!/usr/bin/env -S python -u

It seems to work pretty good on FreeBSD.

user3249132
  • 469
  • 4
  • 2
31

It is better to use environment variable to enable this. See python doc : http://docs.python.org/2/using/cmdline.html

for your case:

export PYTHONUNBUFFERED=1
script.py
Quentin Pradet
  • 4,691
  • 2
  • 29
  • 41
Larry Cai
  • 55,923
  • 34
  • 110
  • 156
16

When you use shebang on Linux, the entire rest of the line after the interpreter name is interpreted as a single argument. The python -u gets passed to env as if you'd typed: /usr/bin/env 'python -u'. The /usr/bin/env searches for a binary called python -u, which there isn't one.

kenorb
  • 155,785
  • 88
  • 678
  • 743
Jimmy Hartzell
  • 307
  • 2
  • 4
12

Passing arguments to the shebang line is not standard and in as you have experimented do not work in combination with env in Linux. The solution with bash is to use the builtin command "set" to set the required options. I think you can do the same to set unbuffered output of stdin with a python command.

my2c

neuro
  • 14,948
  • 3
  • 36
  • 59
8

Here is a script alternative to /usr/bin/env, that permits passing of arguments on the hash-bang line, based on /bin/bash and with the restriction that spaces are disallowed in the executable path. I call it "envns" (env No Spaces):

#!/bin/bash

ARGS=( $1 )  # separate $1 into multiple space-delimited arguments.
shift # consume $1

PROG=`which ${ARGS[0]}`
unset ARGS[0] # discard executable name

ARGS+=( "$@" ) # remainder of arguments preserved "as-is".
exec $PROG "${ARGS[@]}"

Assuming this script is located at /usr/local/bin/envns, here's your shebang line:

#!/usr/local/bin/envns python -u

Tested on Ubuntu 13.10 and cygwin x64.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
philwalk
  • 634
  • 1
  • 7
  • 15
4

This is a kludge and requires bash, but it works:

#!/bin/bash

python -u <(cat <<"EOF"
# Your script here
print "Hello world"
EOF
)
imgx64
  • 4,062
  • 5
  • 28
  • 44
3

Building off of Larry Cai's answer, env allows you to set a variable directly in the command line. That means that -u can be replaced by the equivalent PYTHONUNBUFFERED setting before python:

#!/usr/bin/env PYTHONUNBUFFERED="YESSSSS" python

Works on RHEL 6.5. I am pretty sure that feature of env is just about universal.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • 5
    FYI, this does NOT work in Debian. I'm not sure why it doesn't work (to look at `ps` output there shouldn't be any difference) but it never returns. It's not very clear if python itself is actually running at all when you do this in Debian. I tried this in a few places - definitely doesn't work as expected versus the equivalent command line. – MartyMacGyver Jul 22 '16 at 06:28
  • @MartyMacGyver. Very likely to do with the version of `env` or even `python` you are using. – Mad Physicist Jul 22 '16 at 13:30
  • Could be the version of env, but it's not working with any modern Debian variant thus far. It doesn't appear that Python is actually running in this scenario on Debian, making it of limited use outside certain platforms and/or configs. – MartyMacGyver Jul 23 '16 at 17:23
-2

I recently wrote a patch for the GNU Coreutils version of env to address this issue:

http://lists.gnu.org/archive/html/coreutils/2017-05/msg00018.html

If you have this, you can do:

#!/usr/bin/env :lang:--foo:bar

env will split :lang:foo:--bar into the fields lang, foo and --bar. It will search PATH for the interpreter lang, and then invoke it with arguments --foo, bar, plus the path to the script and that script's arguments.

There is also a feature to pass the name of the script in the middle of the options. Suppose you want to run lang -f <thecriptname> other-arg, followed by the remaining arguments. With this patched env, it is done like this:

#!/usr/bin/env :lang:-f:{}:other-arg

The leftmost field which is equivalent to {} is replaced with the first argument that follows, which, under hash bang invocation, is the script name. That argument is then removed.

Here, other-arg could be something processed by lang or perhaps something processed by the script.

To understand better, see the numerous echo test cases in the patch.

I chose the : character because it is an existing separator used in PATH on POSIX systems. Since env does PATH searching, it's vanishingly unlikely to be used for a program whose name contains a colon. The {} marker comes from the find utility, which uses it to denote the insertion of a path into the -exec command line.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • 2
    To anyone reading this and feeling hopeful, the patch [was abandoned](http://lists.gnu.org/archive/html/coreutils/2017-05/msg00025.html). – Alex Miller Jul 20 '17 at 06:13