有没有办法知道用户如何从bash调用程序?

its*_*dok 14 linux bash command-line-interface

这是问题所在:我有这个脚本foo.py,如果用户在没有--bar选项的情况下调用它,我想显示以下错误消息:

Please add the --bar option to your command, like so:
    python foo.py --bar
Run Code Online (Sandbox Code Playgroud)

现在,棘手的部分是用户可能有多种方式调用该命令:

  • 他们可能python foo.py在示例中使用过
  • 他们可能已经使用过 /usr/bin/foo.py
  • 它们可能有一个shell别名frob='python foo.py',并且实际上已经运行了frob
  • 也许它甚至是一个git别名flab=!/usr/bin/foo.py,他们使用了git flab

在每种情况下,我都希望消息反映用户如何调用命令,这样我提供的示例才有意义.

sys.argv总是包含foo.py,/proc/$$/cmdline不知道别名.在我看来,这个信息的唯一可能来源是bash本身,但我不知道如何问它.

有任何想法吗?

更新如果我们将可能的情况限制为仅限于上面列出的情况怎么样?

更新2:很多人写了很好的解释为什么在一般情况下这是不可能的,所以我想限制我的问题:

根据以下假设:

  • 该脚本是从bash以交互方式启动的
  • 该脚本以以下三种方式之一开始:
    1. foo <args> 其中foo是符号链接/ usr/bin/foo - > foo.py
    2. git foo 其中alias.foo =!/ usr/bin/foo in ~/.gitconfig
    3. git baz 其中alias.baz =!/ usr/bin/foo in ~/.gitconfig

有没有办法区分脚本中的1和(2,3)?有没有办法区分脚本中的2和3?

我知道这是一个很长的镜头,所以我现在接受了Charles Duffy的回答.

更新3:到目前为止,Charles Duffy在下面的评论中提出了最有希望的角度.如果我可以让我的用户拥有

trap 'export LAST_BASH_COMMAND=$(history 1)' DEBUG
Run Code Online (Sandbox Code Playgroud)

在他们的.bashrc,我可以在我的代码中使用这样的东西:

like_so = None
cmd = os.environ['LAST_BASH_COMMAND']
if cmd is not None:
    cmd = cmd[8:]  # Remove the history counter
    if cmd.startswith("foo "):
        like_so = "foo --bar " + cmd[4:]
    elif cmd.startswith(r"git foo "):
        like_so = "git foo --bar " + cmd[8:]
    elif cmd.startswith(r"git baz "):
        like_so = "git baz --bar " + cmd[8:]
if like_so is not None:
    print("Please add the --bar option to your command, like so:")
    print("    " + like_so)
else:
    print("Please add the --bar option to your command.")
Run Code Online (Sandbox Code Playgroud)

这样,如果我不设法获取它们的调用方法,我会显示一般消息.当然,如果我要依赖于改变用户的环境,我也可以确保各种别名导出我自己可以看到的环境变量,但至少这种方式允许我对任何用户使用相同的技术.我可能稍后添加的其他脚本.

Cha*_*ffy 16

不,没有办法看到原始文本(在别名/函数/等之前).

在UNIX中启动程序在底层系统调用级别完成如下:

int execve(const char *path, char *const argv[], char *const envp[]);
Run Code Online (Sandbox Code Playgroud)

值得注意的是,有三个论点:

  • 可执行文件的路径
  • 一个argv数组(其中的第一项 - argv[0]$0- 传递给该可执行文件以反映启动它的名称)
  • 环境变量列表

这里没有任何地方提供原始用户输入的shell命令,从该命令请求新进程的调用.这尤其正确,因为并非所有程序都是从shell启动的 ; 考虑从另一个Python脚本启动程序的情况shell=False.


在UNIX上完全传统的假设您的程序是通过给出的任何名称启动的argv[0]; 这适用于符号链接.

您甚至可以看到标准的UNIX工具:

$ 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 
Run Code Online (Sandbox Code Playgroud)

如果您确实要生成UNIX命令行,请使用pipes.quote()(Python 2)或shlex.quote()(Python 3)安全地执行此操作.

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))
Run Code Online (Sandbox Code Playgroud)

同样,这不会"取消扩展"别名,恢复为调用调用调用命令的函数的代码等; 钟声没有响.


用于在父进程树中查找git实例,并发现其参数列表:

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
Run Code Online (Sandbox Code Playgroud)