emacs.d/clones/lisp/lispcookbook.github.io/cl-cookbook/os.html

617 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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> &ndash; 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> &ndash; 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 href="https://www.udemy.com/course/common-lisp-programming/?couponCode=6926D599AA-LISP4ALL">NEW! Learn Lisp in videos and support our contributors with this 40% discount.</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 doesnt 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> is a command-line argument parser, similar to Pythons <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 thatll 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 &amp;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>Heres 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 well show how to use <a href="https://github.com/mrkkrp/unix-opts">unix-opts</a>.</p>
<pre><code class="language-lisp">(ql:quickload "unix-opts")
</code></pre>
<p>We can now refer to it with its <code>opts</code> nickname.</p>
<p>We first declare our arguments with <code>opts:define-opts</code>, for example</p>
<pre><code class="language-lisp">(opts:define-opts
(:name :help
:description "print this help text"
:short #\h
:long "help")
(:name :level
:description "The level of something (integer)."
:short #\l
:long "level"
:arg-parser #'parse-integer))
</code></pre>
<p>Everything should be self-explanatory. Note that <code>#'parse-integer</code> is
a built-in CL function.</p>
<p>Now we can parse and get them with <code>opts:get-opts</code>, which returns two
values: the first is the list of valid options and the second the
remaining free arguments. We then must use multiple-value-bind to
catch everything:</p>
<pre><code class="language-lisp">(multiple-value-bind (options free-args)
;; There is no error handling yet (specially for options not having their argument).
(opts:get-opts)
</code></pre>
<p>We can explore this by giving a list of strings (as options) to
<code>get-opts</code>:</p>
<pre><code class="language-lisp">(multiple-value-bind (options free-args)
(opts:get-opts '("hello" "-h" "-l" "1"))
(format t "Options: ~a~&amp;" options)
(format t "free args: ~a~&amp;" free-args))
Options: (HELP T LEVEL 1)
free args: (hello)
NIL
</code></pre>
<p>If we put an unknown option, we get into the debugger. We refer you to
unix-opts documentation and code sample to deal with erroneous
options and other errors.</p>
<p>We can access the arguments stored in <code>options</code> with <code>getf</code> (it is a
property list), and we can exit (in a portable way) with
<code>opts:exit</code>. So, for example:</p>
<pre><code class="language-lisp">(multiple-value-bind (options free-args)
;; No error handling.
(opts:get-opts)
(if (getf options :help)
(progn
(opts:describe
:prefix "My app. Usage:"
:args "[keywords]")
(exit))) ;; &lt;= exit takes an optional return status.
...
</code></pre>
<p>And thats it for now, you know the essential. See the documentation
for a complete example, and the Awesome CL list for useful packages to
use in the terminal (ansi colors, printing tables and progress bars,
interfaces to readline,…).</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 &amp;rest keys &amp;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*)
&amp;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 wasnt 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 its <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 its <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 &amp;rest keys
&amp;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
&amp;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 its <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 its <code>T</code>, output goes to your current <code>*standard-output*</code> stream.</li>
<li>If its <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) &lt;-&gt; 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
;; &gt;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 well use <code>slot-value</code>. It has an <code>initform</code> to
nil, so we dont 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 &lt;program&gt;")
</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>
<h2 id="piping">Piping</h2>
<p>Heres 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 Lisps 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&gt; (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/>
&copy; 2002&ndash;2021 the Common Lisp Cookbook Project
</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>