88

I want to execute a python script from a bash script, and I want to store the output of the python script in a variable.

In my python script, I print some stuff to screen and at the end I return a string with:

sys.exit(myString) 

In my bash script, I did the following:

outputString=`python myPythonScript arg1 arg2 arg3 `

But then when I check the value of outputString with echo $outputString I get everything that the Python script had printed to screen, but not the return value myString!

How should I do this?

EDIT: I need the string because that tells me where a file created by the Python script is located. I want to do something like:

fileLocation=`python myPythonScript1 arg1 arg2 arg1`
python myPythonScript2 $fileLocation
Ricky Robinson
  • 21,798
  • 42
  • 129
  • 185
  • did you try just printing from your python command and setting that to a variable without doing any of the system exiting? e.g. `local_dir=$(python execute_tensorboard.py $1)`? – Charlie Parker Mar 14 '21 at 15:15

6 Answers6

70

sys.exit(myString) doesn't mean "return this string". If you pass a string to sys.exit, sys.exit will consider that string to be an error message, and it will write that string to stderr. The closest concept to a return value for an entire program is its exit status, which must be an integer.

If you want to capture output written to stderr, you can do something like

python yourscript 2> return_file

You could do something like that in your bash script

output=$((your command here) 2> &1)

This is not guaranteed to capture only the value passed to sys.exit, though. Anything else written to stderr will also be captured, which might include logging output or stack traces.

example:

test.py

print "something"
exit('ohoh') 

t.sh

va=$(python test.py 2>&1)                                                                                                                    
mkdir $va

bash t.sh

edit

Not sure why but in that case, I would write a main script and two other scripts... Mixing python and bash is pointless unless you really need to.

import script1
import script2

if __name__ == '__main__':
    filename = script1.run(sys.args)
    script2.run(filename)
user2357112
  • 260,549
  • 28
  • 431
  • 505
Loïc Faure-Lacroix
  • 13,220
  • 6
  • 67
  • 99
  • This all works. But I'd advise to use integer exit codes to allow the script to play nice in a Un*x environment. –  Aug 10 '12 at 11:46
  • Uhm. Why are you doing `2>`? what's that `2` for? All I need to do is to retrieve the location of a file that my python script has created and written to and I can't turn off all prints. – Ricky Robinson Aug 10 '12 at 11:48
  • 6
    `>` is a redirection operator. `2>` redirect error messages, `>` alone is stdout `<` is stdin – Loïc Faure-Lacroix Aug 10 '12 at 11:50
  • 1
    @RickyRobinson Perhaps you are looking in the wrong place. Please clarify your goal in your question so we can advise the best way to achive it. –  Aug 10 '12 at 11:51
  • 1
    The thing is that since you're writing into `stderr` with `sys.exit`. It's not certain that nothing else will be written there. – Loïc Faure-Lacroix Aug 10 '12 at 11:53
  • OK, thanks. So what exactly should I do to have the behaviour described in my last edit? – Ricky Robinson Aug 10 '12 at 11:57
  • why don't you call the script from python itself? Isn't it a part of your script... I would even write a python script that use both scripts. I don't really see the point to use BASH if your going back and forth to python. – Loïc Faure-Lacroix Aug 10 '12 at 12:00
  • Oh well, it's just because I'm not too familiar with calling python scripts from within a python script. I don't have the time right now to remodule everything so that I can import the second module and call functions from it. I just need to run the first script, and then the second one on the file just created by the first script. The problem is that I have more arguments to the second script, not just the file position. How does it work with `run`? – Ricky Robinson Aug 10 '12 at 12:15
  • In your last edit, how should I return a value from the first script, if `sys.exit(someValue)` will just write to stderr any non integer value? – Ricky Robinson Aug 10 '12 at 12:20
  • I tried your first solution, but the syntax didn't get accepted: `$ output=$((python myScript.py -f someFile -d 1 --counter 30) 2> &1) -bash: command substitution: line 1: syntax error near unexpected token `&' -bash: command substitution: line 1: `(python myScript.py -f someFile -d 1 --counter 3) 2> &1'` – Ricky Robinson Aug 10 '12 at 12:33
  • extra parenthesis aren't needed as far as I can tell. I added an example – Loïc Faure-Lacroix Aug 10 '12 at 13:31
  • `run` is just a function... in other word it's not really calling an other script. It's using functions or separating things into modules. Its a python script... – Loïc Faure-Lacroix Aug 10 '12 at 13:33
  • leave the space between `2>`and `&1` => `output=$((your command here) 2>&1)` – BrrBr Jul 28 '17 at 21:57
26

sys.exit() should return an integer, not a string:

sys.exit(1)

The value 1 is in $?.

$ cat e.py
import sys
sys.exit(1)
$ python e.py
$ echo $?
1

Edit:

If you want to write to stderr, use sys.stderr.

  • 3
    `$?` can be used by a shell script to check if the previous command worked OK or if it had an error. Exit codes are a basic way for programs in the Un*x environment to communicate. –  Aug 10 '12 at 11:47
  • $? is the return code. It's used to know if the script completed correctly or not. Its how it works, you could have return 0 == 'fine', 1=> error, 2=>Some specific error etc... – Loïc Faure-Lacroix Aug 10 '12 at 11:48
  • Tichodroma, actually sys.exit allow string args they will be outputted to stderr. – Loïc Faure-Lacroix Aug 10 '12 at 11:49
  • @LoïcFaure-Lacroix True, but I'd advice against using this. –  Aug 10 '12 at 11:49
11

Do not use sys.exit like this. When called with a string argument, the exit code of your process will be 1, signaling an error condition. The string is printed to standard error to indicate what the error might be. sys.exit is not to be used to provide a "return value" for your script.

Instead, you should simply print the "return value" to standard output using a print statement, then call sys.exit(0), and capture the output in the shell.

chepner
  • 497,756
  • 71
  • 530
  • 681
8

In addition to what Tichodroma said, you might end up using this syntax:

outputString=$(python myPythonScript arg1 arg2 arg3)
Alp
  • 29,274
  • 27
  • 120
  • 198
8

read it in the docs. If you return anything but an int or None it will be printed to stderr.

To get just stderr while discarding stdout do:

output=$(python foo.py 2>&1 >/dev/null)
tzelleke
  • 15,023
  • 5
  • 33
  • 49
  • This works, but it solves the wrong problem. By passing a string to `sys.exit`, you are implicitly saying your script has failed by using an exit code of 1. – chepner Aug 10 '12 at 13:03
4

Python documentation for sys.exit([arg])says:

The optional argument arg can be an integer giving the exit status (defaulting to zero), or another type of object. If it is an integer, zero is considered “successful termination” and any nonzero value is considered “abnormal termination” by shells and the like. Most systems require it to be in the range 0-127, and produce undefined results otherwise.

Moreover to retrieve the return value of the last executed program you could use the $? bash predefined variable.

Anyway if you put a string as arg in sys.exit() it should be printed at the end of your program output in a separate line, so that you can retrieve it just with a little bit of parsing. As an example consider this:

outputString=`python myPythonScript arg1 arg2 arg3 | tail -0`
Giova
  • 1,879
  • 1
  • 19
  • 27
  • @Ricky Robinson consider just using tail -0. It should work as you stated in your edited question. Then don't forget to rate the answer. – Giova Aug 10 '12 at 11:57
  • @Alp Your answer list yet another command substitution syntax.. That is just an alias for the syntax Ricky Robinson has used in it question. Please pay attention! – Giova Aug 10 '12 at 12:04
  • 1
    The Python documentation also say that if a string is given as parameter to sys.exit() it is treated like an error message. That means (in linux/unix systems) it is sent to stderr. If your bash (like most) had stderr bound with terminal you should see your string at the end of your script output and you could simply retrieve it with tail. To obtain the same result more elegantly you can use sys.stderr("your message") and then sys.exit(0). – Giova Aug 10 '12 at 12:20
  • Thanks. Maybe I should clear `sys.stderr` before including my value, what do you think? Any way I can do that? – Ricky Robinson Aug 10 '12 at 12:22
  • @Ricky Robinson You shouldn't need to clean sys.stderr before, because with tail you are looking for the last stdout/stderr line. – Giova Aug 10 '12 at 12:28
  • `sys.exit` writes to standard error, so piping standard output through `tail` won't help. – chepner Aug 10 '12 at 12:57
  • @chepner If you use sys.exit with strings, but not if you use it as intended in the Python sys.exit() documentation (with integer number from 0 to 127). Please read the doc! – Giova Aug 11 '12 at 08:13
  • 2
    Thanks to @Giova for idea. In my case it work with `tail -1` (I can get last line) – Volkov Maxim May 07 '21 at 12:13