One expect script for all your automation

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:

  1. Get the script’s name as the first argument passed to it.
  2. 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.
  3. A “print_message” procedure for printing messages to either stdout or stderr and optionally sending them to Syslog.
  4. A “help” option for printing the script’s usage instructions.
  5. Obtain the script’s remaining command-line arguments.
  6. 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.
  7. List the quadruples of the command-pattern-timeout-retry count.
  8. Spawn a shell(/bin/sh); we do not want to use a pre-existing shell as it could have a variable PS1.
  9. Run each command using the specified pattern and timeout in a loop that iterates over the list of command-pattern-timeout-retry count quadruples.
  10. Retry the command for the designated number of times if the pattern cannot be discovered within the timeout period.
  11. 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 technekey@localstack-node-1" "password:" 10 3  "technekey" "technekey@.*$" 5 3 "ls -lrt /tmp" ".+@.+$" 5 1
Mon Feb  6 09:36:06 PM CST 2023 Info: Running command: ssh technekey@localstack-node-1 with pattern: password:, timeout: 10 and retry count: 2 remaining retries
Mon Feb  6 09:36:06 PM CST 2023 Info: Command's(ssh technekey@localstack-node-1) output consist: password: and within 10
Mon Feb  6 09:36:06 PM CST 2023 Info: Running command: technekey with pattern: technekey@.*$, timeout: 5 and retry count: 2 remaining retries
Mon Feb  6 09:36:06 PM CST 2023 Info: Command's(technekey) output consist: technekey@.*$ and within 5
Mon Feb  6 09:36:06 PM CST 2023 Info: Running command: ls -lrt /tmp with pattern: .+@.+$, 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: .+@.+$ and within 5
Consolidated Output:
$ ssh technekey@localstack-node-1
technekey@localstack-node-1's password:
technekey@localstack-node-1:~$ls -lrt /tmp
total 24
-rw-rw-r-- 1 technekey technekey    0 Feb  7 02:56 foo
technekey@localstack-node-1:~$

Example-3: Showing later commands using the same shell as previous commands

./runner.exp "ssh technekey@localstack-node-1" "password:" 10 3  "technekey" "technekey@.*$" 5 3 "export foo=1234" ".+@.+$" 5 1 "printenv foo" "technekey@.*$" 5 3
Mon Feb  6 09:38:44 PM CST 2023 Info: Running command: ssh technekey@localstack-node-1 with pattern: password:, timeout: 10 and retry count: 2 remaining retries
Mon Feb  6 09:38:44 PM CST 2023 Info: Command's(ssh technekey@localstack-node-1) output consist: password: and within 10
Mon Feb  6 09:38:44 PM CST 2023 Info: Running command: technekey with pattern: technekey@.*$, timeout: 5 and retry count: 2 remaining retries
Mon Feb  6 09:38:44 PM CST 2023 Info: Command's(technekey) output consist: technekey@.*$ and within 5
Mon Feb  6 09:38:44 PM CST 2023 Info: Running command: export foo=1234 with pattern: .+@.+$, 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: .+@.+$ and within 5
Mon Feb  6 09:38:44 PM CST 2023 Info: Running command: printenv foo with pattern: technekey@.*$, timeout: 5 and retry count: 2 remaining retries
Mon Feb  6 09:38:44 PM CST 2023 Info: Command's(printenv foo) output consist: technekey@.*$ and within 5
Consolidated Output:
$ ssh technekey@localstack-node-1
technekey@localstack-node-1's password:
technekey@localstack-node-1:~$export foo=1234
technekey@localstack-node-1:~$printenv foo
1234
technekey@localstack-node-1:~$

Notes:

  1. 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'
  1. You can hide the output of the script by setting print_enabled to 0 inside the script)
  2. You can also send the execution details to syslog by setting send_to_log to 1.
5 1 vote
Please, Rate this post
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x
Scroll to Top