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别名
,他们使用了git flabflab=/usr/bin/foo.py
sys.argv
始终包含foo.py
,并且/proc/$$/cmdline
不知道别名。在我看来,这些信息的唯一可能来源是bash本身,但我不知道如何询问它
有什么想法吗
更新如果我们将可能的场景限制在上面列出的那些场景,怎么样
更新2:很多人都写了很好的解释,解释了为什么在一般情况下不可能做到这一点,所以我想把我的问题限制在以下几个方面:
在以下假设下:
- 脚本是从bash以交互方式启动的
- 脚本以以下三种方式之一开始:
其中foo是一个符号链接/usr/bin/foo->foo.pyfoo
where alias.foo=/usr/bin/foo在gitfoo
~/.gitconfig
where alias.baz=/usr/bin/foo在gitbaz
~/.gitconfig
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
变量,并在falsepython
所在的目录前添加前缀
在另一种情况下,您可以替换/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
- 如果用户不是真正精通命令行,他们可以粘贴显示的工作命令,即使他们不知道其中的区别
- 如果是,他们应该能够毫无困难地理解消息,并调整其命令行
$ 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