这是问题所在:我有这个脚本foo.py
,如果用户在没有--bar
选项的情况下调用它,我想显示以下错误消息:
Please add the --bar option to your command, like so:
python foo.py --bar
现在,棘手的部分是用户可能通过多种方式调用命令:
python foo.py
/usr/bin/foo.py
frob='python foo.py'
,并且实际上已运行frob
flab=!/usr/bin/foo.py
,他们使用了git flab
在每种情况下,我都希望该消息反映用户如何调用命令,以便我提供的示例有意义。
sys.argv
始终包含foo.py
,而/proc/$$/cmdline
不知道别名。在我看来,此信息的唯一可能来源是bash本身,但我不知道该怎么问。
有什么想法吗?
更新,如果我们将可能的情况限制为仅上面列出的情况,该怎么办?
更新2 :很多人对为什么在一般情况下无法做到这一点写了很好的解释,所以我想将问题限于此:
根据以下假设:
foo <args>
,其中foo是符号链接/ usr / bin / foo-> foo.py git foo
,其中~/.gitconfig
中的alias.foo =!/ usr / bin / foo git baz
,其中~/.gitconfig
中的alias.baz =!/ usr / bin / foo 有没有办法从脚本中区分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.")
这样,如果我无法获取其调用方法,则会显示一般消息。当然,如果我要依赖于更改用户的环境,则最好确保各种别名导出自己可以查看的环境变量,但是至少这样可以允许我对任何其他对象使用相同的技术其他脚本,我稍后可能会添加。
答案 0 :(得分:16)
在底层syscall级别上,按照以下步骤在UNIX中启动程序:
int execve(const char *path, char *const argv[], char *const envp[]);
值得注意的是,有三个参数:
argv[0]
或$0
-传递到该可执行文件以反映启动它的名称)这里没有任何地方提供一个字符串,该字符串提供了原始用户输入的shell命令,从中请求了新进程的调用。 尤其如此,因为并非所有程序都是从外壳启动的;考虑您的程序是使用shell=False
从另一个Python脚本启动的情况。
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
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))
同样,这不会“取消扩展”别名,不会恢复为调用调用您的命令的函数的代码等。没有钟声响起。
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
答案 1 :(得分:4)
有关最初建议的包装器脚本,请参见底部的注释。
一种新的更灵活的方法是让python脚本提供一个新的命令行选项,允许用户指定他们希望在错误消息中看到的自定义字符串。
例如,如果用户希望通过别名调用python脚本“ myPyScript.py
”,则可以从中更改别名定义:
alias myAlias='myPyScript.py $@'
对此:
alias myAlias='myPyScript.py --caller=myAlias $@'
如果他们希望从shell脚本中调用python脚本,则可以使用其他命令行选项,如下所示:
#!/bin/bash
exec myPyScript.py "$@" --caller=${0##*/}
此方法的其他可能应用:
bash -c myPyScript.py --caller="bash -c myPyScript.py"
myPyScript.py --caller=myPyScript.py
要列出扩展的命令行,下面是一个脚本'pyTest.py
',该脚本基于@CharlesDuffy的反馈,其中列出了正在运行的python脚本的cmdline以及生成该脚本的父进程。
如果使用了新的-caller参数,则它会出现在命令行中,尽管别名会被扩展,等等。
#!/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.py
”的文件中,然后从带有各种参数的bash脚本“ pytest.sh
”中调用它之后,输出如下:
$ ./pytest.sh a b "c d" e
['python', './pytest.py']
['/bin/bash', './pytest.sh', 'a', 'b', 'c d', 'e']
注意:对原始包装脚本aliasTest.sh
的批评是有效的。尽管预定义别名的存在是问题说明的一部分,并且可以假定存在于用户环境中,但是提案定义了别名(造成误导性的印象,即它是建议的一部分,而不是指定的别名。用户环境的一部分),并且没有显示包装程序如何与调用的python脚本进行通信。在实践中,用户要么必须派发包装器,要么在包装器中定义别名,而python脚本将必须将错误消息的打印委派给多个自定义调用脚本(调用信息所在的位置),而客户端将调用包装器脚本。解决这些问题导致了一种更简单的方法,该方法可以扩展到任意数量的其他用例。
以下是原始脚本的混乱版本,供参考:
#!/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]
答案 2 :(得分:3)
无法区分是在命令行上显式指定脚本的解释器,还是操作系统从hashbang行中推导出脚本的解释器。
证明:
$ cat test.sh
#!/usr/bin/env bash
ps -o command $$
$ bash ./test.sh
COMMAND
bash ./test.sh
$ ./test.sh
COMMAND
bash ./test.sh
这可防止您检测到列表中前两种情况之间的差异。
我也相信,没有合理的方法来识别调用命令的其他(中介)方法。
答案 3 :(得分:0)
我可以看到两种方法:
argparse
可用于可靠地这样做。仅当您可以更改该脚本时,此方法才有效。python
可执行文件。由于第一个选项已被详细记录,因此以下是有关第二个选项的更多详细信息:
无论调用脚本的方式如何,python
都会运行。这里的目标是用一个脚本替换python
可执行文件,该脚本检查foo.py
是否在参数中,如果存在,则检查--bar
是否也在参数中。如果没有,请打印消息并返回。
在其他所有情况下,都执行真正的python可执行文件。
现在,希望可以通过以下#!/usr/bin/env python3
或python foo.py
来运行python,而不是#!/usr/bin/python
或/usr/bin/python foo.py
的变体。这样,您可以更改$PATH
变量,并在错误的python
所在的目录之前添加一个目录。
在另一种情况下,您可以替换/usr/bin/python executable
,以免对更新不满意。
更复杂的方法可能是使用名称空间和挂载,但是以上内容可能就足够了,尤其是如果您具有管理员权限。
可以用作脚本的示例:
#!/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”或“请尝试(而不是)”。用户输入:
--bar
部分放在哪里/usr/bin/foo.py
或python foo.py
一起使用:
答案 4 :(得分:-1)
我知道这是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