613 lines
26 KiB
HTML
613 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta name="generator" content=
|
||
"HTML Tidy for HTML5 for Linux version 5.2.0">
|
||
<title>Interfacing with your OS</title>
|
||
<meta charset="utf-8">
|
||
<meta name="description" content="A collection of examples of using Common Lisp">
|
||
<meta name="viewport" content=
|
||
"width=device-width, initial-scale=1">
|
||
<link rel="icon" href=
|
||
"assets/cl-logo-blue.png"/>
|
||
<link rel="stylesheet" href=
|
||
"assets/style.css">
|
||
<script type="text/javascript" src=
|
||
"assets/highlight-lisp.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery-3.2.1.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery.toc/jquery.toc.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/toggle-toc.js">
|
||
</script>
|
||
|
||
<link rel="stylesheet" href=
|
||
"assets/github.css">
|
||
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||
</head>
|
||
<body>
|
||
<h1 id="title-xs"><a href="index.html">The Common Lisp Cookbook</a> – Interfacing with your OS</h1>
|
||
<div id="logo-container">
|
||
<a href="index.html">
|
||
<img id="logo" src="assets/cl-logo-blue.png"/>
|
||
</a>
|
||
|
||
<div id="searchform-container">
|
||
<form onsubmit="duckSearch()" action="javascript:void(0)">
|
||
<input id="searchField" type="text" value="" placeholder="Search...">
|
||
</form>
|
||
</div>
|
||
|
||
<div id="toc-container" class="toc-close">
|
||
<div id="toc-title">Table of Contents</div>
|
||
<ul id="toc" class="list-unstyled"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="content-container">
|
||
<h1 id="title-non-xs"><a href="index.html">The Common Lisp Cookbook</a> – Interfacing with your OS</h1>
|
||
|
||
<!-- Announcement we can keep for 1 month or more. I remove it and re-add it from time to time. -->
|
||
<p class="announce">
|
||
📹 💻
|
||
<a style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPMACROSPOWER" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover vindarel's Lisp course in videos with this September coupon.</a>
|
||
<strong>
|
||
Recently added: 18 videos on MACROS.
|
||
</strong>
|
||
<a style="font-size: 90%" href="https://github.com/vindarel/common-lisp-course-in-videos/">Learn more</a>.
|
||
</p>
|
||
<p class="announce-neutral">
|
||
📕 <a href="index.html#download-in-epub">Get the EPUB and PDF</a>
|
||
</p>
|
||
|
||
|
||
<div id="content"
|
||
<p>The ANSI Common Lisp standard doesn’t mention this topic. (Keep in mind that it was written at a time where <a href="https://en.wikipedia.org/wiki/Lisp_machine">Lisp Machines</a> were at their peak. On these boxes Lisp <em>was</em> your operating system!) So almost everything that can be said here depends on your OS and your implementation.
|
||
There are, however, some widely used libraries, which either come with your Common Lisp implementation, or are easily
|
||
available through <a href="https://www.quicklisp.org/beta/">Quicklisp</a>. These include:</p>
|
||
|
||
<ul>
|
||
<li>ASDF3, which is included with almost all Common Lisp implementations,
|
||
includes <a href="https://common-lisp.net/project/asdf/uiop.html">Utilities for Implementation- and OS- Portability (UIOP)</a>.</li>
|
||
<li><a href="https://common-lisp.net/project/osicat/">osicat</a></li>
|
||
<li><a href="http://quickdocs.org/unix-opts/">unix-opts</a> or the newer <a href="https://github.com/dnaeon/clingon">clingon</a> are a command-line argument parsers, similar to Python’s <code>argparse</code>.</li>
|
||
</ul>
|
||
|
||
<p><a name="env"></a></p>
|
||
|
||
<h2 id="accessing-environment-variables">Accessing Environment variables</h2>
|
||
|
||
<p>UIOP comes with a function that’ll allow you to look at Unix/Linux environment variables on a lot of different CL implementations:</p>
|
||
|
||
<pre><code class="language-lisp">* (uiop:getenv "HOME")
|
||
"/home/edi"
|
||
</code></pre>
|
||
|
||
<p>Below is an example implementation, where we can see /feature flags/ used to run code on specific implementations:</p>
|
||
|
||
<pre><code class="language-lisp">* (defun my-getenv (name &optional default)
|
||
"Obtains the current value of the POSIX environment variable NAME."
|
||
(declare (type (or string symbol) name))
|
||
(let ((name (string name)))
|
||
(or #+abcl (ext:getenv name)
|
||
#+ccl (ccl:getenv name)
|
||
#+clisp (ext:getenv name)
|
||
#+cmu (unix:unix-getenv name) ; since CMUCL 20b
|
||
#+ecl (si:getenv name)
|
||
#+gcl (si:getenv name)
|
||
#+mkcl (mkcl:getenv name)
|
||
#+sbcl (sb-ext:posix-getenv name)
|
||
default)))
|
||
MY-GETENV
|
||
* (my-getenv "HOME")
|
||
"/home/edi"
|
||
* (my-getenv "HOM")
|
||
NIL
|
||
* (my-getenv "HOM" "huh?")
|
||
"huh?"
|
||
</code></pre>
|
||
|
||
<p>You should also note that some of these implementations also provide the ability to <em>set</em> these variables. These include ECL (<code>si:setenv</code>) and AllegroCL, LispWorks, and CLISP where you can use the functions from above together with <a href="http://www.lispworks.com/documentation/HyperSpec/Body/m_setf_.htm"><code>setf</code></a>. This feature might be important if you want to start subprocesses from your Lisp environment.</p>
|
||
|
||
<p>Also note that the
|
||
<a href="https://www.common-lisp.net/project/osicat/manual/osicat.html#Environment">Osicat</a>
|
||
library has the method <code>(environment-variable "name")</code>, on POSIX-like
|
||
systems including Windows. It is also <code>fset</code>-able.</p>
|
||
|
||
<p><a name="accessing-command-line"></a></p>
|
||
|
||
<h2 id="accessing-the-command-line-arguments">Accessing the command line arguments</h2>
|
||
|
||
<h3 id="basics">Basics</h3>
|
||
|
||
<p>Accessing command line arguments is implementation-specific but it
|
||
appears most implementations have a way of getting at
|
||
them. UIOP with <code>uiop:command-line-arguments</code> or <a href="https://github.com/roswell/roswell/wiki">Roswell</a> as well as external
|
||
libraries (see next section) make it portable.</p>
|
||
|
||
<p><a href="http://www.sbcl.org">SBCL</a> stores the arguments list in the special variable <code>sb-ext:*posix-argv*</code></p>
|
||
|
||
<pre><code class="language-lisp">$ sbcl my-command-line-arg
|
||
</code></pre>
|
||
|
||
<p>….</p>
|
||
|
||
<pre><code class="language-lisp">* sb-ext:*posix-argv*
|
||
|
||
("sbcl" "my-command-line-arg")
|
||
*
|
||
</code></pre>
|
||
|
||
<p>More on using this to write standalone Lisp scripts can be found in the <a href="http://www.sbcl.org/manual/index.html#Command_002dline-arguments">SBCL Manual</a></p>
|
||
|
||
<p><a href="http://www.lispworks.com">LispWorks</a> has <code>system:*line-arguments-list*</code></p>
|
||
|
||
<pre><code class="language-lisp">* system:*line-arguments-list*
|
||
("/Users/cbrown/Projects/lisptty/tty-lispworks" "-init" "/Users/cbrown/Desktop/lisp/lispworks-init.lisp")
|
||
</code></pre>
|
||
|
||
<p>Here’s a quick function to return the argument strings list across multiple implementations:</p>
|
||
|
||
<pre><code class="language-lisp">(defun my-command-line ()
|
||
(or
|
||
#+SBCL *posix-argv*
|
||
#+LISPWORKS system:*line-arguments-list*))
|
||
</code></pre>
|
||
|
||
<p>Now it would be handy to access them in a portable way and to parse
|
||
them according to a schema definition.</p>
|
||
|
||
<h3 id="parsing-command-line-arguments">Parsing command line arguments</h3>
|
||
|
||
<p>We have a look at the
|
||
<a href="https://github.com/CodyReichert/awesome-cl#scripting">Awesome CL list#scripting</a>
|
||
section and we’ll show how to use <a href="https://github.com/dnaeon/clingon">clingon</a>.</p>
|
||
|
||
<p>Please see our <a href="scripting.html#parsing-command-line-arguments">scripting recipe</a>.</p>
|
||
|
||
<h2 id="running-external-programs">Running external programs</h2>
|
||
|
||
<p><strong>uiop</strong> has us covered, and is probably included in your Common Lisp
|
||
implementation.</p>
|
||
|
||
<h3 id="synchronously">Synchronously</h3>
|
||
|
||
<p><a href="https://common-lisp.net/project/asdf/uiop.html#UIOP_002fRUN_002dPROGRAM"><code>uiop:run-program</code></a> either takes a string as argument, denoting the
|
||
name of the executable to run, or a list of strings, for the program and its arguments:</p>
|
||
|
||
<pre><code class="language-lisp">(uiop:run-program "firefox")
|
||
</code></pre>
|
||
|
||
<p>or</p>
|
||
|
||
<pre><code class="language-lisp">(uiop:run-program (list "firefox" "http:url"))
|
||
</code></pre>
|
||
|
||
<p>This will process the program output as specified and return the
|
||
processing results when the program and its output processing are
|
||
complete.</p>
|
||
|
||
<p>Use <code>:output t</code> to print to standard output.</p>
|
||
|
||
<p>This function has the following optional arguments:</p>
|
||
|
||
<pre><code class="language-lisp">run-program (command &rest keys &key
|
||
ignore-error-status
|
||
(force-shell nil force-shell-suppliedp)
|
||
input
|
||
(if-input-does-not-exist :error)
|
||
output
|
||
(if-output-exists :supersede)
|
||
error-output
|
||
(if-error-output-exists :supersede)
|
||
(element-type #-clozure *default-stream-element-type* #+clozure 'character)
|
||
(external-format *utf-8-external-format*)
|
||
&allow-other-keys)
|
||
</code></pre>
|
||
|
||
<p>It will always call a shell (rather than directly executing the command when possible)
|
||
if <code>force-shell</code> is specified. Similarly, it will never call a shell if <code>force-shell</code> is
|
||
specified to be <code>nil</code>.</p>
|
||
|
||
<p>Signal a continuable <code>subprocess-error</code> if the process wasn’t successful (exit-code 0),
|
||
unless <code>ignore-error-status</code> is specified.</p>
|
||
|
||
<p>If <code>output</code> is a pathname, a string designating a pathname, or <code>nil</code> (the default)
|
||
designating the null device, the file at that path is used as output.
|
||
If it’s <code>:interactive</code>, output is inherited from the current process;
|
||
beware that this may be different from your <code>*standard-output*</code>,
|
||
and under <code>slime</code> will be on your <code>*inferior-lisp*</code> buffer.
|
||
If it’s <code>t</code>, output goes to your current <code>*standard-output*</code> stream.
|
||
Otherwise, <code>output</code> should be a value that is a suitable first argument to
|
||
<code>slurp-input-stream</code> (qv.), or a list of such a value and keyword arguments.
|
||
In this case, <code>run-program</code> will create a temporary stream for the program output;
|
||
the program output, in that stream, will be processed by a call to <code>slurp-input-stream</code>,
|
||
using <code>output</code> as the first argument (or the first element of <code>output</code>, and the rest as keywords).
|
||
The primary value resulting from that call (or <code>nil</code> if no call was needed)
|
||
will be the first value returned by <code>run-program.</code>
|
||
E.g., using <code>:output :string</code> will have it return the entire output stream as a string.
|
||
And using <code>:output '(:string :stripped t</code>) will have it return the same string
|
||
stripped of any ending newline.</p>
|
||
|
||
<p><code>if-output-exists</code>, which is only meaningful if <code>output</code> is a string or a
|
||
pathname, can take the values <code>:error</code>, <code>:append</code>, and <code>:supersede</code> (the
|
||
default). The meaning of these values and their effect on the case
|
||
where <code>output</code> does not exist, is analogous to the <code>if-exists</code> parameter
|
||
to <code>open</code> with <code>:direction</code> <code>:output</code>.</p>
|
||
|
||
<p><code>error-output</code> is similar to <code>output</code>, except that the resulting value is returned
|
||
as the second value of <code>run-program</code>. t designates the <code>*error-output*</code>.
|
||
Also <code>:output</code> means redirecting the error output to the output stream,
|
||
in which case <code>nil</code> is returned.</p>
|
||
|
||
<p><code>if-error-output-exists</code> is similar to <code>if-output-exist</code>, except that it
|
||
affects <code>error-output</code> rather than <code>output</code>.</p>
|
||
|
||
<p><code>input</code> is similar to <code>output</code>, except that <code>vomit-output-stream</code> is used,
|
||
no value is returned, and T designates the <code>*standard-input*</code>.</p>
|
||
|
||
<p><code>if-input-does-not-exist</code>, which is only meaningful if <code>input</code> is a string
|
||
or a pathname, can take the values <code>:create</code> and <code>:error</code> (the
|
||
default). The meaning of these values is analogous to the
|
||
<code>if-does-not-exist</code> parameter to <code>open</code> with <code>:direction :input</code>.</p>
|
||
|
||
<p><code>element-type</code> and <code>external-format</code> are passed on
|
||
to your Lisp implementation, when applicable, for creation of the output stream.</p>
|
||
|
||
<p>One and only one of the stream slurping or vomiting may or may not happen
|
||
in parallel in parallel with the subprocess,
|
||
depending on options and implementation,
|
||
and with priority being given to output processing.
|
||
Other streams are completely produced or consumed
|
||
before or after the subprocess is spawned, using temporary files.</p>
|
||
|
||
<p><code>run-program</code> returns 3 values:</p>
|
||
|
||
<ul>
|
||
<li>the result of the <code>output</code> slurping if any, or <code>nil</code></li>
|
||
<li>the result of the <code>error-output</code> slurping if any, or <code>nil</code></li>
|
||
<li>either 0 if the subprocess exited with success status, or an
|
||
indication of failure via the <code>exit-code</code> of the process</li>
|
||
</ul>
|
||
|
||
<h3 id="asynchronously">Asynchronously</h3>
|
||
|
||
<p>With <a href="https://common-lisp.net/project/asdf/uiop.html#UIOP_002fLAUNCH_002dPROGRAM"><code>uiop:launch-program</code></a>.</p>
|
||
|
||
<p>Its signature is the following:</p>
|
||
|
||
<pre><code class="language-lisp">launch-program (command &rest keys
|
||
&key
|
||
input
|
||
(if-input-does-not-exist :error)
|
||
output
|
||
(if-output-exists :supersede)
|
||
error-output
|
||
(if-error-output-exists :supersede)
|
||
(element-type #-clozure *default-stream-element-type*
|
||
#+clozure 'character)
|
||
(external-format *utf-8-external-format*)
|
||
directory
|
||
#+allegro separate-streams
|
||
&allow-other-keys)
|
||
</code></pre>
|
||
|
||
<p>Output (stdout) from the launched program is set using the <code>output</code>
|
||
keyword:</p>
|
||
|
||
<ul>
|
||
<li>If <code>output</code> is a pathname, a string designating a pathname, or
|
||
<code>nil</code> (the default) designating the null device, the file at that
|
||
path is used as output.</li>
|
||
<li>If it’s <code>:interactive</code>, output is inherited from the current process;
|
||
beware that this may be different from your <code>*standard-output*</code>, and
|
||
under Slime will be on your <code>*inferior-lisp*</code> buffer.</li>
|
||
<li>If it’s <code>T</code>, output goes to your current <code>*standard-output*</code> stream.</li>
|
||
<li>If it’s <code>:stream</code>, a new stream will be made available that can be accessed via
|
||
<code>process-info-output</code> and read from.</li>
|
||
<li>Otherwise, <code>output</code> should be a value that the underlying lisp
|
||
implementation knows how to handle.</li>
|
||
</ul>
|
||
|
||
<p><code>if-output-exists</code>, which is only meaningful if <code>output</code> is a string or a
|
||
pathname, can take the values <code>:error</code>, <code>:append</code>, and <code>:supersede</code> (the
|
||
default). The meaning of these values and their effect on the case
|
||
where <code>output</code> does not exist, is analogous to the <code>if-exists</code> parameter
|
||
to <code>open</code> with <code>:DIRECTION :output</code>.</p>
|
||
|
||
<p><code>error-output</code> is similar to <code>output</code>. T designates the <code>*error-output*</code>,
|
||
<code>:output</code> means redirecting the error output to the output stream,
|
||
and <code>:stream</code> causes a stream to be made available via
|
||
<code>process-info-error-output</code>.</p>
|
||
|
||
<p><code>launch-program</code> returns a <code>process-info</code> object, which look like the following (<a href="https://gitlab.common-lisp.net/asdf/asdf/blob/master/uiop/launch-program.lisp#L205">source</a>):</p>
|
||
|
||
<pre><code class="language-lisp">(defclass process-info ()
|
||
(
|
||
;; The advantage of dealing with streams instead of PID is the
|
||
;; availability of functions like `sys:pipe-kill-process`.
|
||
(process :initform nil)
|
||
(input-stream :initform nil)
|
||
(output-stream :initform nil)
|
||
(bidir-stream :initform nil)
|
||
(error-output-stream :initform nil)
|
||
;; For backward-compatibility, to maintain the property (zerop
|
||
;; exit-code) <-> success, an exit in response to a signal is
|
||
;; encoded as 128+signum.
|
||
(exit-code :initform nil)
|
||
;; If the platform allows it, distinguish exiting with a code
|
||
;; >128 from exiting in response to a signal by setting this code
|
||
(signal-code :initform nil)))
|
||
</code></pre>
|
||
|
||
<p>See the <a href="https://gitlab.common-lisp.net/asdf/asdf/blob/master/uiop/launch-program.lisp#L508">docstrings</a>.</p>
|
||
|
||
<h4 id="test-if-a-subprocess-is-alive">Test if a subprocess is alive</h4>
|
||
|
||
<p><code>uiop:process-alive-p</code> tests if a process is still alive, given a
|
||
<code>process-info</code> object returned by <code>launch-program</code>:</p>
|
||
|
||
<pre><code class="language-lisp">* (defparameter *shell* (uiop:launch-program "bash" :input :stream :output :stream))
|
||
|
||
;; inferior shell process now running
|
||
* (uiop:process-alive-p *shell*)
|
||
T
|
||
|
||
;; Close input and output streams
|
||
* (uiop:close-streams *shell*)
|
||
* (uiop:process-alive-p *shell*)
|
||
NIL
|
||
</code></pre>
|
||
|
||
<h4 id="get-the-exit-code">Get the exit code</h4>
|
||
|
||
<p>We can use <code>uiop:wait-process</code>. If the process is finished, it returns
|
||
immediately, and returns the exit code. If not, it waits for the
|
||
process to terminate.</p>
|
||
|
||
<pre><code class="language-lisp">(uiop:process-alive-p *process*)
|
||
NIL
|
||
(uiop:wait-process *process*)
|
||
0
|
||
</code></pre>
|
||
|
||
<p>An exit code to 0 means success (use <code>zerop</code>).</p>
|
||
|
||
<p>The exit code is also stored in the <code>exit-code</code> slot of our
|
||
<code>process-info</code> object. We see from the class definition above that it
|
||
has no accessor, so we’ll use <code>slot-value</code>. It has an <code>initform</code> to
|
||
nil, so we don’t have to check if the slot is bound. We can do:</p>
|
||
|
||
<pre><code class="language-lisp">(slot-value *my-process* 'uiop/launch-program::exit-code)
|
||
0
|
||
</code></pre>
|
||
|
||
<p>The trick is that we <em>must</em> run <code>wait-process</code> beforehand, otherwise
|
||
the result will be <code>nil</code>.</p>
|
||
|
||
<p>Since <code>wait-process</code> is blocking, we can do it on a new thread:</p>
|
||
|
||
<pre><code class="language-lisp">(bt:make-thread
|
||
(lambda ()
|
||
(let ((exit-code (uiop:wait-process
|
||
(uiop:launch-program (list "of" "commands"))))
|
||
(if (zerop exit-code)
|
||
(print :success)
|
||
(print :failure)))))
|
||
:name "Waiting for <program>")
|
||
</code></pre>
|
||
|
||
<p>Note that <code>run-program</code> returns the exit code as the third value.</p>
|
||
|
||
<h3 id="input-and-output-from-subprocess">Input and output from subprocess</h3>
|
||
|
||
<p>If the <code>input</code> keyword is set to <code>:stream</code>, then a stream is created
|
||
and can be written to in the same way as a file. The stream can be
|
||
accessed using <code>uiop:process-info-input</code>:</p>
|
||
|
||
<pre><code class="language-lisp">;; Start the inferior shell, with input and output streams
|
||
* (defparameter *shell* (uiop:launch-program "bash" :input :stream :output :stream))
|
||
;; Write a line to the shell
|
||
* (write-line "find . -name '*.md'" (uiop:process-info-input *shell*))
|
||
;; Flush stream
|
||
* (force-output (uiop:process-info-input *shell*))
|
||
</code></pre>
|
||
|
||
<p>where <a href="http://clhs.lisp.se/Body/f_wr_stg.htm">write-line</a> writes the
|
||
string to the given stream, adding a newline at the end. The
|
||
<a href="http://clhs.lisp.se/Body/f_finish.htm">force-output</a> call attempts to
|
||
flush the stream, but does not wait for completion.</p>
|
||
|
||
<p>Reading from the output stream is similar, with
|
||
<code>uiop:process-info-output</code> returning the output stream:</p>
|
||
|
||
<pre><code class="language-lisp">* (read-line (uiop:process-info-output *shell*))
|
||
</code></pre>
|
||
|
||
<p>In some cases the amount of data to be read is known, or there are
|
||
delimiters to determine when to stop reading. If this is not the case,
|
||
then calls to <a href="http://clhs.lisp.se/Body/f_rd_lin.htm">read-line</a> can
|
||
hang while waiting for data. To avoid this,
|
||
<a href="http://clhs.lisp.se/Body/f_listen.htm">listen</a> can be used to test if
|
||
a character is available:</p>
|
||
|
||
<pre><code class="language-lisp">* (let ((stream (uiop:process-info-output *shell*)))
|
||
(loop while (listen stream) do
|
||
;; Characters are immediately available
|
||
(princ (read-line stream))
|
||
(terpri)))
|
||
</code></pre>
|
||
|
||
<p>There is also
|
||
<a href="http://clhs.lisp.se/Body/f_rd_c_1.htm">read-char-no-hang</a> which reads
|
||
a single character, or returns <code>nil</code> if no character is available.
|
||
Note that due to issues like buffering, and the timing of when the
|
||
other process is executed, there is no guarantee that all data sent
|
||
will be received before <code>listen</code> or <code>read-char-no-hang</code> return <code>nil</code>.</p>
|
||
|
||
<h3 id="capturing-standard-and-error-output">Capturing standard and error output</h3>
|
||
|
||
<p>Capturing standard output, as seen above, is easily done by telling
|
||
<code>:output</code> to be <code>:string</code>, or using <code>:output '(:string :stripped t)</code> to
|
||
strip any ending newline.</p>
|
||
|
||
<p>You can ask the same to <code>:error-output</code> and, in addition, you can ask
|
||
<code>uiop:run-program</code> to <em>not</em> signal an error, thus to not enter the
|
||
interactive debugger, with <code>:ignore-error-status t</code>.</p>
|
||
|
||
<p>In that case, you can check the success or the failure of the program
|
||
with the returned <code>exit-code</code>. 0 is success.</p>
|
||
|
||
<p>Here’s everything together:</p>
|
||
|
||
<pre><code class="language-lisp">(uiop:run-program (list "git"
|
||
"checkout"
|
||
"me/does-not-exist")
|
||
:output :string
|
||
:error-output :string
|
||
:ignore-error-status t)
|
||
;; =>
|
||
""
|
||
"error: pathspec 'me/does-not-exist did not match any file(s) known to git
|
||
"
|
||
1
|
||
</code></pre>
|
||
|
||
<p><code>uiop:run-program</code> returns 3 values:</p>
|
||
|
||
<ul>
|
||
<li>the standard output (here, as a blank string)</li>
|
||
<li>the error output (here, as a string with our error message)</li>
|
||
<li>the exit code</li>
|
||
</ul>
|
||
|
||
<p>We can bind them with <code>multiple-value-bind</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(multiple-value-bind (output error-output exit-code)
|
||
(uiop:run-program (list …))
|
||
(unless (zerop exit-code)
|
||
(format t "error output is: ~a" error-output)))
|
||
</code></pre>
|
||
|
||
<h3 id="running-visual-commands-htop">Running visual commands (htop)</h3>
|
||
|
||
<p>Use <code>uiop:run-program</code> and set both <code>:input</code> and <code>:output</code> to <code>:interactive</code>:</p>
|
||
|
||
<pre><code class="language-lisp">(uiop:run-program "htop" :output :interactive :input :interactive)
|
||
</code></pre>
|
||
|
||
<p>This will spawn <code>htop</code> in full screen, as it should.</p>
|
||
|
||
<p>It works for more commands (<code>sudo</code>, <code>vim</code>…), however not for all interactive
|
||
programs, such as <code>less</code> or <code>fzf</code>.</p>
|
||
|
||
<h2 id="piping">Piping</h2>
|
||
|
||
<p>Here’s an example to do the equivalent of <code>ls | sort</code>. Note that “ls”
|
||
uses <code>launch-program</code> (async) and outputs to a stream, where “sort”,
|
||
the last command of the pipe, uses <code>run-program</code> and outputs to a
|
||
string.</p>
|
||
|
||
<pre><code class="language-lisp">(uiop:run-program "sort"
|
||
:input
|
||
(uiop:process-info-output
|
||
(uiop:launch-program "ls"
|
||
:output :stream))
|
||
:output :string)
|
||
</code></pre>
|
||
|
||
<h2 id="get-lisps-current-process-id-pid">Get Lisp’s current Process ID (PID)</h2>
|
||
|
||
<p>Implementations provide their own functions for this.</p>
|
||
|
||
<p>On SBCL:</p>
|
||
|
||
<pre><code class="language-lisp">(sb-posix:getpid)
|
||
</code></pre>
|
||
|
||
<p>It is possible portably with the osicat library:</p>
|
||
|
||
<pre><code class="language-lisp">(osicat-posix:getpid)
|
||
</code></pre>
|
||
|
||
<p>Here again, we could find it by using the <code>apropos</code> function:</p>
|
||
|
||
<pre><code class="language-lisp">CL-USER> (apropos "pid")
|
||
OSICAT-POSIX:GETPID (fbound)
|
||
OSICAT-POSIX::PID
|
||
[…]
|
||
SB-IMPL::PID
|
||
SB-IMPL::WAITPID (fbound)
|
||
SB-POSIX:GETPID (fbound)
|
||
SB-POSIX:GETPPID (fbound)
|
||
SB-POSIX:LOG-PID (bound)
|
||
SB-POSIX::PID
|
||
SB-POSIX::PID-T
|
||
SB-POSIX:WAITPID (fbound)
|
||
[…]
|
||
</code></pre>
|
||
|
||
|
||
<p class="page-source">
|
||
Page source: <a href="https://github.com/LispCookbook/cl-cookbook/blob/master/os.md">os.md</a>
|
||
</p>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
|
||
// Don't write the TOC on the index.
|
||
if (window.location.pathname != "/cl-cookbook/") {
|
||
$("#toc").toc({
|
||
content: "#content", // will ignore the first h1 with the site+page title.
|
||
headings: "h1,h2,h3,h4"});
|
||
}
|
||
|
||
$("#two-cols + ul").css({
|
||
"column-count": "2",
|
||
});
|
||
$("#contributors + ul").css({
|
||
"column-count": "4",
|
||
});
|
||
</script>
|
||
|
||
|
||
|
||
<div>
|
||
<footer class="footer">
|
||
<hr/>
|
||
© 2002–2023 the Common Lisp Cookbook Project
|
||
<div>
|
||
📹 Discover <a style="color: darkgrey; text-decoration: underline", href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">vindarel's Lisp course on Udemy</a>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div id="toc-btn">T<br>O<br>C</div>
|
||
</div>
|
||
|
||
<script text="javascript">
|
||
HighlightLisp.highlight_auto({className: null});
|
||
</script>
|
||
|
||
<script type="text/javascript">
|
||
function duckSearch() {
|
||
var searchField = document.getElementById("searchField");
|
||
if (searchField && searchField.value) {
|
||
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
|
||
window.location.href = "https://duckduckgo.com/?kj=b2&kf=-1&ko=1&q=" + query;
|
||
// https://duckduckgo.com/params
|
||
// kj=b2: blue header in results page
|
||
// kf=-1: no favicons
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script async defer data-domain="lispcookbook.github.io/cl-cookbook" src="https://plausible.io/js/plausible.js"></script>
|
||
|
||
</body>
|
||
</html>
|