0

In Python 2.7 I have the following code inside certain loop

file = open("log.txt", 'a+')
last_position = file.tell()
subprocess.Popen(["os_command_producing_error"], stderr = file)
file.seek(last_position)
error = file.read()
print(error) # example of some action with the error

The intention is that the error that was just given by stderr gets, say printed, while file is keeping the whole record.

I am a beginner in Python and I am not clear what happens in the stderr = file.

My problem is that error keeps being empty, even though errors keep getting logged in the file.

Could someone explain why?

I have tried adding closing and opening the file again, or file.flush() right after the subprocess line. But still the same effect.

Edit: The code in the answer below makes sense to me and it seems to work for for the author of that post. For me (in Windows) it is not working. It gives an empty err and an empty file log.txt. If I run it line by line (e.g. debugging) it does work. How to understand and solve this problem?

Edit: I changed the Popen with call and now it works. I guess call waits for the subprocess to finish in order to continue with the script.

  • try add ``file.close()`` at the script end. – Casimir Crystal Sep 19 '15 at 05:19
  • And by the way, are you using **Python 2.4**? – Casimir Crystal Sep 19 '15 at 05:20
  • OK, try use `sys.stderr = file` instead `stderr = file` – Casimir Crystal Sep 19 '15 at 05:31
  • Maybe my comment isn't so clear, let me post an answer. – Casimir Crystal Sep 19 '15 at 05:31
  • Perhaps you can flush the file first, before moving the file pointer. But I'd go with close and reopen. –  Sep 19 '15 at 05:46
  • @Evert I find a problem: when raise the error, then the program will exit. so we can't flush or close the file. – Casimir Crystal Sep 19 '15 at 05:51
  • @Evert And I don't know why ``try`` doesn't work . – Casimir Crystal Sep 19 '15 at 05:52
  • Sorry, that doesn't make sense. What error do you want to raise? What `try` statement (I don't see any)? If there is more context to it than the above, it might be good to show it. –  Sep 19 '15 at 06:18
  • Given your initial code, without a try-except clause, why didn't subprocess raise an exception in the first place why you tried to run that code? Because I think it should, but you claim your code made it through (with an non-existing command) to the end. Which makes me think that command was either successfully executed, or no attempt to execute was made by the underlying shell. In both cases, the log file would not be updated, and `error` would indeed be empty. –  Sep 19 '15 at 06:29
  • @Evert My code is inside a `try:` and inside a loop too. I didn't include them here. The OS command for sure writes to stderr and also to stdout. – Anna Taurogenireva Sep 19 '15 at 06:33
  • @Evert However the ``try-except`` is working now. – Casimir Crystal Sep 19 '15 at 06:35

2 Answers2

1

error is empty because you are reading too soon before the process has a chance to write anything to the file. Popen() starts a new process; it does not wait for it to finish.

call() is equivalent to Popen().wait() that does wait for the child process to exit that is why you should see non-empty error in this case (if the subprocess does write anything to stderr).

#!/usr/bin/env python
import subprocess

with open("log.txt", 'a+') as file:
   subprocess.check_call(["os_command_producing_error"], stderr=file)
   error = file.read()
print(error)

You should be careful with mixing buffered (.read()) and unbuffered I/O (subprocess).

You don't need the external file here, to read the error:

#!/usr/bin/env python
import subprocess

error = subprocess.check_output(["os_command_producing_error"],
                                stderr=subprocess.STDOUT)
print(error)

It merges stderr and stdout and returns the output.

If you don't want to capture stdout then to get only stderr, you could use Popen.communicate():

#!/usr/bin/env python
import subprocess

p = subprocess.Popen(["os_command_producing_error"], stderr=subprocess.PIPE)
error = p.communicate()[1]
print(error)

You could both capture stderr and append it to a file:

#!/usr/bin/env python
import subprocess

error = bytearray()
p = subprocess.Popen(["os_command_producing_error"],
                     stderr=subprocess.PIPE, bufsize=1)
with p.stderr as pipe, open('log.txt', 'ab') as file:
    for line in iter(pipe.readline, b''):
        error += line
        file.write(line)
p.wait()
print(error)

See Python: read streaming input from subprocess.communicate().

Community
  • 1
  • 1
jfs
  • 346,887
  • 152
  • 868
  • 1,518
  • Thank you. There are some commands there that I think I will not be able to use, like `check_output` or `with`. I have Python 3.. and 2.7 in my laptop, but I think I will need to run the script in some places with 2.4. – Anna Taurogenireva Sep 20 '15 at 13:07
  • The third code snippet should work. I think I had tried that one at some point but got rid of it because I thought the command was supposed to communicate through `stdout` and I wasn't seeing the messages in `p.communicate()[0]`. If I knew I had only to change it to `p.communicate()[1]` ... – Anna Taurogenireva Sep 20 '15 at 13:23
  • @MlazhinkaShungGronzalezLeWy: your question says *Python 2.7*. The code in my answer works without any changes on Python 2.7 and 3. It can be adapted to work on Python 2.4 (that also has `subprocess` module) e.g., here's [how to emulate `check_output()` on earlier Python versions](http://stackoverflow.com/a/2924457/4279). You should mention explicitly in your question what python versions you need to support. – jfs Sep 20 '15 at 13:30
0

Try these following codes:

file = open("log.txt", 'a+')
sys.stderr = file
last_position = file.tell()
try:
    subprocess.call(["os_command_producing_error"])
except:
    file.close()
    err_file = open("log.txt", 'r')
    err_file.seek(last_position)
    err = err_file.read()
    print err
    err_file.close()

sys.stderr map the standard error message like sys.stdout(map standard output) and sys.stdin(map standard input).

And this will map the standard error to file. So all of the standard error will be write to the file log.txt.

Casimir Crystal
  • 18,651
  • 14
  • 55
  • 76