I have subprocess.call(["ddrescue", in_file_path, out_file_path], stdout=drclog)
. I'd like this to display the ddrescue in the terminal as it's running and write the output to the file drclog. I've tried using subprocess.call(["ddrescue", in_file_path, out_file_path], stdout=drclog, shell=True)
, but that gives me an input error into ddrescue.
Asked
Active
Viewed 1,699 times
1

bmikolaj
- 485
- 1
- 5
- 16
-
What do you mean by displaying ddrescue in terminal? – kraskevich Sep 21 '14 at 19:38
-
ddrescue shows the process of the copy in this format; `GNU ddrescue 1.18.1 Press Ctrl-C to interrupt rescued: 0 B, errsize: 0 B, current rate: 0 B/s ipos: 0 B, errors: 0, average rate: 0 B/s opos: 0 B, run time: 1 s, successful read: 1 s ago Finished` I'd like to be able to see this and have the final status written to a file (stdout). – bmikolaj Sep 21 '14 at 19:40
-
If you want to write something to terminal and file at the same time you can use tee command. I'm not sure if it is possible to write part of the output to terminal and part of it to a file if the data is written into one output stream. – kraskevich Sep 21 '14 at 19:55
-
I'm familiar with tee, but how would I go about doing this in python? in_file_path and out_file_path are necessary inputs into ddrescue. – bmikolaj Sep 21 '14 at 20:02
-
1`args` should be a string when using `shell=True`, else the arguments get passed as parameters to the shell itself. For example, `call(['echo $0 and $1', 'eggs', 'spam'], shell=True)` prints "eggs and spam". – Eryk Sun Sep 22 '14 at 00:17
-
[to redirect stdout/stderr separately, you need threads or async. io](http://stackoverflow.com/q/17190221/4279) – jfs Sep 22 '14 at 07:21
-
@eryksun: please, do not recommend `shell=True` if the same command can be written without it without loosing readability. If you do recommend it then make sure that you use `pipes.quote()` to quote the argument to avoid common pitfalls such as interpreting a filename with spaces as multiple arguments e.g., look [OP's answer](http://stackoverflow.com/a/25974983/4279) that is vulnerable to it. – jfs Sep 24 '14 at 07:50
-
@J.F.Sebastian, I was just explaining why passing a list wouldn't work. I clearly stated upfront that `args` should be a string. Somehow, the OP misunderstood me. I thought it was obvious that it's silly to use "$" variables in a command line. Sorry, OP. – Eryk Sun Sep 24 '14 at 18:14
2 Answers
2
If ddrescue
doesn't change its output if its stdout/stderr are redirected to a pipe then you could use tee
utility, to display output on the terminal and to save it to a file:
$ ddrescue input_path output_path ddrescue_logfile |& tee logfile
If it does then you could try to provide a pseudo-tty using script
utility:
$ script -c 'ddrescue input_path output_path ddrescue_logfile' -q logfile
If it writes directly to a terminal then you could use screen
to capture the output:
$ screen -L -- ddrescue input_path output_path ddrescue_logfile
The output is saved in screenlog.0
file by default.
To emulate the tee
-based command in Python without calling tee
utility:
#!/usr/bin/env python3
import shlex
import sys
from subprocess import Popen, PIPE, STDOUT
command = 'ddrescue input_path output_path ddrescue_logfile'
with Popen(shlex.split(command), stdout=PIPE, stderr=STDOUT, bufsize=1) as p:
with open('logfile', 'wb') as logfile:
for line in p.stdout:
logfile.write(line)
sys.stdout.buffer.write(line)
sys.stdout.buffer.flush()
To call the tee
-based command in Python using shell=True
:
#!/usr/bin/env python
from pipes import quote
from subprocess import call
files = input_path, output_path, ddrescue_logfile
rc = call('ddrescue {} | tee -a drclog'.format(' '.join(map(quote, files))),
shell=True)
To emulate the script
-based command:
#!/usr/bin/env python3
import os
import shlex
import pty
logfile = open('logfile', 'wb')
def read(fd):
data = os.read(fd, 1024) # doesn't block, it may return less
logfile.write(data) # it can block but usually not for long
return data
command = 'ddrescue input_path output_path ddrescue_logfile'
status = pty.spawn(shlex.split(command), read)
logfile.close()
To call screen
command in Python:
#!/usr/bin/env python3
import os
import shlex
from subprocess import check_call
screen_cmd = 'screen -L -- ddrescue input_path output_path ddrescue_logfile'
check_call(shlex.split(screen_cmd))
os.replace('screenlog.0', 'logfile')
-
How does python handle `{}`? In `call('ddrescue {} | tee -a drclog'...` for example. – bmikolaj Sep 24 '14 at 12:48
-
There are too many different approaches in this answer. I feel that's unconstructive to the community. The answer that worked is just the `subprocess.call('ddrescue {} | tee -a drclog'.format(' '.join(map(quote, files))), shell=True)` part. – bmikolaj Sep 24 '14 at 13:10
-
@p014k: the question: *"how to push subprocess' output to terminal and file at the same time"* has different answers depending on subprocess: I've shown three main types. `tee`-based approach works for `ddrescue` but may fail for subprocesses that produce different output if stdout/stderr is not terminal e.g., `apt-get` if you don't use `-q=0` option. I've provided several approaches so that the answer would be helpful to the community at large and not only in your particular case. – jfs Sep 24 '14 at 16:20
-
I had thought that the tee method had been working because I saw the output in the terminal and figured that the `| tee -a drclog` was adequately recording the contents to drclog, but upon further inspection, it only pushes the results to terminal and not to drclog. I have drclog `open()` as drclog. – bmikolaj Sep 30 '14 at 15:18
-
@p014k: `ddrescue` does write to stdout (and therefore `tee` writes its output to the file) on my system (`GNU ddrescue 1.17`). – jfs Sep 30 '14 at 15:35
-
I have `GNU ddrescue 1.18.1`. Maybe I'm doing something wrong in my code. There are a couple differences in my code which shouldn't matter, but I'll point them out. 1) I have `import subprocess` instead of `from subprocess import call` 2) I have `subprocess.call()` instead of `rc = ...` 3) I have drclog already open and previously written to. (I tried changing it to `| tee drclog2` just to see and it doesn't write to a file called drclog2). 4) I don't have a ddrescue_logfile. Should any of these impact functionability? – bmikolaj Sep 30 '14 at 18:19
-
I found an error. `drclog` is open via `with open(os.path.join(current_out_dir, 'drclog'), 'w+') as drclog:` where `current_out_dir` is defined earlier. The `| tee -a drclog` literally pushes stdout to the directory the python script was executed from. I've used `subprocess.call("ddrescue {} | tee -a {}".format(' '.join(map(quote, files)), os.path.join(current_out_dir, 'drclog')), shell=True)` and it seems to have worked, but I've already opened drclog, shouldn't I be able to do `subprocess.call("ddrescue {} | tee -a {}".format(' '.join(map(quote, files)), drclog), shell=True)`? – bmikolaj Sep 30 '14 at 18:29
-
1. Point `3)` should affect it. `open(filename, 'w')` truncates the file. If you write to the same file from your python script and the subprocess then you should flush the file buffer before calling the subprocess. 2. `'drclog'` inside a shell command is a filename (a string) and `drclog` in your Python script is a file object (not a string). If you want to use the file object then adapt the first Python code example (with `Popen()`) that doesn't use `tee` utility or use `quote(drclog.name)`. – jfs Sep 30 '14 at 19:33
0
The solution that worked for me was subprocess.call(["ddrescue $0 $1 | tee -a drclog", in_file_path, out_file_path], shell=True)
.

bmikolaj
- 485
- 1
- 5
- 16
-
1it is incorrect. the 2nd and 3rd arguments are `bufsize` and `executable`, not files. – jfs Sep 23 '14 at 04:12
-
Fixed a typo. A ']' was in the wrong place. Had it correct in my code, but not in my post. Please remove -1. – bmikolaj Sep 23 '14 at 18:04
-
it is better. Now it fails if there are shell meta characters such as space in the filenames. – jfs Sep 24 '14 at 04:21
-
This is just a snippet, but in_file_path and out_file_path are treated intelligently via `os. path` – bmikolaj Sep 24 '14 at 12:05
-
`os.path` won't help you with spaces. I've added how to call the `tee`-based command in Python using `shell=True` to [my answer](http://stackoverflow.com/a/25968448/4279) – jfs Sep 24 '14 at 12:34
-
1-1: it doesn't handle files with spaces. **Try it** if you don't believe my word (you shouldn't) – jfs Sep 24 '14 at 12:42
-
Anything wrong with `'ddrescue "$0" "$1" | tee -a drclog'`? Works with spaces with my testing. – Matt Joiner Sep 24 '14 at 12:54