Search code examples
pythonlinuxsubprocessdd

How do I push a subprocess.call() output to terminal and file?


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.


Solution

  • 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')