Capturing Terminal Output From An External Program With Node.js

February 4, 2018

Previously I wrote about capturing terminal output from within a Node.js program.

In this post, our capture program will spawn a separate program, and then capture and act on the output.

It’s the same story as before, in which we re-enact a scene from 2001: A Space Odyssey.

Astronaut Dave is trying to persuade Hal to open the pod bay doors. If we capture the output when HAL says “mission”, Dave gets back into the spacecraft.

The code that does the capturing:

// capture-external-process.js
//
// ======================================
// Capture and Trigger Function on Output
// stdout produced by external process

// We accept:
//
// external_process
//   - the external process with output to monitor
//
// success_condition
//   - check this on output and if true invoke success_fn
//
// success_fn
//   - invoked when success_condition is true

function capture(external_process, success_condition, success_fn) {
  external_process().stdout.on('data', process_output)

  function process_output(output) {
    process.stdout.write(output)

    if (success_condition(output))
      success_fn()
  }

}

Test case:

// capture-external-process.js (continued)
//
// =========
// Test case

function test_case() {

  // success_fn -- this should execute when HAL says 'mission'
  function dave_gets_back_in() {
    console.log(`
      HAL says 'mission' and the pod bay doors open!
      Dave climbs back into the spacecraft and disables HAL.
    `)
    process.exit()
  }

  // success_condition
  const HAL_says_mission = output => output.includes('mission')

  // external_process
  const dave_and_HAL_speak =
    () => require('child_process').spawn('node', ['dave_and_hal.js'])

  // start external process and check for the word 'mission'
  capture(dave_and_HAL_speak, HAL_says_mission, dave_gets_back_in)

}

test_case()

The spawned program that outputs the lines:

// dave-and-HAL.js
//
// =========
// Separate JS program that outputs the lines

lines = [
  "DAVE: Open the pod bay doors, HAL.",
  "HAL:  I’m sorry, Dave. I’m afraid I can’t do that.",
  "DAVE: What’s the problem?",
  "HAL:  I think you know what the problem is just as well as I do.",
  "DAVE: What are you talking about, HAL?",
  "HAL:  This mission is too important ...",
  "HAL:  ... for me to allow you to jeopardize it.",
  "DAVE: I don’t know what you're talking about, HAL.",
  "HAL:  I know that you and Frank were planning to disconnect me,",
  "HAL:  ... and I’m afraid that's something I can’t allow to happen.",
  "DAVE: Where the hell did you get that idea, HAL?",
  "HAL:  Although you took very thorough precautions in the pod",
  "HAL:  ... against my hearing you, I could see your lips move.",
  "DAVE: All right, HAL. I’ll go in through the emergency air lock.",
  "HAL:  Without your space helmet, Dave, ...",
  "HAL:  ... you’re going to find that rather difficult.",
  "DAVE: HAL, I won’t argue with you anymore. Open the doors!",
  "HAL:  Dave...This conversation can serve no purpose anymore.",
  "HAL:  Goodbye."
]

// Display the next line in `lines` and
// remove it from the array
function display_line() {
  console.log(lines.shift())

  // If we're properly capturing output, the program
  // will end before this if statement is true
  if (lines.length == 0)
    dave_never_gets_back_in()
}

// This function should never execute
function dave_never_gets_back_in() {
  console.log(`
    This line should never be reached.
    Dave is trapped out in space forever!
  `)
  process.exit()
}

// Output each line each line, one a second
setInterval(display_line, 1000)

Enjoy!