Nemo
Nemo

Reputation: 49

How to run different python functions from command line

I'm trying to run different functions from a python script(some with arguments and some without)

So far I have

def math(x):
   ans = 2*x
   print(ans)

def function1():
  print("hello")

if __name__ == '__main__':   
     globals()[sys.argv[1]]()

and in the command line if I type python scriptName.py math(2)

I get the error

File "scriptName.py", line 28, in <module>

globals()[sys.argv[1]]()

KeyError: 'mat(2)'

New to python and programming so any help would be apprecitated. This is also a general example...my real script will have a lot more functions.

Thank you

Upvotes: 3

Views: 3289

Answers (2)

James Mchugh
James Mchugh

Reputation: 1014

Here is another approach you can take:

#!/usr/bin/env python3
"""Safely run Python functions from command line.

"""
import argparse
import ast
import operator


def main():
    # parse arguments
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("function", help="Python function to run.")
    parser.add_argument("args", nargs='*')
    opt = parser.parse_args()

    # try to get the function from the operator module
    try:
        func = getattr(operator, opt.function)
    except AttributeError:
        raise AttributeError(f"The function {opt.function} is not defined.")

    # try to safely eval the arguments
    try:
        args = [ast.literal_eval(arg) for arg in opt.args]
    except SyntaxError:
        raise SyntaxError(f"The arguments to {opt.function}"
                          f"were not properly formatted.")

    # run the function and pass in the args, print the output to stdout
    print(func(*args))


if __name__ == "__main__":
    main()

Then you can execute this by doing the following:

./main.py pow 2 2
4

We use the argparse module from Python's Standard Library to facilitate the parsing of arguments here. The usage for the script is below:

usage: main.py [-h] function [args [args ...]]

function is the name of the function you want to run. The way this is currently structured is to pull functions from the operator module, but this is just an example. You can easily create your own file containing functions and use that instead, or just pull them from globals().

Following function you can supply any number of arguments that you want. Those arguments will be ran through ast.literal_eval to safely parse the arguments and get the corresponding types.

The cool thing about this is your arguments are not strictly limited to strings and numbers. You can pass in any literal. Here is an example with a tuple:

./main.py getitem '(1, 2, 3)' 1
2

These arguments are then passed to the selected function, and the output is printed to stdout. Overall, this gives you a pretty flexibly framework in which you can easily expand the functionality. Plus, it avoids having to use eval which greatly reduces the risk of doing something of this nature.

Why not to use eval:

Here is a small example of why just using eval is so unsafe. If you were to simply use the following code to solve your issue:

def math(x):
   ans = 2*x
   print(ans)

def function1():
  print("hello")

if __name__ == '__main__':   
     print(eval(sys.argv[1]])) # DO NOT DO IT THIS WAY

Someone could pass in an argument like so:

python main.py 'import shutil; shutil.rmtree("/directory_you_really_dont_want_to_delete/")'

Which would in effect, import the shutil module and then call the rmtree function to remove a directory you really do not want to delete. Obviously this is a trivial example, but I am sure you can see the potential here to do something really malicious. An even more malicious, yet easily accessible, example would be to import subprocess and use recursive calls to the script to fork-bomb the host, but I am not going to share that code here for obvious reasons. There is nothing stopping that user from downloading a malicious third party module and executing code from it here (a topical example would be jeilyfish which has since been removed from PyPi). eval does not ensure that the code is "safe" before running it, it just arbitrarily runs any syntactically correct Python code given to it.

Upvotes: 2

Ari
Ari

Reputation: 6169

Try this!

import argparse

def math(x):
    try:
        print(int(x) * 2)
    except ValueError:
        print(x, "is not a number!")


def function1(name):
    print("Hello!", name)


if __name__ == '__main__':
    # if you type --help
    parser = argparse.ArgumentParser(description='Run some functions')

    # Add a command
    parser.add_argument('--math', help='multiply the integer by 2')
    parser.add_argument('--hello', help='say hello')

    # Get our arguments from the user
    args = parser.parse_args()


    if args.math:
        math(args.math)

    if args.hello:
        function1(args.hello)

You run it from your terminal like so:

python script.py --math 5 --hello ari

And you will get

>> 10
>> Hello! ari

You can use --help to describe your script and its options

python script.py --help

Will print out

Run some functions

optional arguments:
  -h, --help     show this help message and exit
  --math MATH    multiply the integer by 2
  --hello HELLO  say hello

Read More: https://docs.python.org/3/library/argparse.html

Upvotes: 3

Related Questions