PicoLisp Explored: Piping from the Command Line

PicoLisp Explored: Piping from the Command Line


5 min read

Today we will explore further input/output options in PicoLisp, specifically how to pipe input from the command line to a PicoLisp script. For the example, we will try to read the battery status of our laptop, but of course the principles apply to any kind of command line output.

This article builds up on the Input-Output functions post from the Beginner's Guide.

Getting the battery status (Debian)

In Debian systems we can get the status of your system with the upower command. First, we need to find the path of your power source by enumerating our system with upower -e:

$ upower -e

$ stands for commands which are executed in the terminal.

We can see a number of battery powered devices. The main one which powers my laptop is battery_BAT1.

Now we let's check the status of "battery_BAT1" by typing upower -i <path> which gives a lot of information such as vendor, path, model...:

$ upower -i /org/freedesktop/UPower/devices/battery_BAT1
  native-path:          BAT1
    state:               discharging
    warning-level:       none
    energy:              23,1186 Wh
    energy-empty:        0 Wh
    energy-full:         49,7364 Wh
    energy-full-design:  55,2372 Wh
    energy-rate:         13,3242 W
    voltage:             15,018 V
    time to empty:       1,7 hours
    percentage:          46%
    capacity:            90,0415%
    technology:          lithium-ion

Let's try to pipe this output to a simple PicoLisp script.

Creating the script

As we learned in the "Input-Output"-functions blog, we have two main options to read in from an input source:

  • read stops at specific delimitors such as whitespace or new-line,
  • line reads in the full line.

But what is our input source? Let's check the documentation of the in-function:

(in 'any . prg) -> any

Opens any as input channel during the execution of prg. The current input channel will be saved and restored appropriately. If the argument is NIL, standard input is used. If the argument is a symbol, it is used as a file name (opened for reading and writing if the first character is "+"). [...] Otherwise (if it is a list), it is taken as a command with arguments, and a pipe is opened for input. [...]

According to the docs, we have three main options:

  1. Reading from standard input using in NIL
  2. Reading from file using in <filename>
  3. Reading from command with in <list>.

Let's try the third option and see if we can get the output to PicoLisp. It seems we need to transform the command to a list. What does that mean?

It's much easier than you might expect - it simply means we split up the words of the command like this: ("upower" "-i" "/org/freedesktop/UPower/devices/battery_BAT1").

Let's type pil + to open the PicoLisp REPL and try to get the first line:

: (in '("upower" "-i" "/org/freedesktop/UPower/devices/battery_BAT1") (line T))      
-> "  native-path:          BAT1"

Note: the quote ' before the list is needed to escape evaluation. (line T) returns the list as single string instead of a list of single characters.

We get the first line of the command line printed as expected.

Now let's do the same using piping. In this case, we use input option 1) NIL to get the input from "the current input channel". We define a script called battery.l and try:

# <battery.l>
(in NIL
   (prinl (line)) )


Now we query the battery status from the command line and pipe it to the PicoLisp script.

$ upower -i /org/freedesktop/UPower/devices/battery_BAT1 | pil battery.l
  native-path:          BAT1

Again, we receive the first line as output.

Formatting the output

upower gives us a lot of information, but probably we will not need all of it. Of course we could pre-format output using tools like grep, but let's try to do it in PicoLisp now.

Some helpful functions:

  • (from 'any): Skips the current input channel until one of the strings any is found, and starts subsequent reading from that point.
  • (till 'any): Reads from the current input channel till a character contained in any is found (or until end of file if any is NIL). -skip ['any]: Skips all whitespace (and comments if any is given) in the input stream.

As an example, let's say we want to read out the battery status and percentage. As a reminder, the command line output has this format:

    state:               discharging
    percentage:          46%

Let's add (from "percentage:") to our previous script.

: (in '("upower" "-i" "/org/freedesktop/UPower/devices/battery_BAT1") (from "percentage:") (line T]
-> "          70%"

] is a short form which closes all open parentheses.

The output is correct, but we have a lot of whitespace before the output. In order to prettify it a little bit, let's (skip) the whitespace:

: (in '("upower" "-i" "/org/freedesktop/UPower/devices/battery_BAT1") (from "percentage:") (skip) (line T]
-> "70%"

Looks good!

Finalizing the script

We are almost done, but let's make three small improvements:

  1. We should add a check if the "percentage:" string exists in our piped output before we try reading it.
  2. Read the output of "state"
  3. Add some custom formatting to the output.

A possible solution could look like this:

(in NIL
   (when (from "state:")
      (prinl "Battery status: " (line))
      (when (from "percentage:")
         (prinl (line) " remaining") ) ) )


Calling the script returns:

$ upower -i /org/freedesktop/UPower/devices/battery_BAT1 | pil batt.l
Battery status: charging
49% remaining

The final script can be downloaded here.