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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.