If you are into automation, you know the extra trouble with dealing with extra prompts when running any commands. I work in an environment where I have to run commands on multiple custom CLI(vendor built, for example, cisco CLI), so my option here is limited to expect to spawn the custom CLI and run commands over it, exit from the custom CLI and get the execution back to the shell. This caused a lot of repeated code and mess to manage.
This is an Expect script written in Tcl (Tool Command Language). The only purpose of this script is to provide an abstraction over the TCL language and let the user call it simple from the shell or integrate it with their programs.
The script has the following functionality in the following order:
- Get the script’s name as the first argument passed to it.
- Set print_enabled, send_to_log, and exp_internal flags for debugging and logging purposes.
Set fallback_prompt and fallback_response for handling any unexpected situations. You can add more prompts/responses if they “might” occasionally appear during the execution. - A “print_message” procedure for printing messages to either stdout or stderr and optionally sending them to Syslog.
- A “help” option for printing the script’s usage instructions.
- Obtain the script’s remaining command-line arguments.
- Verify the timeout and retry count arguments are all integers and that the number of arguments is divisible by 4. Otherwise, invoke the help method and end the script.
- List the quadruples of the command-pattern-timeout-retry count.
- Spawn a shell(/bin/sh); we do not want to use a pre-existing shell as it could have a variable PS1.
- Run each command using the specified pattern and timeout in a loop that iterates over the list of command-pattern-timeout-retry count quadruples.
- Retry the command for the designated number of times if the pattern cannot be discovered within the timeout period.
- Each command’s output should be saved and printed with the execution time and date.
If this is not yet clear, I will try to make this clear in this post using examples. The script can be found on Github Page.
Script location:
git clone https://github.com/technekey/expect-runner.git
cd expect-runner/
ls -lrt
total 52
-rwxrwxr-x 1 technekey technekey 5140 Feb 6 22:06 runner.exp
-rw-rw-r-- 1 technekey technekey 6022 Feb 6 22:06 README.md
-rw-rw-r-- 1 technekey technekey 35149 Feb 6 22:06 LICENSE
Syntax:
./runner.exp <cmd1> <expected-output1> <timeout1> <retries1>
# The above code would run 'cmd1' command and would either wait till 'expected-output1' regex presence in `cmd1` output or `timeout1` seconds. If the `expected-output1` is not seen then `cmd1` will be retried for `retries1` times.
What if we want to run 2nd command…?
./runner.exp <cmd1> <expected-output1> <timeout1> <retries1> <cmd2> <expected-output2> <timeout2> <retries2> .........
# The above code would run 'cmd1' command and would either wait till 'expected-output1' regex presence in `cmd1` output or `timeout1` seconds. If the `expected-output1` is not seen then `cmd1` will be retried for `retries1` times.
#only if the `cmd1` execution sucessfully finished. (expected-output1 is seen within timeout*retries) then the same logic will be used for cmd2
Example-1:
./runner.exp date 2023 10 3 "uname" Linux 10 1
Mon Feb 6 09:30:06 PM CST 2023 Info: Running command: date with pattern: 2023, timeout: 10 and retry count: 2 remaining retries
Mon Feb 6 09:30:06 PM CST 2023 Info: Command's(date) output consist: 2023 and within 10
Mon Feb 6 09:30:06 PM CST 2023 Info: Running command: uname with pattern: Linux, timeout: 10 and retry count: 0 remaining retries
Mon Feb 6 09:30:06 PM CST 2023 Info: Command's(uname) output consist: Linux and within 10
Consolidated Output:
$ date
Mon Feb 6 09:30:06 PM CST 2023
$ uname
Linux
Example-2: SSH to remove and run multiple commands
./runner.exp "ssh [email protected]" "password:" 10 3 "technekey" "[email protected]*$" 5 3 "ls -lrt /tmp" "[email protected]+$" 5 1
Mon Feb 6 09:36:06 PM CST 2023 Info: Running command: ssh [email protected] with pattern: password:, timeout: 10 and retry count: 2 remaining retries
Mon Feb 6 09:36:06 PM CST 2023 Info: Command's(ssh [email protected]) output consist: password: and within 10
Mon Feb 6 09:36:06 PM CST 2023 Info: Running command: technekey with pattern: [email protected]*$, timeout: 5 and retry count: 2 remaining retries
Mon Feb 6 09:36:06 PM CST 2023 Info: Command's(technekey) output consist: [email protected]*$ and within 5
Mon Feb 6 09:36:06 PM CST 2023 Info: Running command: ls -lrt /tmp with pattern: [email protected]+$, timeout: 5 and retry count: 0 remaining retries
Mon Feb 6 09:36:06 PM CST 2023 Info: Command's(ls -lrt /tmp) output consist: [email protected]+$ and within 5
Consolidated Output:
$ ssh [email protected]
[email protected]'s password:
[email protected]:~$ls -lrt /tmp
total 24
-rw-rw-r-- 1 technekey technekey 0 Feb 7 02:56 foo
[email protected]:~$
Example-3: Showing later commands using the same shell as previous commands
./runner.exp "ssh [email protected]" "password:" 10 3 "technekey" "[email protected]*$" 5 3 "export foo=1234" "[email protected]+$" 5 1 "printenv foo" "[email protected]*$" 5 3
Mon Feb 6 09:38:44 PM CST 2023 Info: Running command: ssh [email protected] with pattern: password:, timeout: 10 and retry count: 2 remaining retries
Mon Feb 6 09:38:44 PM CST 2023 Info: Command's(ssh [email protected]) output consist: password: and within 10
Mon Feb 6 09:38:44 PM CST 2023 Info: Running command: technekey with pattern: [email protected]*$, timeout: 5 and retry count: 2 remaining retries
Mon Feb 6 09:38:44 PM CST 2023 Info: Command's(technekey) output consist: [email protected]*$ and within 5
Mon Feb 6 09:38:44 PM CST 2023 Info: Running command: export foo=1234 with pattern: [email protected]+$, timeout: 5 and retry count: 0 remaining retries
Mon Feb 6 09:38:44 PM CST 2023 Info: Command's(export foo=1234) output consist: [email protected]+$ and within 5
Mon Feb 6 09:38:44 PM CST 2023 Info: Running command: printenv foo with pattern: [email protected]*$, timeout: 5 and retry count: 2 remaining retries
Mon Feb 6 09:38:44 PM CST 2023 Info: Command's(printenv foo) output consist: [email protected]*$ and within 5
Consolidated Output:
$ ssh [email protected]
[email protected]'s password:
[email protected]:~$export foo=1234
[email protected]:~$printenv foo
1234
[email protected]:~$
Notes:
- To parse the output, you can pipe the expect script with the below command:
./runner.exp {. . . .} {. . . .}| perl -0777 -lne '/(?s)Consolidated [oO]utput:(.*)/;printf "%s\n",$1'
- You can hide the output of the script by setting print_enabled to 0 inside the script)
- You can also send the execution details to syslog by setting send_to_log to 1.