2

My situation is similar to Redirect Stdin and Stdout to File. I want to run a program and capture the entire session (stdin and stdout) of running a program as though it had been run interactively from a terminal.

From Dump terminal session to file I found out that the script command would work. Here is a simplified version of my program (written in Groovy) that does this.

import groovy.io.GroovyPrintWriter

def OUTPUT_FILE = "output.txt"

def sysout = new StringBuffer()
def syserr = new StringBuffer()

// On Linux (Ubuntu 12.04) - starts the bc command with script and capture session to OUTPUT_FILE
Process proc = "script -q -f -c bc ${OUTPUT_FILE}".execute()
// On OS X with a slightly different version of Script
// Process proc = "script -q ${OUTPUT_FILE} bc".execute()

proc.consumeProcessOutput(sysout, syserr) // Spawns separate threads that will consume the out to prevent overflow

proc.withWriter { writer ->
  // Writes the following commands to the spawned process
  def gWriter = new GroovyPrintWriter(writer)
  // Imagine that the user enters these lines
  gWriter.println "a = 5"
  gWriter.println "b = 4"
  gWriter.println "c = a * b"
  gWriter.println "c"
  gWriter.println "quit"
}

proc.waitForOrKill(20000) // Give it 20 seconds to complete or kill the process

println 'Standard out captured from process:\n' + sysout
println 'Standard err captured from process:\n' + syserr

The program I wrote works on the version of script available on OS X. On OS X, the entire session is captured in the sysout variable and also written to the OUTPUT_FILE correctly.

Here's what I expect to get

bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. a = 5
b = 4
c = a * b
c
20
quit

However, here is what I get on Linux (Ubuntu 12.04), it omits everything that was typed into STDIN and only captures STDOUT. Here is the content of sysout and OUTPUT_FILE

bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
20

As you can see, the lines a = 4, b = 5, etc are missing.

When I run the script command manually on Linux from the command line prompt (script -q -f -c bc output.txt} and enter the lines one by one, it works, i.e., all the lines are also written to OUTPUT_FILE.

What additional steps do I need to do to get my program to work on Linux? I know that the program works on OS X so it is unlikely to be a problem with the program (or Groovy). I suspect that it must be something with how the Linux version of script behaves when launched as an external process.

Any pointers would be appreciated.

Community
  • 1
  • 1
vazexqi
  • 211
  • 2
  • 4
  • Can you come up with an example `script` that shows this happening? – tim_yates Jan 28 '13 at 07:35
  • @tim_yates: Yes the code that I included above (runnable through Groovy) shows this problem on Linux (Ubuntu). It calls `bc` as an external command and communicates with it. – vazexqi Jan 28 '13 at 17:30

1 Answers1

0

I'm not sure why this happens on Ubuntu and not on OS X, but one possibile work-around on Ubuntu may be to decorate the writer with your own class which appends the written characters to the StringBuffer.

It not very pretty, since you will have to add OS specific code to make sure that the output is not included twice on OS X.

Anyway, here is the script in full with the decorator class, which works fine for me on Ubuntu 12.04 using Groovy 2.0.5.

import groovy.io.GroovyPrintWriter

def OUTPUT_FILE = "output.txt"

def sysout = new StringBuffer()
def syserr = new StringBuffer()

// On Linux (Ubuntu 12.04) - starts the bc command with script and capture session to OUTPUT_FILE
Process proc = "script -q -f -c bc ${OUTPUT_FILE}".execute()
// On OS X with a slightly different version of Script
// Process proc = "script -q ${OUTPUT_FILE} bc".execute()

proc.consumeProcessOutput(sysout, syserr) // Spawns separate threads that will consume the out to prevent overflow

proc.withWriter { writer ->
    // Writes the following commands to the spawned process
    def gWriter = new GroovyPrintWriter(new MyCaptureWriter(writer, sysout))
    // Imagine that the user enters these lines
    gWriter.println "a = 5"
    gWriter.println "b = 4"
    gWriter.println "c = a * b"
    gWriter.println "c"
    gWriter.println "quit"
}

proc.waitForOrKill(20000) // Give it 20 seconds to complete or kill the process

println 'Standard out captured from process:\n' + sysout
println 'Standard err captured from process:\n' + syserr

class MyCaptureWriter extends Writer {

    @Delegate OutputStreamWriter writer
    def sysout

    MyCaptureWriter(writer, sysout) {
        this.writer = writer
        this.sysout = sysout
    }

    @Override
    void write(char[] cbuf, int off, int len) throws IOException {
        sysout.append(cbuf, off, len)
        writer.write(cbuf, off, len)
    }
}
Steinar
  • 5,860
  • 1
  • 25
  • 23