When a script is invoked explicitly with python
, the argv
is mucked with so that argv[0]
is the path to the script being run. This is the case if invoked as python foo/bar.py
or even as python -m foo.bar
.
I need a way to recover the original argv
(ie. the one received by python
). Unfortunately, it's not as easy as prepending sys.executable
to sys.argv
because python foo/bar.py
is different than python -m foo.bar
(the implicit PYTHONPATH
differs, which can be crucial depending on your module structure).
More specifically in the cases of python foo/bar.py some other args
and python -m foo.bar some other args
, I'm looking to recover ['python', 'foo/bar.py', 'some', 'other', 'args']
and ['python', '-m', 'foo.bar', 'some', 'other', 'args']
, respectively.
I am aware of prior questions about this:
- how to get the ORIGINAL command line in python? with spaces, tabs, etc
- Full command line as it was typed
But these seem to have a misunderstanding of how shells work and the answers reflect this. I am not interested in undoing the work of the shell (eg. evaluated shell vars and functions are fine), I just want to get at the original argv
given to python
.
The only solution I've found is to use /proc/<PID>/cmdline
:
import os
with open("/proc/{}/cmdline".format(os.getpid()), 'rb') as f:
original_argv = f.read().split('\0')[:-1]
This does work, but it is Linux-only (no OSX, and Windows support seems to require installing the wmi package). Fortunately for my current use case this restriction is fine. But, it would be nice to have a cleaner, cross platform approach.
The fact that that /proc/<PID>/cmdline
approach works gives me hope that python isn't execing before it runs the script (at least not the syscall exec, but maybe the exec
builtin). I remember reading somewhere that all of this argument handling (ex. -m
) is done in pure python, not C (this is confirmed by the fact that python -m this.does.not.exist
will produce an exception that looks like it came from the runtime). So, I'd venture a guess that somewhere in pure python the original argv
is available (perhaps this requires some spelunking through the runtime initialization?).
tl;dr Is there a cross platform (builtin, preferably) way to get at the original argv
passed to python
(before it remove the python
executable and transforms -m blah
into blah.py
)?
edit From spelunking, I discovered Py_GetArgcArgv
, which can be accessed via ctypes (found it here, links to several SO posts that mention this approach):
import ctypes
_argv = ctypes.POINTER(ctypes.c_wchar_p)()
_argc = ctypes.c_int()
ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(_argc),
ctypes.byref(_argv))
argv = _argv[:_argc.value]
print(argv)
Now this is OS-portable, but not python implementation portable (only works on cpython and ctypes
is yucky if you don't need it). Also, peculiarly, I don't get the right output on Ubunutu 16.04 (python -m foo.bar
gives me ['python', '-m', '-m']
), but I may just be making a silly mistake (I get the same behavior on OSX). It would be great to have a fully portable solution (that doesn't dig into ctypes
).