Is there a way to know how the user invoked a program from bash?

  • A+
Category:Languages

Here's the problem: I have this script foo.py, and if the user invokes it without the --bar option, I'd like to display the following error message:

Please add the --bar option to your command, like so:     python foo.py --bar 

Now, the tricky part is that there are several ways the user might have invoked the command:

  • They may have used python foo.py like in the example
  • They may have used /usr/bin/foo.py
  • They may have a shell alias frob='python foo.py', and actually ran frob
  • Maybe it's even a git alias flab=!/usr/bin/foo.py, and they used git flab

In every case, I'd like the message to reflect how the user invoked the command, so that the example I'm providing would make sense.

sys.argv always contains foo.py, and /proc/$$/cmdline doesn't know about aliases. It seems to me that the only possible source for this information would be bash itself, but I don't know how to ask it.

Any ideas?

UPDATE How about if we limit possible scenarios to only those listed above?

UPDATE 2: Plenty of people wrote very good explanation about why this is not possible in the general case, so I would like to limit my question to this:

Under the following assumptions:

  • The script was started interactively, from bash
  • The script was start in one of these 3 ways:
    1. foo <args> where foo is a symbolic link /usr/bin/foo -> foo.py
    2. git foo where alias.foo=!/usr/bin/foo in ~/.gitconfig
    3. git baz where alias.baz=!/usr/bin/foo in ~/.gitconfig

Is there a way to distinguish between 1 and (2,3) from within the script? Is there a way to distinguish between 2 and 3 from within the script?

I know this is a long shot, so I'm accepting Charles Duffy's answer for now.

 


No, there is no way to see the original text (before aliases/functions/etc).

Starting a program in UNIX is done as follows at the underlying syscall level:

int execve(const char *path, char *const argv[], char *const envp[]); 

Notably, there are three arguments:

  • The path to the executable
  • An argv array (the first item of which -- argv[0] or $0 -- is passed to that executable to reflect the name under which it was started)
  • A list of environment variables

Nowhere in here is there a string that provides the original user-entered shell command from which the new process's invocation was requested. This is particularly true since not all programs are started from a shell at all; consider the case where your program is started from another Python script with shell=False.


It's completely conventional on UNIX to assume that your program was started through whatever name is given in argv[0].

You can even see standard UNIX tools doing this:

$ ls '*.txt'         # sample command to generate an error message; note "ls:" at the front ls: *.txt: No such file or directory $ (exec -a foobar ls '*.txt')   # again, but tell it that its name is "foobar" foobar: *.txt: No such file or directory $ alias somesuch=ls             # this **doesn't** happen with an alias $ somesuch '*.txt'              # ...the program still sees its real name, not the alias! ls: *.txt: No such file  

If you do want to generate a UNIX command line, use pipes.quote() (Python 2) or shlex.quote() (Python 3) to do it safely.

try:     from pipes import quote # Python 2 except ImportError:     from shlex import quote # Python 3  cmd = ' '.join(quote(s) for s in open('/proc/self/cmdline', 'r').read().split('/0')[:-1]) print("We were called as: {}".format(cmd)) 

Again, this won't "un-expand" aliases, revert to the code that was invoked to call a function that invoked your command, etc; there is no un-ringing that bell.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: