| |

klish tutorial

note: amidst writing this tutorial, i realized that klish 3 documentation in itself is quite self-sufficient. nevertheless, i am publishing this hoping it serves as either a quickstart guide or notes for me for future references.

klish is a framework for building command line interfaces (clis). it lets you define the structure of your cli using simple xml files, takes care of parsing user input, and gives you some very sick tab autocompletion out of the box (along with escape characters, history, etc).

this is not a comprehensive tutorial. it’s more of a quickstart to get you up and running with klish. for deeper details, refer to the official documentation.

the main reason this post exists is because there’s surprisingly little about klish on the internet, and i wanted to change that.

i’ve been behind on writing this, and there’s a reason for it. initially, i was working with klish 2.2 (the version available in the openwrt package manager), while the latest release is klish 3.

from an architectural standpoint, a lot has changed. i’m now back in the learning phase myself and need to understand the newer version before continuing.


one of the first things to do if you are setting up klish3 on your openwrt vm then is to install less (gnu less), because the busy box less that comes with openwrt does not support the -e option that klish uses when you have the pager enabled.

opkg update
opkg install less

i have took the trouble to build the latest klish from the source code for x86_64 architecture. you will be required to downgrade libxml2 to the version it requires. also you will need to install faux. i have all the packages here.

setting up

after installation, you will have the klish binary at /usr/bin/klish. you still need to set up a few things before you can run it.

first, create a directory to hold your klish configuration files.

mkdir -p /etc/klish/xml
cd /etc/klish
cat > klish.conf << EOF
paste the contents of klish.conf from https://github.com/dhrm1k/klish/blob/master/klish.conf here.
EOF


cat > klishd.conf << EOF
paste the contents of klishd.conf from https://github.com/dhrm1k/klish/blob/master/klishd.conf here.
EOF

make sure to adjust the paths in these configuration files. in particular, for klishd.conf, set the DB.libxmls.XMLPath parameter to point to your configuation directory (i.e. /etc/klish here).

DB.libxml2.XMLPath=/etc/klish/xml/

there's a really good example configuration in the klish soruce code repository. you can copy the xml file from there to your configuration directory to get started.

cat > /etc/klish/xml/example.xml << EOF
paste the contents from example.xml at https://github.com/dhrm1k/klish/blob/master/examples/simple/example.xml here.

now you need to run the klish daemon with klishd to load the configuration. you can run it with the foreground option -foreground for testing purposes.


the commands

this was the easy part. the main reason of writing this tutorial was to understand how to implement the command handlers. the basic xml for basic commands is pretty straightforward (but it does get a little dirty on a little user experience edge cases, more on that later).

ptype

when starting out, i myself was wondering, if ptype is some special type defined by klish or limited to klish or is it something more general. turns out, ptype is just short for parameter type. it defines the type of argument that a command takes.

for example, in the example.xml file, you can see the following:

<PTYPE name="STRING">
	<ACTION sym="STRING@klish"/>
</PTYPE>

this defines a parameter type named STRING, which takes a string argument.

klish comes with a set of built-in ptypes, which you can find in the official documentation. i am also adding it here for reference:

  • STRING: any string
  • INT: integer
  • UINT: unsigned integer

you can also define your own ptypes. let's say you want to define a ptype for an ip address. now why would you want to do that? let's say you want to implement a command that takes an ip address as an argument (ping command, anyone?). you can define a ptype for an ip address as follows:

<PTYPE name="IP_ADDR">
	<ACTION sym="REGEX@klish">
		^([0-9]{1,3}\.){3}[0-9]{1,3}$
	</ACTION>
</PTYPE>

and you will be using it as follows:

<COMMAND name="ping" help="Ping an IP address">
	<PARAM name="ip" ptype="IP_ADDR" help="IP address"/>
	<ACTION sym="script">ping -c 4 ${KLISH_PARAM_ip}</ACTION>
</COMMAND>

actions

you have seen the action tag in the above examples. action defines what happens when a command ois executed when sym is set to script. above, you see, sym is set to REGEX@klish.

this means that klish will use the regex defined in the action tag to validate the input for that ptype. REGEX@klish is a built-in action that is part of the default klish plugin.

in the default example.xml file, you can see that prompt uses an action with sym set to PROMPT@klish. this is another built-in action that is used to customize the prompt/appearance.

<PROMPT name="prompt">
	<ACTION sym="prompt">%u@%h&gt; </ACTION>
</PROMPT>

to even give an example of how to use the parameters in the action script, here's a greet command that takes name and age as parameters and prints a greeting message.

<COMMAND name="greet" help="Greet user">	
	<PARAM name="name" ptype="STRING" help="Name"/>
	<PARAM name="age" ptype="INT" help="Age"/>
	<ACTION sym="script">
		echo "hii, $KLISH_PARAM_name! you are $KLISH_PARAM_age years old"
	</ACTION>
</COMMAND>

command hierarchy

commands can be organized in a hierarch to give better autocomplete experience. basic commands have already been implemented above. i'd like to give a example to set up hierarchial commands. for example,

<COMMAND name="show" help="Show information">
	<COMMAND name="interface" help="Show interface information">
		<COMMAND name="eth0" help="Show eth0 details">
			<ACTION sym="script">ip addr show eth0</ACTION>
		</COMMAND>
		<COMMAND name="wlan0" help="Show wlan0 details">
			<ACTION sym="script">ip addr show wlan0</ACTION>
		</COMMAND>
	</COMMAND>
</COMMAND>
root@OpenWrt> show interface
     eth0  wlan0
root@OpenWrt> show interface eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc...

views

a view is a context or mode that contains its own set of commands, prompts, and behavior. users navigate between views to access different functionality.

an example of defining multiple views is as follows:

<VIEW name="main">
	<PROMPT name="prompt">
		<ACTION sym="prompt">user&gt; </ACTION>
	</PROMPT>
	
	<COMMAND name="exit" help="Exit CLI">
		<ACTION sym="nav">pop</ACTION>
	</COMMAND>
	
	<COMMAND name="show" help="Show information">
		<COMMAND name="version" help="Show version">
			<ACTION sym="printl">Version 1.0.0</ACTION>
		</COMMAND>
	</COMMAND>
	
	<!-- Command to enter config view -->
	<COMMAND name="configure" help="Enter configuration mode">
		<ACTION sym="nav">push config</ACTION>
	</COMMAND>
</VIEW>

<!-- Config VIEW: Configuration mode -->
<VIEW name="config">
	<PROMPT name="prompt">
		<ACTION sym="prompt">config&gt; </ACTION>
	</PROMPT>
	
	<COMMAND name="exit" help="Exit to main view">
		<ACTION sym="nav">pop</ACTION>
	</COMMAND>
	
	<COMMAND name="hostname" help="Set hostname">
		<PARAM name="name" ptype="STRING" help="Hostname"/>
		<ACTION sym="script">
			echo "Setting hostname to $KLISH_PARAM_name"
			# hostname $KLISH_PARAM_name
		</ACTION>
	</COMMAND>
	
	<COMMAND name="ip" help="IP configuration">
		<PARAM name="address" ptype="STRING" help="IP address"/>
		<ACTION sym="script">
			echo "Setting IP to $KLISH_PARAM_address"
		</ACTION>
	</COMMAND>
</VIEW>

this was intuitive and easy to implement.

the thing that's been taking my mind and time is implementing a password protected view. i did achieve that in klish 2.2 by a custom action script that prompted for password input with the read -s flag to hide input. however, in klish 3, i am not able to get that working yet. i will experiment more and update this tutorial for that part when i have something working.

well, it seems i will have to write a custom plugin to test this.

voila, i was an inch away from giving up when i found a way. after nothing worked, i thought to write a custom auth plugin in klish, but installing gcc made my vm run out of space, and well it's 3:55 am. though, i am not exhaused, i want to think about other pretty things.

	<COMMAND name="enable" help="Enter privileged mode (password required)">
		<ACTION sym="script" in="tty" out="tty"><![CDATA[
#!/bin/sh

# Read password with hidden input using read -s
read -s -p "Password: " password
echo ""

# Check password
if [ "$password" = "admin123" ]; then
    echo "Access granted"
    exit 0
else
    echo "Access denied"
    exit 1
fi
]]></ACTION>
		<ACTION sym="nav">push enable</ACTION>
	</COMMAND>

this worked. to answer why,

<ACTION sym="script" in="tty" out="tty">
    read -s -p "Password: " password
</ACTION>

this tells klish to create a proper PTY and set KTP_STATUS_NEED_STDIN and KTP_STATUS_INTERACTIVE flags. even then, there's still a conflict: the client-side tinyrl holds the terminal in raw mode, and while it does call tinyrl_raw_mode() for interactive commands (line 570-571 in klish.c).

the version of klish on my machine is klish - 3.2.0-1.


this is a ongoing tutorial. i will add more here. until then, if any questions, feel free to mail me. my email address is in the contact section of this webpage.