Sooshell

The Sooshell shell leverages ZSH capabilities to provide new features to users. In Sooshell, you have access to both Soodar configuration commands and POSIX shell syntax structures simultaneously. Sooshell offers new capabilities to Soodar users and network administrators in both interactive usage and scripting, allowing you to increase your efficiency.

Interactive Usage

Sooshell presents unique features for interactive user to use, making working with the command-line interface easier, more efficient, and more enjoyable.

Prompt

The Sooshell prompt accurately indicates the status of the user session in terms of the configuration path it is in:

router1/c/interface/ge0#

For example, the above prompt shows that we have entered the path enable -> config -> interface : ge0

router1/c/i/g/link-params#

Or here we have entered enable -> config -> interface : ge0 -> link-params.

Auto-completion

Using the auto-completion feature, you can avoid writing full commands and save time. To complete a word, simply press the TAB key, and Sooshell will suggest possible words to complete the commands:

router1/config# p<TAB>

password    pbr     pbr-map     pseudowire

You can select one of the options by pressing the arrow keys ⬆️➡️⬅️⬇️ or the TAB key. Also, if Sooshell finds only one possible way to complete the command, it will automatically complete it without the need to press another key.

router1/config# ps<TAB>
router1/config# pseudowire

History

Sooshell stores a list of commands you’ve previously entered. The command history doesn’t disappear when you close the session and will be available in the next Sooshell execution. By pressing the up and down arrow keys ⬆️⬇️, you can view the history and re-execute previously entered commands.

Highlighting

Sooshell indicates whether the entered command is correct or not by coloring the initial word of the commands:

  • Red color: The command does not exist.

  • Green color: The command exists.

  • Yellow color: The command is ambiguous as typed so far and needs more of it to be typed.

  • Blue color: The command exists but was in the previous configuration path, and executing it will cause us to exit the current path.

Auto-suggestion

Sooshell simultaneously suggests a command in faded text in front of the user’s input based on the history of commands the user has entered and the input typed so far. The user can select that suggestion by pressing the right arrow key ➡️ or continue typing and ignore it.

Scripting

Sooshell’s scripting capability allows us to automate many tedious manual tasks. The syntax of Sooshell scripts is the same as POSIX shells in Unix-like operating systems such as Bash and Zsh. The programmability of Sooshell gives network administrators the ability to write and execute scripts for configuration automation and any desired routine. Scripts can also be executed interactively. Just paste the script code you’ve written previously into the terminal to run it. However, a more efficient method of running scripts is through saving and calling them by their names, which we’ll discuss next.

Syntax

In this part, we will show with numerous examples how to make the most of Sooshell’s scripting capabilities.

Variable Definition

Defining variables allows us to run our commands for different values by simply changing the value of that variable, without needing to change the command itself.

router1/config# # set variable IFNAME to ge0
router1/config# IFNAME=ge0
router1/config# # following command evaluates to 'interface ge0' and
router1/config# interface $IFNAME
router1/c/interface/ge0#

Deleting a variable:

router1/config# unset IFNAME

Command Execution Syntax

The above commands can be written in one line:

router1/config# IFNAME=ge0; interface $IFNAME && { no shut; quit; }
  • Commands are separated by the ; character.

  • The && series also acts like ;, with the difference that the second command is only executed if the first command was executed successfully.

  • The {} characters place one or more commands in a block.

In the above example, if the value we put in the IFNAME variable is not the name of an existing interface, the interface $IFNAME command fails and the commands placed in the block are not executed.

Output Processing

Consider the following command:

router1# show interface brief
Interface       Status  VRF             Addresses
---------       ------  ---             ---------
lo              up      default
ge0             up      default         192.168.1.1/24
ge1             up      default         192.168.2.1/24
wg0             down    default         10.10.49.1/16

We want to get only the information related to ge1. Sooshell allows us to use the output of one command as input for another command. The grep command can search for a pattern in its input. We just need to give the name of our desired interface along with the input of the previous command to grep:

router1# show interface brief | grep ge1
ge1             up      default         192.168.2.1/24

Another command we can use is the awk command. This command provides the user with more advanced search capabilities. For example, we want to find the IP address of the ge1 interface:

router1# show interface brief | awk 'NR > 1 && $1 == "ge1" { print $4 }'
192.168.2.1/24

It’s also possible to store the output of a command in a variable to use later, which we do using the VAR=$(COMMAND) syntax:

router1# ipv4=$(show interface brief | awk '$1 == "ge1" { print $4 }')

Conditional Operations

Sometimes we need certain commands to be executed only under specific conditions. In Sooshell, each condition is actually a command where the successful execution of the command means the condition is met. Let’s say we want to show all interfaces if there are less than 5 interfaces:

router# # get all rows except the first 2 heading rows
router# interfaces=$(show interface brief | tail +3)
router# # count the number of rows
router# count=$(wc -l <<< $interfaces)
router# # check if we have less than 5 rows
router# if [[ $count -lt 5 ]]
if> then
then>     # print all rows
then>     echo $interfaces
then> fi
ge0             up      default         192.168.30.251/24
ge1             up      default         200.1.2.1/24
ge2             down    default
ge3             up      default         1.1.1.1/24
  • In the first line, we use the tail command to store only from the third line onwards.

  • We get the number of rows using the wc -l command.

  • In the next line, we use the if structure to check the mentioned condition

  • If the condition is met, we print all rows

Note

The command arg1 arg2 <<< $variable syntax gives the value of a variable as an argument to a command.

The general syntax of the conditional control structure is as follows:

if command1
then
    command2
    command3
fi

If the first command is successful, both subsequent commands are executed. In the example we mentioned, the first command is [[ $count -lt 5 ]]. The string [[` is actually a command that is called with the arguments $count, -lt, and 5. Its second argument, -lt, means less than, and the command is successful and the condition is met if the first argument is less than the third.

Now suppose we want to print only the interface names if the number of interfaces is between 5 and 10, and if it’s more, print only their number:

router1# # get all rows except the first 2 heading rows
router1# interfaces=$(show interface brief | tail +3)
router1# # count the number of rows
router1# count=$(wc -l <<< $interfaces);
router1# # check if we have less than 5 rows
router1# if [[ $count -lt 5 ]]
if> then
then>     # print all rows
then>     echo $interfaces
then> elif [[ $count -lt 10 ]]
elif> then
elif-then>     # print first column of all rows
elif-then>     echo $interfaces | awk '{print $1}'
elif-then> else
else>     echo "number of interfaces: $count"
else> fi
number of interfaces: 16

The complete syntax of the conditional control structure is as follows:

if cond1
then
    command1-1
    command1-2
    ...
elif cond2
then
    command2-1
    command2-2
    ...
elif...
fi

Other conditional operators:

  • Is the length of string a greater than zero? [[ -n a ]]

  • Is the length of string a equal to zero? [[ -z a ]]

  • Are strings a and b equal? [[ a == b ]]

  • Are strings a and b not equal? [[ a != b ]]

  • Does string a match regex b? [[ a =~ b ]]

  • Is number a less than number b? [[ a -lt b ]]

  • Is number a greater than number b? [[ a -gt b ]]

  • Is number a less than or equal to number b? [[ a -le b ]]

  • Is number a greater than or equal to number b? [[ a -ge b ]]

  • Is number a equal to number b? [[ a -eq b ]]

  • Is number a not equal to number b? [[ a -ne b ]]

Helper Commands

When you are interacting with Sooshell or running a script, you are always in a configuration path.

router1/c/interface/ge0# node
interface

With this command, you can get the name of the node you are in. If that node has additional information, you can also obtain it:

router1/c/interface/ge0# node
interface
router1/c/interface/ge0# node -x
ge0

Also, we can use the following conditional command to check if we are in a specific node or not:

router1/c/interface/ge0# if [[ -in interface ]]; then echo "yes"; else echo "no"; fi
yes

For Loop

In Sooshell script, you can use a loop to perform repetitive tasks. The for loop in Sooshell has a simple syntax and can be used to perform a task for a range of numbers. For example, here we want to repeat the command for adding a static NAT for ports [1200,1400]:

router1/config# for port in {1200..1400}; do
for>     ip nat inside source static tcp "1.1.1.10" $port "200.1.2.2" $port
for> done

In this loop, the variable port is the loop index that can be used in the commands within the loop. Sometimes we want to execute commands for each element in an array. In that case, we act like the following example:

router1/config# # Define variables for VLAN configuration
router1/config# vlan_ids=(10 20 30)
router1/config# vlan_ifname="ge0"
router1/config# # Loop through VLAN configuration
router1/config# for idx in $vlan_ids; do
for>     vlan_name="$vlan_ifname.$idx"
for>     echo "Configuring VLAN $vlan_name ..."
for>     interface "$vlan_name"
for>     encapsulation dot1q $idx
for>     bridge-group 1 split-horizon group 1
for>     no shutdown
for>     exit
for> done
Configuring VLAN ge0.10 ...
Configuring VLAN ge0.20 ...
Configuring VLAN ge0.30 ...
n1/config# echo "VLAN configuration completed."
VLAN configuration completed.
n1/config#

In this example, the commands inside the loop are executed 3 times, where idx is equal to 10, 20, and 30.

While Loop

Another type of loop is while loops, which execute the commands inside them as long as their condition is met. In the following example, a script is written that reads the name of a protocol from the input and as long as the input is not empty, it denies that protocol and gets the next protocol from the input:

# read protocol names (one per line) and deny them in current ACL
[[ -in "ip-access-list" ]] || {
    echo Error: Must be in ACL node 1>&2
    return 1
}
while read PROTOCOL && [[ -n $PROTOCOL ]]
do
    deny $PROTOCOL any any
    echo denied protocol $PROTOCOL in ACL $(node -x)
done
permit any

The read command reads a line from the input and puts it in the variable whose name it received as an argument (here Protocol`). If it receives an EOF( Ctrl+D), the command fails and the loop condition is not met.

Functions

Sooshell provides the ability to define functions and call them for code reuse. Functions can receive arguments when called and even return a status code. The function’s status code acts like the status code of other commands, with code zero meaning success and other codes meaning failure. Functions can also act like output processing commands, meaning they can be called after the | character to take the output of the previous command as input. Here we define a simple function that fails if all its arguments are not equal:

check-equality()
{
    if [[ $# -eq 0 ]]; then
        return 2
    fi
    for arg in $@; do
        if [[ $arg -neq $1 ]]; then
            return 1
        fi
    done
    return 0
}

To define a function, we must use the name(){ … } syntax. The function definition is placed between two curly braces. At the beginning of the function, we check if any arguments have been given to the function. The term $# represents the number of arguments. We want the function to fail and return value 2 if no argument is given. Then we put a loop that traverses all arguments using the $@ expression and compares them with the first argument, i.e., $1 (in general, $n means the nth argument.) and if they are not equal, it returns 1 meaning error. Now we want to call the function we’ve written. The function call is in the form check-equality arg1 arg2 arg3 …. In the example below, in the first line, we use the && operator to check if the execution was successful or not, because if it failed, the next command should not be executed. In the next line, we also ensure the function fails, because the || operator with the meaning “or” causes Sooshell to ignore executing the next command if the first command was successful.

check-equality a a a && echo '1st' case passed
check-equality a a b || echo '2nd' case failed

Execution output:

1st case passed
2nd case failed

Examples:

A function to get the names of all interfaces:

interfaces()
{
    do sh int brief json | jq -r 'keys[]'
}

A function to bring up all VLAN interfaces:

vlanup()
{
    if [[ ! -in config ]]; then
        echo this command must be run in config node, not $(node) node 1>&2
        return 1
    fi

    for ifname in $(interfaces); do
        if [[ $ifname =~ .*\\..* ]]; then
            interface $ifname
            no shut
            quit
        fi
    done
}
# usage
valnup

A function that receives interface/IP pairs and sets the IPs on the interfaces:

addips()
{
    local retcode=0

    if [[ ! -in config ]]; then
        echo this command must be run in config node, not $(node) node 1>&2
        return 1
    fi

    for pair in "$@"
    do
        ifname=($(cut -d '=' -f '1' <<< $pair))
        ipaddr=($(cut -d '=' -f '2' <<< $pair))
        if do sh int json | jq -e -r ".$ifname" &> /dev/null
        then
            echo "On $ifname set IP $ipaddr"
            interface $ifname
            ip address $ipaddr
            no shut
            quit
        else
            echo "interface $ifname does not exist" 1>&2
            retcode=1
        fi
    done
    return $retcode
}
# usage
addips wg0="192.168.1.1/24" wg1="192.168.2.1/24" wg2="192.168.3.1/24"

Manual pages

Sooshell provides a manual page for its functionality and other system binaries. To access the manual page of a wanted section, use the man command followed by the section name. As an example, the following manual pages are available:

  • man sooshell : Sooshell manual. It provides a more detailed overview of Sooshell and its features.

  • man awk : AWK is a powerful text processing tool. The manual of AWK provides an overview of the AWK command and its parameters.

  • man jq : jq is a lightweight and flexible command-line JSON processor. The manual of jq provides an overview of the jq command and its parameters.

Use man commands completion feature to see all available manual pages.

Script management

Script management is a feature that allows users to create, store, and execute custom scripts on the Soodar router. This functionality leverages the router’s file management engine.

Scripts are stored in a dedicated filesystem within the router, specifically the “script:” filesystem. This organization ensures that scripts are kept separate from other system files and can be easily managed, accessed, and executed as needed.

CLI

The Sooshell CLI provides a set of commands to manage scripts. The following commands are available:

copy <system:startup-config|system:running-config> script: [force]

This command copies the startup configuration or the running configuration of the router to the script filesystem. It allows you to save the current configuration as a script, which can be useful for simple backup purposes or for creating templates for future configurations.

  • system:startup-config: Specifies the source to be the saved startup configuration.

  • system:running-config: Specifies the source to be the currently active running configuration.

  • script:: Specifies that the source is copied to script filesystem. This URI accepts an optional name parameter to specify the name of the script.

  • force: Optional parameter to overwrite an existing file. If not provided, the command will fail if the file already exists.

Note

script: URI is: script:[name].

Note

If a filename is not specified, the script will be saved as startup for the startup config and running for the running config.

Examples:

Copy the startup configuration to the script filesystem with default name:

router1# copy system:startup-config script:

Copy the running configuration to the script filesystem, overwriting any existing file named running:

router1# copy system:running-config script:running force
copy <sftp:|ftp:|http:|https:> script: [force]

This command copies a file from a remote server using various file transfer protocols (SFTP, FTP, HTTP, or HTTPS) to the router’s script filesystem. It allows you to download scripts or configuration files from external sources and store them in the router’s script directory.

  • sftp:: Specifies the source to be an SFTP server. the sftp: URI could contain the username, password and address of the remote computer with the path in remote. If URI is provided, all fields are shown to the user for confirmation; Otherwise, the user is asked for the required information

  • ftp:: specifies the source to be an FTP server. the ftp: URI could contain the username, password and address of the remote computer with the path in remote. If URI is not provided with username and password, the operation will fail.

  • http[s]:: specifies the source to be an HTTP[S] server.

  • script:: Specifies that the source is copied to the script filesystem. This URI accepts an optional name parameter to specify the name of the script.

  • force: Optional parameter to overwrite an existing file. If not provided, the command will fail if the file already exists.

Note

script: URI is: script:[name].

Note

sftp URI is: sftp://[user]:[password]@[host]:[path].

Note

ftp URI is: ftp://[user[:password]@]host[:port]/[url-path].

Note

If a filename is not specified in the destination, the system will use the original filename from the source.

Examples:

Copy a file from an SFTP server:

router1# sftp://user:password@192.168.1.100/scripts/my_script script:

Download a script from an HTTP server

router1# copy http://example.com/router_scripts/config_template script:

Retrieve a file from an FTP server and force overwrite any existing file

router1# ftp://anonymous@ftp.example.com/public/network_script script: force

Download a file from an HTTPS server and save it with a specific name:

router1# copy https://example.com/scripts/script script:new_script
copy script: <sftp:> [force]

This command copies a file from the router’s script filesystem to a remote server using the SFTP (Secure File Transfer Protocol) protocol. It allows you to upload scripts or configuration files from the router to an external SFTP server.

  • script:: Specifies the source to be the script filesystem.

  • sftp:: Specifies the destination to be an SFTP server. the sftp: URI could contain the username, password and address of the remote computer with the path in remote. If URI is provided, all fields are shown to the user for confirmation; Otherwise, the user is asked for the required information

  • force: Optional parameter to overwrite an existing file. If not provided, the command will fail if the file already exists.

Note

script: URI is: script:name.

Note

sftp URI is: sftp://[user]:[password]@[host]:[path].

Note

If a filename is not specified in the destination, the system will use the original filename from the source.

Examples:

Copy a script file to an SFTP server:

router1# copy script:my_script sftp://user:password@192.168.1.100/backups/

Copy a script to a specific directory on the SFTP server with a new name:

router1# copy script:router_config sftp://user:password@10.0.0.5/configs/router1_backup
copy script: script: [force]

This command copies a file from the router’s script filesystem to another file in the script filesystem. It allows you to create a copy of a script or configuration file within the router’s script directory.

  • script:: Specifies the source to be the script filesystem.

  • script:: Specifies the destination to be the script filesystem.

  • force: Optional parameter to overwrite an existing file. If not provided, the command will fail if the file already exists.

Note

script: URI is: script:name.

Note

Both source and destination file names should be provided.

delete script:

This command is used to delete files from the router’s script filesystem.

  • script:: Specifies the file to be deleted from the script filesystem.

Note

script: URI is: script:name.

dir script:

This command lists the files in the router’s script filesystem.

import script:

This command is used to execute scripts or apply configuration files from the router’s script filesystem. It functions similarly to the source command in POSIX shells, reading and executing the contents of the specified file in the current context of the router’s CLI.

  • script:: Specifies the script to be imported from the script filesystem.

Note

script: URI is: script:name.

Note

When importing a script, the router executes the commands in the script line by line, as if they were entered manually in the CLI.

Note

The execution context is the same as the current CLI session, meaning variables and environment settings are preserved and can be used or modified by the script.

Examples:

content of my_script:

Import the file:

router1# import script:my_script
hello world!
router1# echo $var1
hello world!
router1#
run script:

This command runs a script from the router’s script filesystem. It allows you to execute a script stored in the router’s script directory.

  • script:: Specifies the script to be executed from the script filesystem.

Note

script: URI is: script:name.

Note

Unlike the ‘import’ command, which executes commands in the current CLI context, ‘run’ typically executes the script in a separate environment and the current shell context is not affected.

Examples:

content of my_script:

Run the file:

router1# run script:my_script
hello world!
router1# echo $var1

router1#
check-syntax script:

This command checks the syntax of a script without executing it. It allows you to verify the correctness of a script before running it.

  • script:: Specifies the script to be checked from the script filesystem.

Note

script: URI is: script:name.

Note

The script is checked for syntax errors, but it is not checked for errors in commands spelling or their presence.

edit script:

This command opens a script in the router’s script filesystem for editing. It allows you to create or modify the contents of a script stored in the router’s script directory.

  • script:: Specifies the script to be edited from the script filesystem.

Note

script: URI is: script:name.

Note

The script is opened in the nano text editor.

more script:

This command displays the contents of a script stored in the router’s script filesystem. It allows you to view the contents of a script without executing it.

  • script:: Specifies the script to be displayed from the script filesystem.

Note

script: URI is: script:name.

rename script: script:

This command renames a file in the router’s script filesystem. It allows you to change the name of a script stored in the router’s script directory.

  • script:: Specifies the source file to be renamed in the script filesystem.

  • script:: Specifies the destination file name in the script filesystem.

Note

script: URI is: script:name.

Note

Both source and destination file names should be provided.