flesh/Shell.org
2023-05-28 15:14:15 -07:00

7.2 KiB

Relish as a Shell

Note: this document is best read using a dedicated ORG mode editor

Description

This document outlines the ways that Relish can be used as a shell for daily administration or scripting purposes. Readers should have already read through the general Relish documentation. With the exception of the circuit function, all facilities introduced in this document apply only to relish interpreters compiled with the POSIX module enabled and CFG_RELISH_POSIX set at load time.

Launch a command

For the most common uses (executing shell commands) a function is provided to find and load binaries from entries in the PATH variable. This function may be called either with load or l.

  (load htop) ;; executes the htop binary, if htop is found on the users PATH
  (l emacs)   ;; executes the emacs binary, if emacs is found on the users PATH

The load command takes an infinite number of arguments and uses the following rules to construct a shell command from them:

  • symbols that are not set are taken as strings

      (l emacs -nw) ;; 'emacs' and '-nw' not defined
  • symbols that are set are replaced by their values

      (l ls -la HOME)
      (let ((ping-count 4)
            (domain "sunnypup.io"))
        (l ping -c ping-count domain)
  • nested forms are evaluated

      (l cat (concat HOME "/notes.txt"))

Shell command evaluation rules apply to the following functions:

  • load / l
  • pipe
  • load-to-string
  • load-with
  • bg

With the exception of the load-to-string function and the bg function, each of the aforementioned functions returns the exit code of a new process as an integer.

Symbols set in the Relish REPL are converted to strings and placed in the environment as well, so def can be used to define environment variables for a process (but not let, which only creates form-local symbols).

Special command forms

A number of forms are provided to offer a first class experience when running Relish as a shell.

Command short circuiting

In a shell such as Bash or Zsh, commands can be chained with the && operator:

  $ apt update && apt upgrade && echo "Success!"

In these chains, if one command fails the next one(s) are not run. Colloquially, the command short-circuits. A similar construct is offered in Relish called circuit. Circuit will evaluate one or more forms (all expected to evaluate to either an integer (shell command) or a boolean (more general form). If a form returns false (or non-zero) no other forms are evaluated. The printed error message will identify where in the sequence evaluation was halted.

(circuit
  (l apt update)  ;; if this fails, no upgrade is made
  (l apt upgrade) ;; if this fails, "Success!" is not printed
  (l echo "Success!"))

Command piping

In a shell such as Bash or Zsh, the output of one command may be automatically looped into the input of another command. Below is an example of three shell commands piped together. On execution this example counts the number of running Relish processes:

  $ ps aux | grep relish | wc -l

In order to provide such a facility in Relish, the pipe function is provided.

(pipe
  (ps aux)
  (grep relish)
  (wc -l))

Processing command output

There will be many times a user will want to directly process command output other than the process exit code. For this a function load-to-string is included. Below is an example of a series of commands that leverage this function to print a symbolic token based on the presence and status of a Git repository.

(def in-a-git-repo?
'returns true or false depending on if currently in a git repo'
  () (eq? (load-to-string git rev-parse --is-inside-work-tree) "true"))

(def git-repo-is-dirty?
'returns true or false depending on if current dir is a dirty git repo'
  () (not (eq? (load-to-string git diff '--stat') "")))

(def git-status 'returns "(git:<branch>{!,})" if dir is in a git repository'
  ()
  (if (in-a-git-repo?)
    (concat
      "(git:"
      (load-to-string git rev-parse --abbrev-ref HEAD)
      (if (git-repo-is-dirty?)
        "!"
        "")
      ")")
    ''))

(git-status)

(Example is from Ava's Laptop Prompt)

Redirecting command output to or from files

Another common shell feature is the redirection of input/output to/from files. For example:

  $ find / -iname "needle.haystack" 2> /dev/null

Relish can redirect "stdin", "stdout", or "stderr" of a shell command using the load-with function.

(load-with (("stderr" "/dev/null"))
  (find /  -iname "needle.haystack"))

Or, a more comprehensive example using hypothetical commands and data:

(load-with (("stdin"  "img.png")
            ("stdout" "img.jpg")
            ("stderr" "/dev/null"))
  (my-img-convert))

Control background and foreground processes

Relish implements fully interactive job control. To launch a background process use the bg function:

(bg emacs -nw)

To get all jobs in your shell use the system ps binary:

(l ps)

To foreground a background process use the fg function:

(fg <pid>)

Changing directories

Relish also provides a cd utility to change current working directory:

(cd (concat HOME '/repositories')) ;; $ cd ~/repositories

Creating bindings for shell commands

Daily Relish users will long for first class shell commands that are accounted for in autocomplete.

A simple solution:

(def lis 'shortcut for ls -la'
  (lambda (dir) (l ls -la dir)))

(lis HOME)

The reader may also wish for a utility that allows them to create first-class shortcuts to shell subcommands. It is for this purpose that genbind was written. Genbind allows the user to create globally defined functions that reference shell commands (more specifically, subcommands) from their shell. The reader is advised to refer to the documentation in Genbind for more information.

(def g-add 'shortcut for git add'
  (gen-binding "git" "add"))

(g-add ("src" "docs"))

Or:

;; REQUIRES USERLIB FOR PREPEND (or write your own)
(def pacman-search 'shortcut for sudo pacman -Ss'
  (lambda (many-dirs) ((gen-binding "sudo" "pacman") (prepend "-Ss" many-dirs))))

(pacman-search "xfce4")

Using shell commands in the configuration file

A warning to the reader:

  • the .relishrc configuration file is loaded before any POSIX shell functions are added
  • direct calls to these functions will throw errors
  • the user can still configure functions and data that relies on shell commands, just not directly
  • lambdas can be used to encapsulate shell commands and run them during the prompt or as shortcuts/functions at the REPL
  • note the examples in this document, as well as Ava's Laptop Prompt