Sometimes it may be necessary to execute an external command that takes a long time to complete, long enough that the user needs visual feedback while it is running to show that the process is still alive.

UIOP provides fantastic tools for running external commands in a portable manner but it was not obvious to me how to show the external command’s output to the user while it was still busy. I also wanted to execute the external command in a synchronous fashion, i.e. my lisp application must wait for the external command to finish before continuing. The need for synchronicity sent me down the wrong path of using the synchronous uiop:run-program. It only returns when the external command has finished, which means you can only process the command output once it is completed.

I eventually realised I should use uiop:launch-program, the asynchronous version, and I came up with the following solution. In the example below the (ping) function pings a website and prints the results as they become available. Afterwards it returns the exit code of the ping command.

(defun ping ()
  (let (proc out exit-code)
    (unwind-protect
         (progn
           (setf proc (uiop:launch-program
                       (list "ping" "-c" "5" "www.darkchestnut.com")
                       :ignore-error-status t
                       :output :stream))
           (setf out (uiop:process-info-output proc))
           (iter
             (while (uiop:process-alive-p proc))
             (iter
               (while (listen out))
               (write-char (read-char out) *STANDARD-OUTPUT*))
             ;; ... Maybe do something here
             (sleep 0.5))
           (uiop:copy-stream-to-stream out *STANDARD-OUTPUT* 
                                       :linewise t))
      (setf exit-code (uiop:wait-process proc))
      (uiop:close-streams proc))
    exit-code))

In the first example the command’s output is shown to the user but it is not processed in any other way. If you need to do some extra processing on it after completion then the next example should provide a good starting point.

(defun ping-processing ()
  (let (proc out exit-code output)
    (with-open-stream (output-stream (make-string-output-stream))
      (with-open-stream (broadcast-stream 
                         (make-broadcast-stream *STANDARD-OUTPUT* 
                                                output-stream))
        (unwind-protect
             (progn
               (setf proc (uiop:launch-program
                           (list "ping" "-c" "5" 
                                 "www.darkchestnut.com")
                           :ignore-error-status t
                           :output :stream))
               (setf out (uiop:process-info-output proc))
               (iter
                 (while (uiop:process-alive-p proc))
                 (iter
                   (while (listen out))
                   (write-char (read-char out) broadcast-stream))
                 (sleep 0.5))
               (uiop:copy-stream-to-stream out broadcast-stream 
                                           :linewise t)))
          (setf exit-code (uiop:wait-process proc))
          (uiop:close-streams proc)
        (setf output (get-output-stream-string output-stream))
        ;; ... process output here
        exit-code))))