PSALM - Plain Shell Access to Library Modules
Not logged in

Introduction

While my GTK-server was specifically intended to allow access to the GTK libraries for interpreted programming (like shell scripting), I always wanted to replace this specific approach for a more generic piece of software, which enables access for any shell or script to any library on the system.

First of all, the program should be plain and simple. For example, it should not use external configuration files and it should neither have an elaborate API nor a macro language.

Also, the program should be developed in a generic manner, meaning without depending on 3rd party Foreign Function Interface libraries like FFI, FFCALL, DYNCALL or Cinvoke.

Lastly, it should implement a communication interface which is universally available on all Unix type platforms.

The result of these considerations is the program below. Because of a lack of fantasy it simply is called “Psalm”, which is an acronym for Plain Shell Access to Library Modules.

With Psalm, it is possible to connect any shell or script to a C library on your system, invoke the requested function, and obtain a result.

Psalm was fully implemented in the Basic-to-C generator BaCon. The advantage of this is that BaCon allows high-level programming while it generates C code which is generic and can be compiled on various platforms.

Download

The code for Psalm is here: psalm.bac

Compile Psalm with BaCon. There are no external libraries required.

  $ bacon psalm

If a full installation of BaCon cannot be achieved then at least the shell version of BaCon is required. Compile as follows (use either BASH, KSH or ZSH):

  $ bash ./bacon.sh psalm

Tutorial

As an introduction, the following is an example of an interactive session, demonstrating the use of the ‘cosine’ function from the math library in C:

  $ ./psalm
  DEF libm.so double cos double
  ok
  EXE cos 0.8
  0.696707
  EXIT
  Goodbye!

First, Psalm needs to know the prototype of the function. This can be defined using the DEFINE keyword (abbreviated: DEF). In Psalm, all keywords may be written in small letters as well. The DEF keyword defines the actual library name, the return type, the name of the function itself and the type of each argument.

If the definition was successful and Psalm can find the function in the library, it will return ‘ok’. If Psalm detects an error, it will return a message indicating the problem.

Note that Psalm needs to know the exact name of the library in which the function resides. The math library is called ‘libm.so’ on most systems, but sometimes the name is different, for example ‘libm.so.6’. It is the responsibility of the script to use the correct name. If the library is not found then Psalm will generate an error (‘cannot open library’). However, Psalm always will understand ‘libm.so’ and ‘libc.so’ as it makes use of these libraries by itself.

There is no limitation in the amount of definitions.

After the definition, the function is ready to be used. For this, the EXECUTE keyword (abbreviated: EXE) will instruct Psalm to execute the function, also taking into account the provided arguments.

When executed successfully, Psalm will return the result, and it will wait for the next instruction. It is possible to define and execute another function. The EXIT or QUIT keyword will actually return to the shell.

Communication interface

As may be clear from the above, the communication interface for Psalm is plain textual input and output. This obviously is available in every Unix-like operating system.

The tutorial demonstrated an interactive session. However, it would be convenient to put a series of Psalm keywords into a script, and let the script communicate with the Psalm binary.

To make this work, it is necessary to connect the output from the script to the input of Psalm, and at the same time connect the output from Psalm back to the input of the script.

At the GTK-server examples page, there are a few demonstration scripts showing how this works. The ‘stdin’ interface of the GTK-server is similar to the communication interface of Psalm.

Specific shell and interpreted language constructs enable setting up a bidirectional pipe. In BASH, ZSH, AWK and KSH it is possible to create a so-called ‘co-process’.

In KSH it works as follows (examples below taken from my GTK-server and BaCon site):

  psalm |&
  print -p <string>
  read -p <variable>

ZShell example:

  coproc psalm
  print -p <string>
  read -p <variable>

BASH example:

  coproc psalm
  echo "<string>" >&"${COPROC[1]}"
  read <variable> <&"${COPROC[0]}"

AWK example:

  psalm = "./psalm"
  print <string> |& psalm
  psalm |& getline <variabe>

The session ends when the script sends the ‘EXIT’ or ‘QUIT’ keyword to Psalm.

At the GTK-server website examples with other languages (newLisp, Perl, Expect, Prolog, Python, etc) can be found as well, but it is impossible to show all those implementations here.

However, what if the language used cannot setup such bidirectional pipe by itself? Fortunately, there are multiple solutions to accomplish such a pipe (the information below was shamelessly copied from Stackexchange).

A generic Linux solution could be the following:

  $ : | { ./psalm | ./script.lang; } > /dev/fd/0

In this example, the script and Psalm are connected in a bidirectional manner. They can now communicate with each other, and send and receive each others responses.

Note that this does not work in BSD or some other Unix type. On other Unix platforms, the following may work:

  $ cmd0 <&1 | cmd1 >&0

Lastly, there always is a possibility to use an external tool like ‘socat’ or ‘dpipe’ or ‘pipexec’:

  $ socat EXEC:./psalm EXEC:./script.lang
  $ dpipe ./psalm = ./script.lang
  $ pipexec [ A ./psalm ] [ B ./script.lang ] "{A:1>B:0}" "{B:1>A:0}"

Most of these tools are already available in your favorite repo. It should always be possible to let Psalm and a (shell-) script communicate with each other.

If your shell or language has access to the ‘mkfifo’ command from the GNU coreutils, then it is also possible to redirect the input and output of Psalm to a named pipe. To make this work, a separate pipe for input and a separate pipe for output must be created.

As soon as the named pipes “input” and “output” are created, Psalm can be started with redirection:

  $ mkfifo input output
  $ ./psalm <input >output &

The script or program now can read and write towards the named pipes in order to access Psalm. Note that Psalm will always try to keep its communication channels open. This will prevent unexpected closure of the channels when sending data towards the named pipes.

Advanced usage

The question may arise whether it is possible to access more elaborate libraries. For example, would it be possible to access a library like GTK and build a complete GUI, even though Psalm itself was not specifically designed for such task?

When building a GUI, it is required to obtain a result from a function and use it in the program. If, for example, a button is created, Psalm will return its ID and the shell script needs to know this ID, so it can, for example, add a caption to the button.

Using the aforementioned ways to setup bidirectional communication, this should be possible.

Also, a lot of libraries, like graphical toolkits, use so-called ‘callbacks’ to pass information back to the program. Of course, when using an external script, it is not possible for a library to pass such information to that script.

Fortunately, Psalm has a facility which allows querying the values sent to a callback function.

This leads to the last keyword in Psalm not yet discussed: CALLBACK (abbreviated: CB). Psalm has one internal generic function which allows 8 incoming arguments. The CB keyword can be used to obtain the value of one of these arguments.

As mentioned before, each function definition declared with the keyword DEF should also specify the individual types of the arguments. To specify a callback, the type definition provided should be ‘address’. This allows a callback either to a previous defined function, or to the Psalm internal function called ‘callback’.

It is time to look at a BASH script which sets up a very basic GTK window with a callback:

  # Communication function
  function comm
  {
      echo "${@}" >&"${COPROC[1]}"
      read answer <&"${COPROC[0]}"
  }

  # Start psalm
  coproc ./psalm

  # Send definitions first
  comm "DEF libgtk-3.so.0 void gtk_init int int"
  comm "DEF libgtk-3.so.0 long gtk_window_new int"
  comm "DEF libgtk-3.so.0 void gtk_main_iteration void"
  comm "DEF libgtk-3.so.0 void gtk_widget_show_all int"
  comm "DEF libgobject-2.0.so.0 long g_signal_connect_data int char* address void* void* int"

  # Design GUI
  comm "EXE gtk_init 0 0"
  comm "EXE gtk_window_new 0"; win=${answer}
  comm "EXE gtk_widget_show_all ${win}"

  # Connect signals
  comm "EXE g_signal_connect_data ${win} delete-event callback NULL NULL 0"

  # Main loop
  while [[ $cb -ne $win ]]
  do
      comm "EXE gtk_main_iteration"
      comm "CALLBACK 1"; cb=${answer}
  done
  comm "EXIT"

This should popup an empty GTK window. The main loop at the end of the script will capture each event. The ‘delete-event’ for the window was connected to the internal Psalm function ‘callback’ and the ID of the window is passed to it (by GTK) as a first argument.

The CALLBACK keyword then obtains this first argument and it is compared with the ID of the window which is already known. If it is the same, the code exits.

Again, note the names of the libraries. The definitions should refer to an existing library on the system. It is the responsibility of the script to use the correct library names.

This concludes the documentation on Psalm. It should now be possible to reach out to external libraries, and even create more sophisticated scripts creating a GUI.

Overview of Psalm keywords

DEFINE, DEF, define, def

Defines an external function. A space separated list should specify the following items:

  1. the name of the external library to import the function from

  2. the return type of that function: void, char, short, int, long, float, double, void*, char*

  3. the actual name of the external function

  4. the types of the arguments: void, char, short, int, long, float, double, void*, char*, address (currently Psalm supports up to 8 arguments)

EXECUTE, EXE, execute, exe

Executes a previously defined function

CALLBACK, CB, callback, cb

Queries the internal function of Psalm used by callbacks. The argument to this keyword indicates the position of the argument in the internal function.

EXIT, QUIT, exit, quit

Exits Psalm

Demonstration scripts

The table below is not complete but it should provide some ideas on how to use Psalm.


Language External tool Coprocess Named Pipes TCP/UDP
AWK demo.awk demo2.awk demo4.awk
BASH demo.bash demo2.bash demo4.bash
CSH demo.csh
Expect demo2.exp
FISH demo.fish demo3.fish
KSH demo.ksh demo2.ksh
LUA demo.lua
M4 demo3.m4
Make demo3.make
newLisp demo2.lsp demo4.lsp
Python demo2.py demo3.py
RC demo.rc
SH demo.sh demo3.sh
SLSH demo.sl demo3.sl
TCLSH demo.tclsh demo2.tclsh
YASH demo.yash demo3.yash
ZSH demo.zsh demo2.zsh

FAQ

COPYRIGHT

(c) Peter van Eerten, November 30, 2022. Updated for Markdown Wiki at March 9, 2024.

Return to BaCon