Reputation: 22510
There is a lot of literature on how to run shell commands from python, but I am interested in doing the opposite. I have a python module mycommands.py which contains functions like below
def command(arg1, arg2):
pass
def command1(arg1, arg2, arg3):
pass
where the function arguments are all strings. The objective is to be able to run these functions from bash like below
$ command arg1 arg2
$ command1 arg1 arg2 arg3
So far I have the following brute setup in .bash_profile where I have to provide bash bindings to each python function manually
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)"
}
It would be nice if one could have a single bash command like
$ import_python mycommands.py
which would automatically import all the python functions in the module as bash commands. Does there exist a library which implements such a command?
Upvotes: 14
Views: 11719
Reputation: 551
autocommand seems to be relevant to consider (cli directly from function signatures)
example:
@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
Upvotes: 1
Reputation: 20777
Fabric can do just this:
from fabric.api import run
def host_type():
run('uname -s')
fab -H localhost,linuxbox host_type
[localhost] run: uname -s
[localhost] out: Darwin
[linuxbox] run: uname -s
[linuxbox] out: Linux
Done.
Disconnecting from localhost... done.
Disconnecting from linuxbox... done.
Or more specific to the question:
$ cat fabfile.py
def command1(arg1, arg2):
print arg1, arg2
$ fab command1:first,second
first second
Done.
Upvotes: 1
Reputation:
Depending on your actual use case, the best solution could be to simply use Click (or at least the argparse
module from the Standard Library) to build your Python script and call it as
command sub-command arg1 args2
from the shell.
See Mercurial for one prominent example of this.
If you really must have your commands as first-level shell commands, use symlinks or aliases as described in other answers.
With a only a few methods, you can symlink and dispatch on 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
With more than a couple of sub-commands, you could add something like the following to your Python script to "automate" the process:
#!/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)
You would use it like this (assuming an executable Python script called cmd.py
somewhere in your $PATH
):
$ 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
NB: The above is a very rudimentary example to show how to use eval
in the shell.
Upvotes: 5
Reputation: 1568
You could run the script with arguments, and then do something like this:
(f1 is called without arguments, f2 with 2 arguments. Error handling is not fool prove yet.)
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()
This doesn't let you call the functions like
commmand arg1 arg2
but you can do
python test.py f1
python test.py f2 arg1 arg2
You can create aliases:
alias f1="python test.py f1"
alias f1="python test.py f2"
to achieve what you requested:
$ f1
$ f2 arg1 arg2
Upvotes: 4
Reputation: 20025
You can create a base script, let's say command.py
and check with what name this script was called (don't forget to make it executable):
#!/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:])
Then you can create soft links to this script:
ln -s command.py command1
ln -s command.py command2
and finally test it:
$ ./command1 hello
Command1
('hello',)
$ ./command2 world
Command2
('world',)
Upvotes: 8