【问题标题】:Running a python function as a bash command将 python 函数作为 bash 命令运行
【发布时间】:2015-06-26 09:10:17
【问题描述】:

有很多关于如何从 python 运行 shell 命令的文献,但我有兴趣做相反的事情。我有一个 python 模块 mycommands.py,其中包含如下函数

def command(arg1, arg2):
    pass

def command1(arg1, arg2, arg3):
    pass

函数参数都是字符串。目标是能够从 bash 中运行这些函数,如下所示

$ command arg1 arg2 
$ command1 arg1 arg2 arg3 

到目前为止,我在 .bash_profile 中有以下蛮力设置,我必须手动为每个 python 函数提供 bash 绑定

function command() {
    python -c "import mycommand as m; out=m.command('$1', '$2'); print(out)"
}

function command1() {
    python -c "import mycommand as m; out=m.command1('$1', '$2', '$3'); print(out)"
}

如果能有一个像这样的 bash 命令就好了

$ import_python mycommands.py

它会自动将模块中的所有 python 函数导入为 bash 命令。是否存在实现此类命令的库?

【问题讨论】:

  • 你为什么要这样做?
  • 只需使用Click
  • @tzaman Click 并没有为一个模块中的不同功能提供多个调度,但是,这是大部分问题所在。除此之外,只需导入argparse 并为不同的函数编写解析器。
  • @Dzhelil 为什么不直接启动 python 解释器并直接调用您的函数?
  • 我想要这样做的一个原因是因为 shell 具有出色的文件名完成支持,而 python 解释器没有。从 shell 调用对文件进行操作的函数比从 python 解释器调用要容易得多。

标签: python bash shell


【解决方案1】:

Fabric 可以做到这一点:

from fabric.api import run
def host_type():
    run('uname -s')

fab -H localhost,linuxbox host_type
[本地主机] 运行:uname -s
[本地主机] 出:达尔文
[linuxbox] 运行:uname -s
[linuxbox] 出:Linux
完成。
与本地主机断开连接...完成。
与 linuxbox 断开连接...完成。

或者更具体的问题:

$ cat fabfile.py
def command1(arg1, arg2):
    print arg1, arg2
$ fab command1:first,second

第一秒
完成。

【讨论】:

  • 不知道为什么有人不赞成这个.. 以 fab 前缀为代价使用现有库对我来说似乎比任何本土和更复杂的东西更可取。
  • 也不知道为什么。当然面料是直接做的。
  • fabric 不是 OP 实际要求的。可以说,在这种情况下,它使整个事情变得更加麻烦。
  • @hop 虽然这个答案确实与问题不完全匹配,但在我看来,鉴于问题和完全缺乏背景,它仍然是一种有效的方法。我强烈建议其他读者使用像 fabric 这样的库,而不是上面更复杂的参数处理/bash 别名组合。
【解决方案2】:

您可以使用参数运行脚本,然后执行以下操作:

(f1 调用时不带参数,f2 调用时有 2 个参数。错误处理还不是傻瓜证明。)

import sys

def run():
    if len(sys.argv)<2:
        error()
        return

    if sys.argv[1] == 'f1':
        f1()

    elif sys.argv[1] == 'f2':
        if(len(sys.argv)!=4):
            error()
            return
        f2(sys.argv[2],sys.argv[3])

    else:
        error()

def f1():
    print("f1")

def f2(a,b):
    print("f2, arguments:",a,b)

def error():
    print("error")

run()

这不允许你调用类似的函数

commmand arg1 arg2

但你可以做到

python test.py f1
python test.py f2 arg1 arg2

编辑:

您可以创建别名:

alias f1="python test.py f1"
alias f1="python test.py f2"

实现您的要求:

$ f1
$ f2 arg1 arg2

【讨论】:

    【解决方案3】:

    您可以创建一个基本脚本,比如command.py,然后检查该脚本的名称(不要忘记使其可执行):

    #!/usr/bin/python
    import os.path
    import sys
    
    def command1(*args):
        print 'Command1'
        print args
    
    def command2(*args):
        print 'Command2'
        print args
    
    
    commands = {
        'command1': command1,
        'command2': command2
    }
    
    if __name__ == '__main__':
        command = os.path.basename(sys.argv[0])
        if command in commands:
            commands[command](*sys.argv[1:])
    

    然后你可以创建到这个脚本的软链接:

    ln -s command.py command1
    ln -s command.py command2
    

    最后测试一下:

    $ ./command1 hello
    Command1
    ('hello',)
    
    $ ./command2 world
    Command2
    ('world',)
    

    【讨论】:

    • 如果您不想要符号链接并希望像使用常规 bash 函数一样从 bash 脚本调用 Python 函数,请进行附加操作。使command.py 可执行,然后像这样使用它:./command.py command1 arg1 arg2。您只需要在最后的 if 块中上移数组索引,因为 command1 现在已成为第一个参数。我提到这一点是因为这是我的用例导致我得到这个答案(我赞成:-))。
    【解决方案4】:

    根据您的实际用例,最好的解决方案可能是简单地使用 Click(或至少标准库中的 argparse 模块)来构建您的 Python 脚本并将其称为

    command sub-command arg1 args2
    

    从外壳。

    请参阅 Mercurial 了解其中的一个突出示例。

    如果您确实必须将命令作为第一级 shell 命令,请使用其他答案中所述的符号链接或别名。

    只需几个方法,您就可以在sys.argv[0] 上进行符号链接和调度:

    $ cat cmd.py 
    #!/usr/bin/env python
    
    if __name__ == '__main__':
        import sys
        from os.path import basename
        cmd = basename(sys.argv[0])
        print("This script was called as " + cmd)
    
    $ ln -s cmd.py bar
    $ ln -s cmd.py foo
    $ ./foo 
    This script was called as foo
    $ ./bar
    This script was called as bar
    

    使用多个子命令,您可以在 Python 脚本中添加类似以下内容以“自动化”该过程:

    #!/usr/bin/env python
    import sys
    
    def do_repeat(a, b):
        print(a*int(b))
    
    def do_uppercase(args):
        print(''.join(args).upper())
    
    def do_eval():
        print("alias repeat='python cmd.py repeat';")
        print("alias uppercase='python cmd.py uppercase';")
    
    if __name__ == '__main__':
        cmd = sys.argv[1]
        if cmd=='--eval':
            do_eval()
    
        else:
            args = sys.argv[2:]
            if cmd=='repeat':
                do_repeat(*args)
            elif cmd=='uppercase':
                do_uppercase(args)
            else:
                print('Unknown command: ' + cmd)
    

    您可以这样使用它(假设在您的$PATH 某处有一个名为cmd.py 的可执行Python 脚本):

    $ cmd.py --eval          # just to show what the output looks like
    alias repeat='python cmd.py repeat';
    alias uppercase='python cmd.py uppercase';
    
    $ eval $(python cmd.py --eval)  # this would go in your .bashrc
    $ repeat asdf 3
    asdfasdfasdf
    $ uppercase qwer
    QWER
    

    注意:以上是一个非常基本示例,展示了如何在 shell 中使用eval

    【讨论】:

      【解决方案5】:

      autocommand 似乎需要考虑(cli 直接来自函数签名)

      示例:

      
      
      @autocommand(__name__)
      def cat(*files):
          for filename in files:
              with open(filename) as file:
                  for line in file:
                      print(line.rstrip())
      
      $ python cat.py -h
      usage: ipython [-h] [file [file ...]]
      
      positional arguments:
        file
      
      optional arguments:
        -h, --help  show this help message and exit
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-07
        • 2015-06-23
        • 2011-04-13
        • 2011-05-14
        • 2012-04-07
        • 1970-01-01
        • 2018-08-19
        相关资源
        最近更新 更多