# Capturing shell output

**URL:** https://swi-prolog.discourse.group/t/capturing-shell-output/7301
**Category:** General
**Created:** [March 11, 2024, 4:07pm UTC](https://swi-prolog.discourse.group/t/capturing-shell-output/7301 "2024-03-11T16:07:32Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![esad](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/esad/32/884_2.png) [@esad](https://swi-prolog.discourse.group/u/esad)
#### Post date: [March 11, 2024, 4:07pm UTC](https://swi-prolog.discourse.group/t/capturing-shell-output/7301/1 "2024-03-11T16:07:32Z")

</div>

Hi!

Is there an API in SWI-Prolog that would be something between `shell/2` and `process_create/3` in terms of complexity? All I want to is run some commands and capture the output, the `process_create` dance with setting up input, output then reading those is bit too much of an overhead for such a simple, and in my opinion, relatively common task. For example in Ruby, it’s so common that it’s as easy as just quoting the command in backticks (e.g. `user = `whoami``).

---

<div class="post-metadata">

### Author: ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)
#### Post date: [March 11, 2024, 5:01pm UTC](https://swi-prolog.discourse.group/t/capturing-shell-output/7301/2 "2024-03-11T17:01:04Z")

</div>

It is still a one-liner 🙂

```prolog
?- setup_call_cleanup(process_create(path(whoami), [], [stdout(pipe(Out))]), read_string(Out, _, Str), close(Out)).
Out = <stream>(0x556dc2d65e00),
Str = "janw\n".

```

AFAIK there are various packages that provide such abstractions. All is simple as long as there is one input or output that we want to connect to Prolog. If we want to handle multiple streams we must make sure we keep draining the output stream(s). Failure to to do so may fill the process’ output buffer and cause the process to suspend and the whole thing to deadlock. Then it gets harder as we need I/O multiplexing using wait\_for\_input/3 or we need to use threads. This notably gets complicated if we want to cleanup everything cleanly in all error scenarios.

Possibly we should add a library(process\_util), similar to thread\_util to deal with some of these common scenarios? What are the common scenarios though? Note that even above we may or may not want to remove white space or get the result as atom, string, codes or chars.

Together with the portability problems, quoting problems and typically rather poor performance has been a motivation to offer as much as possible functionality through libraries rather then by calling processes. That is why there are bindings for libarchive, zlib, a lot if the file primitives, etc. Note that Janus may also allow to do a lot of this stuff through Python.

Maybe not the answer you were looking for …

---

<div class="post-metadata">

### Author: ![brebs](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@brebs](https://swi-prolog.discourse.group/u/brebs)
#### Post date: [March 11, 2024, 6:27pm UTC](https://swi-prolog.discourse.group/t/capturing-shell-output/7301/3 "2024-03-11T18:27:20Z")

</div>

> [@esad](#):
>
> it’s as easy as just quoting the command in backticks

What about:

- Exit code - only provided at the end. Normally 0 for success, but [grep returns 1](https://stackoverflow.com/questions/49730043/the-exit-status-code-for-the-grep-command) if no match, which might or might not be acceptable also.
- Do you want to stream the output (e.g. piping through `gzip`), or wait for the definiteness of the exit code first?
- STDERR output, if any (rather than just STDOUT)
- For completeness - might want to be piping via STDIN to e.g. `gzip`

I gave some basic predicates at [How to get shell echo in Prolog variables - #15 by brebs](https://swi-prolog.discourse.group/t/how-to-get-shell-echo-in-prolog-variables/6547/15)

Haven’t looked at it since. This could do with a standard library, sure.

---

<div class="post-metadata">

### Author: ![esad](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/esad/32/884_2.png) [@esad](https://swi-prolog.discourse.group/u/esad)
#### Post date: [March 11, 2024, 7:47pm UTC](https://swi-prolog.discourse.group/t/capturing-shell-output/7301/4 "2024-03-11T19:47:19Z")

</div>

I do agree that it’s not too hard to do `process_create` and have full control about all details. It’s just that many times while using SWI-Prolog to script things I just want the default case of reading the output from stdout and not really handling errors. A quick github search for “process\_create lang:prolog” reveals mostly this usage pattern. I think natural place for this kind of behaviour would be `shell/3` as we probably wouldn’t want to break compatibiliy of `shell/2` which could return both error code and output of the command.

---

<div class="post-metadata">

### Author: ![brebs](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@brebs](https://swi-prolog.discourse.group/u/brebs)
#### Post date: [March 11, 2024, 8:22pm UTC](https://swi-prolog.discourse.group/t/capturing-shell-output/7301/5 "2024-03-11T20:22:41Z")

</div>

> [@esad](#):
>
> not really handling errors

Can’t write a professional-quality app without sensible error-handling.

Anyway, in the code I linked to, you want `ExitCode` to unify with `exit(0)` - easy.

The exit code should never be ignored - it indicates whether the process’ output is complete.

---

<div class="post-metadata">

### Author: ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)
#### Post date: [March 12, 2024, 9:54am UTC](https://swi-prolog.discourse.group/t/capturing-shell-output/7301/6 "2024-03-12T09:54:57Z")

</div>

> [@esad](#):
>
> I think natural place for this kind of behaviour would be `shell/3` as we probably wouldn’t want to break compatibiliy of `shell/2` which could return both error code and output of the command.

I fear this will get an ever growing tree of options and arguments. Note that shell/1,2 uses the system command interpreter, in introducing the necessary portability and quoting issues. You may or may not want using the shell. process\_create/3 calls the process itself and provides safe passing of arguments (except for Windows as the underlying API passes the commandline as a single string).

For library(git), which provides some abstraction over running git, notably for the pack manager, I have defined git\_process\_output/3. This takes as argument a closure that is called with the output stream as created by create\_process/3. That is a reasonable API for a lot of stuff, but probably again too complicated.

I’m still in doubt between the idea that there are simply too many options and applications should make up their mind and define a couple of helpers to get what they need, e.g.

```prolog
shell_output(Command, String) :-
    setup_call_cleanup(
        process_create('/bin/sh', ['-c', Command],
                       [ stdout(pipe(Out))
                       ]),
        read_string(Out, _, String),
        close(Out)).

```

This is a bit annoying if you just want to write a half page script to do something useful, but it is IMO a bearable overhead for most more serious applications.

On the other handle, there are complicated scenarios were we need threads and/or I/O multiplexing. One would like to have this complicated stuff implemented by knowledgeable people and reused from a library. Unfortunately, the list of scenarios is large.

P.s. Note that SWI-Prolog also offers open/3,4 with pipe(Command) as “filename”. That is a little shorter. It is not portable though and it makes decisions for you that you may or may not like while you can be explicit about these in the example above.

> [@brebs](#):
>
> The exit code should never be ignored - it indicates whether the process’ output is complete.

Luckily, if we ignore the exit code when using process\_create/3, one gets an exception if the process terminates with non-zero exit or due to a signal.

---

<div class="post-metadata">

### Author: ![nicos](https://avatars.discourse-cdn.com/v4/letter/n/7feea3/32.png) [@nicos](https://swi-prolog.discourse.group/u/nicos)
#### Post date: [March 15, 2024, 6:38pm UTC](https://swi-prolog.discourse.group/t/capturing-shell-output/7301/7 "2024-03-15T18:38:22Z")

</div>

if you are after convenience or elegance, you might be interested in pack(by\_unix).

άμπελος;src/by\_unix\> swipl -q -f none  
?- use\_module(library(by\_unix)).  
true.

?- Lines @@ ls().  
Lines = [doc, ‘pack.pl’, prolog, scripts].

hope it entertains if not help,

## Nicos Angelopoulos

> **[Nicos Angelopoulos](https://stoics.org.uk/~nicos/)**
>
> logic programming
