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

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


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]; this works for symlinks.

You can even see standard UNIX tools doing this:

$ ls '*.txt'         # sample command to generate an error message; note "ls:" at the frontls: *.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 2except ImportError:    from shlex import quote # Python 3cmd = ' '.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.


That can be used to look for a git instance in your parent process tree, and discover its argument list:

def find_cmdline(pid):    return open('/proc/%d/cmdline' % (pid,), 'r').read().split('\0')[:-1]def find_ppid(pid):    stat_data = open('/proc/%d/stat' % (pid,), 'r').read()    stat_data_sanitized = re.sub('[(]([^)]+)[)]', '_', stat_data)    return int(stat_data_sanitized.split(' ')[3])def all_parent_cmdlines(pid):    while pid > 0:        yield find_cmdline(pid)        pid = find_ppid(pid)def find_git_parent(pid):    for cmdline in all_parent_cmdlines(pid):        if cmdline[0] == 'git':            return ' '.join(quote(s) for s in cmdline)    return None


See the Note at the bottom regarding the originally proposed wrapper script.

A new more flexible approach is for the python script to provide a new command line option, permitting users to specify a custom string they would prefer to see in error messages.

For example, if a user prefers to call the python script 'myPyScript.py' via an alias, they can change the alias definition from this:

  alias myAlias='myPyScript.py $@'

to this:

  alias myAlias='myPyScript.py --caller=myAlias $@'

If they prefer to call the python script from a shell script, it can use the additional command line option like so:

  #!/bin/bash  exec myPyScript.py "$@" --caller=${0##*/}

Other possible applications of this approach:

  bash -c myPyScript.py --caller="bash -c myPyScript.py"  myPyScript.py --caller=myPyScript.py

For listing expanded command lines, here's a script 'pyTest.py', based on feedback by @CharlesDuffy, that lists cmdline for the running python script, as well as the parent process that spawned it.If the new -caller argument is used, it will appear in the command line, although aliases will have been expanded, etc.

#!/usr/bin/env pythonimport os, rewith open ("/proc/self/stat", "r") as myfile:  data = [x.strip() for x in str.split(myfile.readlines()[0],' ')]pid = data[0]ppid = data[3]def commandLine(pid):  with open ("/proc/"+pid+"/cmdline", "r") as myfile:    return [x.strip() for x in str.split(myfile.readlines()[0],'\x00')][0:-1]pid_cmdline = commandLine(pid)ppid_cmdline = commandLine(ppid)print "%r" % pid_cmdlineprint "%r" % ppid_cmdline

After saving this to a file named 'pytest.py', and then calling it from a bash script called 'pytest.sh' with various arguments, here's the output:

$ ./pytest.sh a b "c d" e['python', './pytest.py']['/bin/bash', './pytest.sh', 'a', 'b', 'c d', 'e']

NOTE: criticisms of the original wrapper script aliasTest.sh were valid. Although the existence of a pre-defined alias is part of the specification of the question, and may be presumed to exist in the user environment, the proposal defined the alias (creating the misleading impression that it was part of the recommendation rather than a specified part of the user's environment), and it didn't show how the wrapper would communicate with the called python script. In practice, the user would either have to source the wrapper or define the alias within the wrapper, and the python script would have to delegate the printing of error messages to multiple custom calling scripts (where the calling information resided), and clients would have to call the wrapper scripts. Solving those problems led to a simpler approach, that is expandable to any number of additional use cases.

Here's a less confusing version of the original script, for reference:

#!/bin/bashshopt -s expand_aliasesalias myAlias='myPyScript.py'# called like this:set -o historymyAlias $@_EXITCODE=$?CALL_HISTORY=( `history` )_CALLING_MODE=${CALL_HISTORY[1]}case "$_EXITCODE" in0) # no error message required  ;;1)  echo "customized error message #1 [$_CALLING_MODE]" 1>&2  ;;2)  echo "customized error message #2 [$_CALLING_MODE]" 1>&2  ;;esac

Here's the output:

$ aliasTest.sh 1 2 3['./myPyScript.py', '1', '2', '3']customized error message #2 [myAlias]


There is no way to distinguish between when an interpreter for a script is explicitly specified on the command line and when it is deduced by the OS from the hashbang line.

Proof:

$ cat test.sh #!/usr/bin/env bashps -o command $$$ bash ./test.sh COMMANDbash ./test.sh$ ./test.sh COMMANDbash ./test.sh

This prevents you from detecting the difference between the first two cases in your list.

I am also confident that there is no reasonable way of identifying the other (mediated) ways of calling a command.