Using the Lua Shell
Table of contents
Introduction
The Lua shell is an alternative to the ioc shell for IOC startup scripts and interactive use. It can be invoked from an iocsh startup script or used as the startup program directly.
The shell is designed for compatibility with iocsh-style startup scripts. The global environment is set up so that name lookups fall through to EPICS environment variables and iocsh-registered functions. This means iocsh functions can be called directly without any special syntax:
luash> EPICS_VERSION_MAJOR
7
luash>
luash> epicsEnvShow("EPICS_VERSION_MAJOR")
EPICS_VERSION_MAJOR=7
Hash Comments
The directive #ENABLE_HASH_COMMENTS configures the shell to accept iocsh-style # comments. This only applies to lines where # is the first non-whitespace character – it does not affect the use of # for table length in normal Lua expressions.
luash> #ENABLE_HASH_COMMENTS
Accepting iocsh-style comments
luash>
luash> #print("This won't print")
luash> print(#"Check len")
9
Comments starting with #- are silent – they are not echoed when running scripts in file mode. This matches the iocsh convention:
#ENABLE_HASH_COMMENTS
# This comment will be echoed
#- This comment will be silent
Blank lines in file mode are also elided from the output.
info() Function
The info() function is available in all Lua states for discovering available functions and methods. It can be called on any library table or userdata object:
luash> info(epics)
.get(PV [, timeout | {timeout, count, string}])
.put(PV, value [, timeout | {timeout}])
.pv(PV) -- create PV proxy object
luash> pv = epics.pv("IOC:m1")
luash> info(pv)
.name -- PV name (property)
.FIELD -- read field value
.FIELD = value -- write field value
:get(field [, {timeout, count, string}])
:put(field, value [, {timeout}])
Calling info() with no arguments prints usage help. Calling it with a nil argument reports that the input is nil, which helps diagnose cases where a library hasn’t been loaded yet.
Shell Commands
All run/load commands share the same argument shape:
command "target" ["macros"] ["options"]
- target – a Lua code string (
luaRunString) or a script filename (luaRunFile,luaShell). - macros – a
"KEY=val,KEY2=val2"string (strings quoted, unquoted values interpreted as numbers/booleans), set as global variables in the target state. When called from Lua, a table ({P="dev1:"}) is also accepted. - options – a
"key=val"string of execution-control flags (same grammar as macros). Currently onlyasyncis defined, forluaRunFile. From Lua a table ({async=true}) is also accepted.
luaRunString
Execute a string of Lua code as a single chunk, printing the result of the final expression.
luaRunString "lua code" ["macros"]
luaRunString("print(P .. ' started')", {P="dev1:"})
Runs in a new state which is closed after the code completes.
Returns: nothing on success, error string on failure.
luaRunFile
Load and execute a Lua script file in a new state.
luaRunFile "file.lua" ["macros"] ["options"]
The entire file is compiled as a single chunk, so local variables work across lines. By default execution is synchronous (the command blocks until the script completes). With the option async=true the script runs in a background thread and the command returns immediately.
luaRunFile("driver.lua", {P="dev1:", PORT="SENSOR1"})
luaRunFile("tick.lua", nil, {async=true}) -- background thread
# iocsh: async as a keyword option
luaRunFile "tick.lua", "", "async=true"
The new state has access to all registered libraries and paths. If the script calls luaNameState, the state is kept alive after execution (see Named States below). Otherwise, the state is closed when the script finishes. async=true is commonly used for long-running scripts such as device polling loops or port-driver definitions.
Returns: nothing on success, error string on failure.
luaShell
Run a Lua script in the calling shell’s state, line-by-line (interactive/REPL style).
luaShell "file.lua" ["macros"]
The script shares variables, loaded modules, and function definitions with the shell session. If no filename is given, the shell reads from standard input with a prompt set by LUASH_PS1.
luaShell("config.lua", {P="dev1:", PORT="serial1", ADDR=0})
The file is located by searching LUA_SCRIPT_PATH, paths registered via luaAddPath/luaAddModule, and the current directory.
File mode: each statement is executed as it is read, with lines echoed and output interleaved (matching iocsh behavior). Multi-line constructs (function definitions, if blocks, loops) are accumulated until the statement is complete. Return values from expressions are printed automatically. Because each line is a separate chunk, local variables do not persist between lines – use global variables, or use luaRunFile which compiles the whole file as one chunk.
Include directive: the < command includes another script at the current point:
< other_script.lua
Exit: a line containing only exit ends the current script and returns to the caller.
Returns: nothing on success, error string on failure.
Command Comparison
| Command | Runs in | Execution | Line echoing |
|---|---|---|---|
luaShell "file" | Calling shell | Synchronous, line-by-line | Yes |
< file | Calling shell | Synchronous, line-by-line | Yes |
luaRunFile "file" | New state | Synchronous, whole file | No |
luaRunFile "file","","async=true" | New state, background thread | Whole file | No |
luaRunString "code" | New state | Synchronous, single chunk | No |
Commands that run in the calling shell share variables, loaded modules, and function definitions with the shell session. Commands that run in a new state start fresh – pass configuration via macros.
Deprecated command names
The following older names still work but are deprecated; each emits a one-time warning on first use naming its replacement. Migrate to the canonical names above.
| Deprecated | Use instead |
|---|---|
luash / luashLoad | luaShell |
luaLoadFile | luaRunFile |
luaSpawn | luaRunFile with option async=true |
luaCmd | luaRunString |
luaRegisterState | luaNameState |
Named States
By default, a Lua state created by luaRunFile is closed when the script finishes. A named state is one that has been registered with a name, making it persistent and referenceable from other parts of the IOC.
Creating a Named State
Call luaNameState from within a script to register the current state under a name:
-- Inside a script loaded via luaRunFile
luaNameState("mydevice")
The state will not be closed when the script finishes. All global variables and function definitions in the state remain available.
Referencing Named States
Named states are referenced using the @name syntax in luascript record CODE fields and DTYP device support INP/OUT fields. When the name after @ does not resolve to a file on disk, it is looked up as a named state:
-- In a script loaded with luaRunFile:
luaNameState(PORT)
function read_temperature()
return client:write("MEAS:TEMP?"):read("%f")
end
-- Record references the named state and function:
db.record("ai", P .. "temperature") {
DTYP = "lua",
INP = "@" .. PORT .. " read_temperature()",
SCAN = "1 second",
}
Typical Pattern
The most common pattern is a single file loaded via luaRunFile that:
- Registers its state under a unique name (typically a port name or prefix)
- Creates records using
db.record - Defines the callback functions those records reference
-- device.lua: loaded before iocInit via luaRunFile
local db = require("db")
luaNameState(PORT)
-- ... set up clients, define functions ...
db.record("ai", P .. "reading") {
DTYP = "lua",
INP = "@" .. PORT .. " read_value()",
}
function read_value()
-- this function runs in the named state
return get_reading()
end
-- st.lua: startup script
luaRunFile("device.lua", {P="dev1:", PORT="DEV1"})
luaRunFile("device.lua", {P="dev2:", PORT="DEV2"})
iocInit()
Each call to luaRunFile creates a separate named state, so the two instances do not interfere with each other.
luaAddPath / luaAddModule
The luaAddPath and luaAddModule commands register directories for both require() (Lua’s module loader) and script file resolution (used by luaRunFile, luascript @file, and DTYP @file).
luaAddPath
Add a directory to the library and script search paths.
luaAddPath "directory"
The directory is used for:
require(): appendsdir/?.luaanddir/?/init.luatopackage.path, anddir/?.so(ordir/?.dllon Windows) topackage.cpath- Script loading: the directory is searched after
LUA_SCRIPT_PATHdirectories but before the current directory
luaAddPath("/usr/local/share/lua/5.4")
local mylib = require("mylib") -- finds /usr/local/share/lua/5.4/mylib.lua
Duplicate paths are silently ignored. When called from Lua, the path takes effect immediately in the calling state. When called from iocsh, the path takes effect for all future Lua states.
luaAddModule
Register an EPICS module’s library and script directories.
luaAddModule "module_top"
Reads the EPICS_HOST_ARCH environment variable and adds two directories via luaAddPath:
module_top/lib/<arch>/– for libraries installed viaLIB_INSTALLSmodule_top/bin/<arch>/– for scripts installed viaSCRIPTSorBIN_INSTALLS
-- From a Lua startup script:
luaAddModule("../..")
luaAddModule(MYMODULE)
local bs = require("bytestream")
# From iocsh:
< envPaths
luaAddModule("$(LUA)")
luaAddModule("$(MYMODULE)")
If EPICS_HOST_ARCH is not set, luaAddModule prints a warning and does nothing.
Path Search Order
Paths are searched in the order they are registered:
luaAddPath("/first")
luaAddPath("/second")
-- package.path: /first/?.lua;...;/second/?.lua;...;<defaults>
-- script search: LUA_SCRIPT_PATH dirs, /first/, /second/, .