gpt4 book ai didi

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

转载 作者:IT王子 更新时间:2023-10-29 00:25:12 24 4
gpt4 key购买 nike

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

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

现在,棘手的部分是用户可以通过多种方式调用命令:

  • 他们可能使用了 python foo.py就像在例子中一样
  • 他们可能使用了 /usr/bin/foo.py
  • 他们可能有一个 shell 别名 frob='python foo.py' ,实际上跑了frob
  • 也许它甚至是一个 git 别名 flab=!/usr/bin/foo.py , 他们使用了 git flab

在任何情况下,我都希望消息反射(reflect)用户如何调用命令,这样我提供的示例才有意义。

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

有什么想法吗?

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

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

在以下假设下:

  • 脚本是从 bash 交互式启动的
  • 脚本以这 3 种方式之一启动:
    1. foo <args>其中 foo 是符号链接(symbolic link)/usr/bin/foo -> foo.py
    2. git foo其中 alias.foo=!/usr/bin/foo 在 ~/.gitconfig
    3. git baz其中 alias.baz=!/usr/bin/foo 在 ~/.gitconfig

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

我知道这是不可能的,所以我现在接受 Charles Duffy 的回答。

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

trap 'export LAST_BASH_COMMAND=$(history 1)' DEBUG

在他们的 .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.")

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

最佳答案

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

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

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

值得注意的是,存在三个参数:

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

这里没有一个字符串提供原始用户输入的 shell 命令,从中请求新进程的调用。 尤其如此,因为并非所有程序都是从 shell 启动的;考虑一下您的程序是从另一个具有 shell=False 的 Python 脚本启动的情况。


在 UNIX 上,假设您的程序是通过 argv[0] 中给出的任何名称启动的,这是完全常规的;这适用于符号链接(symbolic link)。

您甚至可以看到标准的 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

如果您确实想要生成 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))

同样,这不会“取消扩展”别名,恢复到被调用以调用调用您的命令的函数的代码,等等;没有响铃。


可以用于在父进程树中查找 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

关于linux - 有没有办法知道用户如何从 bash 调用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51301508/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com