Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/user-interface/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Linux 有没有办法知道用户是如何从bash调用程序的?_Linux_Bash_Command Line Interface - Fatal编程技术网

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

Linux 有没有办法知道用户是如何从bash调用程序的?,linux,bash,command-line-interface,Linux,Bash,Command Line Interface,问题是:我有这个脚本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',并实际运行f

问题是:我有这个脚本
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
在任何情况下,我都希望消息反映用户是如何调用命令的,这样我提供的示例才有意义

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

有什么想法吗

更新如果我们将可能的场景限制在上面列出的那些场景,怎么样

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

在以下假设下:

  • 脚本是从bash以交互方式启动的
  • 脚本以以下三种方式之一开始:
  • foo
    其中foo是一个符号链接/usr/bin/foo->foo.py
  • gitfoo
    where alias.foo=/usr/bin/foo在
    ~/.gitconfig
  • gitbaz
    where alias.baz=/usr/bin/foo在
    ~/.gitconfig
有没有办法从脚本中区分1和(2,3)?有没有办法从脚本中区分2和3

我知道这很难,所以我现在接受查尔斯·达菲的答案

更新3:到目前为止,查尔斯·达菲在下面的评论中提出了最有希望的角度。如果我能让我的用户

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.")

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

我知道这是
bash
任务,但我认为最简单的方法是修改'foo.py'。当然,这取决于脚本的复杂程度,但它可能适合。以下是示例代码:

#!/usr/bin/python

import sys

if len(sys.argv) > 1 and sys.argv[1] == '--bar':
    print 'make magic'
else:
    print 'Please add the --bar option to your command, like so:'
    print '    python foo.py --bar'
在这种情况下,用户如何运行此代码并不重要

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

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

$ ./a.py --bar
make magic

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

$ /home/3sky/test/a.py
Please add the --bar option to your command, like so:
    python foo.py --bar

$ alias a='python a.py'
$ a
Please add the --bar option to your command, like so:
    python foo.py --bar

$ a --bar
make magic

我可以看到两种方法:

  • 正如3sky所建议的,最简单的方法是从python脚本内部解析命令行。可用于以可靠的方式执行此操作。这只有在您可以更改该脚本时才有效
  • 一种更复杂、更通用、更复杂的方法是更改系统上的
    python
    可执行文件

由于第一个选项有很好的文档记录,下面是关于第二个选项的更多细节:

不管脚本的调用方式如何,都会运行
python
。这里的目标是用一个脚本替换
python
可执行文件,该脚本检查
foo.py
是否在参数中,如果在参数中,则检查
--bar
是否也在参数中。如果没有,请打印消息并返回

在其他情况下,执行真正的python可执行文件

现在,希望通过下面的说明来运行python:
#/usr/bin/env python3
,或槽
python foo.py
,而不是
#的变体/usr/bin/python
/usr/bin/python foo.py
。这样,您就可以更改
$PATH
变量,并在false
python
所在的目录前添加前缀

在另一种情况下,您可以替换
/usr/bin/python可执行文件
,但有可能无法很好地处理更新

更复杂的方法可能是使用名称空间和挂载,但是上面的方法可能就足够了,特别是如果您拥有管理员权限的话


可以用作脚本的示例:

#!/usr/bin/env bash

function checkbar
{
    for i in "$@"
    do
            if [ "$i" = "--bar" ]
            then
                    echo "Well done, you added --bar!"
                    return 0
            fi
    done
    return 1
}

command=$(basename ${1:-none})
if [ $command = "foo.py" ]
then
    if ! checkbar "$@"
    then
        echo "Please add --bar to the command line, like so:"
        printf "%q " $0
        printf "%q " "$@"
        printf -- "--bar\n"
        exit 1
    fi
fi
/path/to/real/python "$@"
然而,在重读你的问题之后,很明显我误解了它。在我看来,打印“foo.py必须像foo.py--bar一样被调用”、“请将bar添加到参数中”或“请尝试(而不是)”,这都是可以的,不管用户输入了什么:

  • 如果是(git)别名,则这是一次性错误,用户将在创建别名后尝试别名,以便知道将
    --bar
    部件放置在何处
  • 使用
    /usr/bin/foo.py
    python foo.py
    • 如果用户不是真正精通命令行,他们可以粘贴显示的工作命令,即使他们不知道其中的区别
    • 如果是,他们应该能够毫无困难地理解消息,并调整其命令行

无法区分脚本的解释器何时在命令行上显式指定,何时由操作系统从hashbang行推断

证明:

$ cat test.sh 
#!/usr/bin/env bash

ps -o command $$

$ bash ./test.sh 
COMMAND
bash ./test.sh

$ ./test.sh 
COMMAND
bash ./test.sh
这会阻止您检测列表中前两个案例之间的差异


我还确信,没有合理的方法来识别调用命令的其他(中介)方式。

请参阅底部关于最初建议的包装器脚本的说明

一种新的更灵活的方法是python脚本提供一个新的命令行选项,允许用户指定他们希望在中看到的自定义字符串
  alias myAlias='myPyScript.py $@'
  alias myAlias='myPyScript.py --caller=myAlias $@'
  #!/bin/bash
  exec myPyScript.py "$@" --caller=${0##*/}
  bash -c myPyScript.py --caller="bash -c myPyScript.py"

  myPyScript.py --caller=myPyScript.py
#!/usr/bin/env python

import os, re

with 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_cmdline
print "%r" % ppid_cmdline
$ ./pytest.sh a b "c d" e
['python', './pytest.py']
['/bin/bash', './pytest.sh', 'a', 'b', 'c d', 'e']
#!/bin/bash
shopt -s expand_aliases
alias myAlias='myPyScript.py'

# called like this:
set -o history
myAlias $@
_EXITCODE=$?
CALL_HISTORY=( `history` )
_CALLING_MODE=${CALL_HISTORY[1]}

case "$_EXITCODE" in
0) # 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
$ aliasTest.sh 1 2 3
['./myPyScript.py', '1', '2', '3']
customized error message #2 [myAlias]
int execve(const char *path, char *const argv[], char *const envp[]);
$ 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 
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))
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