example-01-using-popen.scm 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. ;;; ABOUT
  2. ;; The examples in this document were posted on the Guile
  3. ;; user mailing list and are not originally written by me
  4. ;; zelphirkaltstahl@posteo.de.
  5. ;; Comments, some formatting and editing by me
  6. ;; (zelphirkaltstahl@posteo.de).
  7. ;;; PIPES
  8. ;; For process communication pipes are useful. A pipe is a
  9. ;; pair of 2 ports. An input port and an output port. With
  10. ;; such ports it is possible for a process to output
  11. ;; messages to the output port, which in turn enables the
  12. ;; parent process (or anyone else with a handle on the input
  13. ;; port) to read those messages from the input port of the
  14. ;; pair of ports. The ports are coupled together as such,
  15. ;; making a pipe.
  16. ;; The official docs are at:
  17. ;; https://www.gnu.org/software/guile/manual/html_node/Ports-and-File-Descriptors.html#index-pipe-2
  18. ;; Some of the used procedures are for dealing with
  19. ;; ports. Official documentation about ports is at:
  20. ;; https://www.gnu.org/software/guile/manual/html_node/Ports-and-File-Descriptors.html
  21. ;; There are also procedures for dealing with textual IO and ports,
  22. ;; documented at:
  23. ;; https://www.gnu.org/software/guile/manual/html_node/Textual-I_002fO.html
  24. ;; The following code is adapted from a post on the Guile
  25. ;; user mailing list, posted by post@thomasdanckaert.be and
  26. ;; is part of
  27. ;; https://github.com/tdanckaert/jobview/blob/master/jobtools.scm#L38.
  28. (import (ice-9 popen)
  29. (ice-9 textual-ports)
  30. (ice-9 exceptions)
  31. (ice-9 receive)
  32. (ice-9 match))
  33. (define process-output
  34. (λ (proc cmd)
  35. "Runs CMD as an external process, with an input port from which
  36. the process' stdout may be read, and runs the procedure PROC. PROC
  37. takes as input the input port, from which is can read the output of
  38. the command, as a single argument and processes it in any way.
  39. Throws an exception 'cmd-failed if CMD's exit-code is non-zero."
  40. ;; Bind some ports for error messaging and handling.
  41. (match-let (;; Create a pipe, which consists of 2 ports,
  42. ;; an input and an output port. These are
  43. ;; meant for error messages. Extract input
  44. ;; port and output port via pattern matching
  45. ;; from the pair which (pipe)
  46. ;; returns. err-read is an input port
  47. ;; err-write is an output port.
  48. [(err-read . err-write) (pipe)]
  49. ;; Alias the current error port to use
  50. ;; later.
  51. [stderr (current-error-port)])
  52. ;; Create a context or scope, in which the error port
  53. ;; is set to the output port of the pipe. This will
  54. ;; enable to read the errors from the input port, once
  55. ;; they have been written to the output port.
  56. (with-error-to-port err-write
  57. (λ ()
  58. (let* (;; open-input-pipe is the same as open-pipe
  59. ;; with mode OPEN_READ. open-pipe runs a
  60. ;; command in a child process. More
  61. ;; precisely it runs the command as
  62. ;; argument to '/bin/sh -c'. The OPEN_READ
  63. ;; mode makes it so that the return value
  64. ;; is an input port, from which one can
  65. ;; read the output of the command. The call
  66. ;; to open-input-pipe is the actual call
  67. ;; running the command.
  68. [port (open-input-pipe cmd)]
  69. ;; Set the buffer behavior of the port to
  70. ;; block buffered. For more detail read:
  71. ;; https://www.gnu.org/software/guile/manual/html_node/Buffering.html. Line
  72. ;; buffered might also make sense for
  73. ;; running commands and getting their
  74. ;; output. However, a line can also be very
  75. ;; long, so maybe block buffered is more
  76. ;; generally applicable.
  77. [_ignored (setvbuf port 'block)]
  78. ;; Apply the given procedure PROC to the
  79. ;; port. The procedure must expect an input
  80. ;; port as an argument, from which it
  81. ;; reads.
  82. [processed-output
  83. ;; Error handling using Guile's catch
  84. ;; procedure. catch is given 2 lambda
  85. ;; expressions. One to evaluate in any
  86. ;; case, and a second one to handle
  87. ;; errors, if any happen, during the
  88. ;; evaluation of the first lambda
  89. ;; expression.
  90. ;; The argument #t specifies, that
  91. ;; whatever the key of any raised
  92. ;; exception is, it will be handled by the
  93. ;; second lambda expression.
  94. ;; This exception handling deals with
  95. ;; errors, that originate from applying
  96. ;; the output processor, not with
  97. ;; exceptions from running the command
  98. ;; itself.
  99. (catch #t
  100. ;; Catch any exception thrown by applying PROC to
  101. ;; the output of CMD: if CMD fails, we check the
  102. ;; exit-code below; if CMD succeeds, PROC must be
  103. ;; able to deal with its output.
  104. (λ () (proc port))
  105. ;; Exception handling procedure. It
  106. ;; takes the key of the exception, which
  107. ;; is a symbol, and an arbitrary number
  108. ;; of other arguments.
  109. (λ (key . args)
  110. ;; To handle any error, output to the
  111. ;; stderr of the outer context, as
  112. ;; stderr was bound earlier. This
  113. ;; might not be actually handling an
  114. ;; exception at all, but at least
  115. ;; tells us, that something has gone
  116. ;; wrong.
  117. (format stderr "Caught exception ~a from ~y~%" key proc)))]
  118. ;; Finally, close the port and retrieve the
  119. ;; exit-code, which is the exit code of the
  120. ;; command, which was run with '/bin/sh
  121. ;; -c'.
  122. [exit-code (close-pipe port)])
  123. ;; Close the port, to which the child process
  124. ;; was to write errors, as the child process has
  125. ;; finished (either successfully or
  126. ;; unsuccessfully, but definitely finished).
  127. (close-port err-write)
  128. ;; If the exit code was non-zero, get the output
  129. ;; by reading from the err-read port, which is
  130. ;; the corresponding input port of the initially
  131. ;; created pipe.
  132. (display (simple-format #f "~a\n" "checking the exit code"))
  133. (cond
  134. [(zero? exit-code)
  135. (values exit-code processed-output)]
  136. [else
  137. (let ([error-message (get-string-all err-read)])
  138. (values exit-code error-message))])))))))
  139. ;; Example output processor
  140. (define output-processor
  141. (λ (in-port)
  142. (display
  143. (simple-format
  144. #f "~a\n"
  145. (get-string-all in-port)))))
  146. (define output-identity
  147. (λ (in-port)
  148. (get-string-all in-port)))
  149. ;; Example usage:
  150. (receive (exit-code output)
  151. (process-output output-identity "ls -al")
  152. (display (simple-format #f "exit code: ~a\n" exit-code))
  153. (display (simple-format #f "output: ~a\n" output)))
  154. (receive (exit-code output)
  155. (process-output output-identity "lsaas -alasdasd")
  156. (display (simple-format #f "exit code: ~a\n" exit-code))
  157. (display (simple-format #f "output: ~a\n" output)))