19

I have a bash script provided by a 3rd party which defines a set of functions. Here's a template of what that looks like

$ cat test.sh

#!/bin/bash

define go() {
    echo "hello"
}

I can do the following from a bash shell to call go():

$ source test.sh
$ go
hello

Is there any way to access the same function from a python script? I tried the following, but it didn't work:

Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39) 
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> subprocess.call("source test.sh")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/subprocess.py", line 470, in call
    return Popen(*popenargs, **kwargs).wait()
  File "/usr/lib/python2.6/subprocess.py", line 623, in __init__
    errread, errwrite)
  File "/usr/lib/python2.6/subprocess.py", line 1141, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory
>>> 
1
  • 4
    What function is it in bash that you can't duplicate in Python? Commented Apr 29, 2011 at 0:18

3 Answers 3

44

Yes, indirectly. Given this foo.sh:

function go() { 
    echo "hi" 
}

Try this:

>>> subprocess.Popen(['bash', '-c', '. foo.sh; go'])

Output:

hi
Sign up to request clarification or add additional context in comments.

Comments

2

Based on @samplebias solution but with some modification that worked for me,

So I wrapped it into function that loads bash script file, executes bash function and returns output

def run_bash_function(library_path, function_name, params):
    params = shlex.split('"source %s; %s %s"' % (library_path, function_name, params))
    cmdline = ['bash', '-c'] + params
    p = subprocess.Popen(cmdline,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = p.communicate()
    if p.returncode != 0:
        raise RuntimeError("'%s' failed, error code: '%s', stdout: '%s', stderr: '%s'" % (
            ' '.join(cmdline), p.returncode, stdout.rstrip(), stderr.rstrip()))
    return stdout.strip()  # This is the stdout from the shell command

6 Comments

If you're writing a function to take arbitrary parameters, then you should be using shlex.quote.
@donkopotamus Correct, but I think shlex.split will do the work in that case, isn't it? i.e: shlex.split('"source %s; %s %s"' % (library_path, function_name, params))
There's no mention of shlex.split in your answer. In any case, shlex.split does not handle quoting inputs. The point is the following ... what will your function do if called with a function name of blah; rm -rf /? (I wouldn't recommend you test it ...)
@donkopotamus, Still didn't get your point though. If user decides to call rm -fr / instead of calling actual function it his own fault. BTW, "blah" is not a function name but path to libray from where the function loaded
Yes, that was a typo ... I'm aware blah is a filename. Filenames can be anything ... they should be quoted before passing to a shell in case they contain characters that mean something to the shell. Those characters could be as simple as a space, or as foolish as a semicolon.
|
1

No, the function is only available within that bash script.

What you could do is adapt the bash script by checking for an argument and execute functions if a specific argument is given.

For example

# $1 is the first argument

case $1 in
 "go" )
       go
       ;;
 "otherfunc" )
       otherfunc
       ;;
 * )
       echo "Unknown function"
       ;;
esac 

Then you can call the function like this:

subprocess.call("test.sh otherfunc")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.