Expect - get variable from screen region based on row and column - scripting

I'm auto-interacting with an SSH session and an ERP program using Expect.
Rather than relying on a regular expression to capture a variable in my expect script would it be possible upon receiving a certain keystroke from a user to capture a screen region, say one field, into a variable in the code? Send the server some other commands and resend the field?
Say an order number is contained at 6, 12, 6, 18 (where 6 is the row and 12-18 are the columns) containing my 6 digit order number. I want to get that order number from row 6 columns 12 to 18 copy that into a variable. Then allow the user to interact some more (or expect a move into another menu), then re-send the order number in another menu.
So I guess my question is: Are the current screen's contents in one buffer? (not the whole session) Can you extract just a certain data element that would only exist at that row and column range on the screen?
Sample pseudocode:
#!/usr/bin/expect -f
set env(TERM) vt100
spawn ssh -Y user#domain
#... set user/pass and other vars...
#... send commands to log into ERP
#don't time out
set timeout -1
interact {
-reset $CTRLZ {exec kill -STOP [pid]}
$CTRLA {
exp_send "menu_address\ry\r"
}
$CTRLO {
#...acquire order number variable...
#...some code I don't understand yet...
exp_send "menu_exit_sequence\r"
exp_send "menu_address\r"
exp_send $ordernumvar
}
~~
}

Actually the term_expect example program that comes with Expect can do exactly this. It emulates a cursor-addressable terminal and allows you to test output at specific screen locations. In my ActiveTcl distribution it's in demos/Expect/term_expect.

No, you can't grab something off of the screen at a particular row/column. However, think about how the information got to the screen in the first place. If it's a tty-based application using curses it was output to stdout with special escape sequences that caused it to appear at that row/column. So, 'expect' those specific escape sequences to get what is at that position.

To debug output of my ERP I found I could use exp_internal to get output characters.
exp_internal -f file 0
The output of that gave me: (my number entered being 076338)
spawn id exp0 sent <0>^M
spawn id exp6 sent <0>^M
spawn id exp0 sent <7>^M
spawn id exp6 sent <7>^M
spawn id exp0 sent <6>^M
spawn id exp6 sent <6>^M
spawn id exp0 sent <3>^M
spawn id exp6 sent <3>^M
spawn id exp0 sent <3>^M
spawn id exp6 sent <3>^M
spawn id exp0 sent <8>^M
spawn id exp6 sent <8>^M
spawn id exp0 sent <\r>^M
spawn id exp6 sent <\r\n\u001b[1;14H>^M
So now I need to figure out the regex to get the field. I had this:
-nobuffer -re {^([a-zA-Z0-9]{1})?[0-9]{5}$} {
set ordernumber $interact_out(0,string)
}
but now I need to incorporate this:
^([a-zA-Z0-9]{1})?[0-9]{5}
With some regex that would represent this:
\r\n\u001b[1;14H
And then once I have that stored in the $ordernumber variable, I need to somehow isolate just the characters prior to the termination string and store those in a variable.

Related

Connect to connman service using the first field rather than the second

Given the following table
$ connmanctl services
*AO MyNetwork wifi_dc85de828967_68756773616d_managed_psk
OtherNET wifi_dc85de828967_38303944616e69656c73_managed_psk
AnotherOne wifi_dc85de828967_3257495245363836_managed_wep
FourthNetwork wifi_dc85de828967_4d7572706879_managed_wep
AnOpenNetwork wifi_dc85de828967_4d6568657272696e_managed_none
I'd like to be able to connect to a network, e.g. OtherNET, using the string OtherNET rather than the long wifi_dc85de828967_38303944616e69656c73_managed_psk, as I don't want to count the times I press Tab and/or check that the wifi_ line in the prompt corresponds to the intended network.
Is this possible with connman only? Or do I really have to write a wrapper myself?
The man page of connmanctl contains
services
Shows a list of all available services. This includes the
nearby wifi networks, the wired ethernet connections, blue‐
tooth devices, etc. An asterisk in front of the service
indicates that the service has been connected before.
and
connect service
Connects to the given service. Some services need a so-
called provisioning file in order to connect to them, see
connman-service.config(5).
which both don't say much about the format of the output or the use of the command.
Similarly, the wiki on Arch Linux refers to the last column as the second field beginning with wifi_.
Since nobody has answered yet, I have found some spare time to code the following wrapper, which basically does the following
keeps reading as long as the input is not exit;
when an input is provided which starts with connect, the following word is used to pattern-match one line (only the first matching line) from connman services; the last field of this line (the one which starts with wifi_) is forwarded to connmanctl connect;
any other non-empty input is forwarded to connmanctl as it is.
a certain number (to be passed through the variable NOINPUTS_BEFORE_EXIT whose default is 3) of empty inputs causes the script to exit.
The script is the following
#!/usr/bin/env bash
name=$(basename $0)
noinputs_before_exit=${NOINPUTS_BEFORE_EXIT:=3}
while [[ $cmd != 'exit' ]]; do
echo -n "$name " 1>&2
read cmd
if [[ -z "$cmd" ]]; then
(( --noinputs_before_exit == 0 )) && exit
else
noinputs_before_exit=$NOINPUTS_BEFORE_EXIT
if [[ $cmd =~ ^connect\ ]]; then
connmanctl connect $(connmanctl services | awk '/'"${cmd#* }"'/ { print $NF; exit }')
else
connmanctl $cmd
fi
fi
done
The script limitations are at least the following:
(less importantly, to me) I have no idea whether it meets any security requirements;
it does not allow Tab-completion;
(most importantly, to me) it does not use the readline library, so line editing impossible.

Get value of variable passed to spawn_id in Expect

I've managed to write a simple expect script to backup multiple router configs. This works fine but takes some time slow when the list of devices grows.
I tried to find an a way to spawn SSH sessions simultaneously and came across an article that goes:
# spawn all connections
foreach conn $allconnections {
spawn telnet $conn
lappend spawn_id_list $spawn_id
}
# run expect script for all connections individually
foreach id $spawn_id_list {
# this is important - for unknown (to me) reasons
set spawn_id $id
send "your_send_message"
expect "your_expect_pattern"
}
This seems to work but I am now faced with another challenge in creating a backup file named after the actual device. When $conn is passed to spawn_id and I create the backup file based on $id_configs.txt, I only get "expN" where N is a number which increments as each line on the device list is read. I've been experimenting on how to get the actual $id value back to use as filename but have yet to find a solution.
Appreciate any input from you guys. Thanks in advance!
% set pid [spawn telnet xx.xx.xxx.xx]
spawn telnet xx.xx.xxx.xx
29353
% set pid
29353
The spawn returns process id of whatsoever program being spawned. You can save that in a variable and use it further.

copy 3 newest files in remote server using expect the close the session

For starters, I'm a complete novice with expect scripts. I have written a few ssh scripts but I cant seem to figure out how to get the latest 3 log files after running a set of tests for a new build. My main goal is to find the latest log files and copy them to my local machine. PLEASE DON'T tell me that it's bad practice to hard code the login and password, I'm doing so because it's temporary to make the script work. My code currently...
#!/usr/bin/expect -f
set timeout 15
set prompt {\]\$ ?#}
spawn ssh -o "StrictHostKeyChecking no" "root#remote_ip"
expect {
"RSA key fingerprint" {send "yes\r"; exp_continue}
"assword:" {send "password\r"; exp_continue}
}
sleep 15
send -- "export DISPLAY=<display_ip>\r"
sleep 5
send "cd /path/to/test/\r"
sleep 5
set timeout -1
send "bash run.sh acceptance.test\r"
#Everything above all works. The tests has finished, about to cp log files
send "cd Log\r"
sleep 5
send -- "pwd\r"
sleep 5
set newestFile [send "ls -t | head -3"]
#tried [eval exec `ls -t | head -3`]
#No matter what I try, my code always gets stuck here. Either it wont close the session
#or ls: invalid option -- '|' or just nothing and it closes the session.
#usually never makes it beyond here :(
expect $prompt
sleep 5
puts $newestFile
sleep 5
send -- "exit\r"
sleep 5
set timeout 120
spawn rsync -azP root#remote_ip:'ls -t /logs/path/ | head -3' /local/path/
expect {
"fingerprint" {send "yes\r"; exp_continue};
"assword:" {send "password\r"; exp_continue};
}
Thanks in advance
When writing an expect script, you need to follow the pattern of expecting the remote side to write some output (e.g., a prompt) and then sending something to it in reply. The overall pattern is spawn, expect, send, expect, send, …, close, wait. If you don't expect from time to time, there are some buffers that fill up, which is probably what's happening to you.
Let's fix the section with the problems (though you should be expecting the prompt before this too):
send "cd Log\r"
expect -ex $prompt
send -- "pwd\r"
expect -ex $prompt
send "ls -t | head -3\r"
# Initialise a variable to hold the list of files produced
set newestFiles {}
# SKIP OVER THE LINE "TYPED IN" JUST ABOVE
expect \n
expect {
-re {^([^\r\n]*)\r\n} {
lappend newestFiles $expect_out(1,string)
exp_continue
}
-ex $prompt
}
# Prove what we've found for demonstration purposes
send_user "Found these files: \[[join $newestFiles ,]\]\n"
I've also made a few other corrections. In particular, send has no useful result itself, so we need an expect with a regular expression (use the -re flag) to pick out the filenames. I like to use the other form of the expect command for this, as that lets me match against several things at once. (I'm using the -ex option for exact matching with the prompts because that works better in my testing; you might need it, or might not.)
Also, make sure you use \r at the end of a line sent with send or the other side will be still be waiting “for you to press Return” which is what the \r simulates. And don't forget to use:
exp_internal 1
when debugging your code, as that tells you exactly what expect is up to.

Expect gets stuck sometimes during login

I have the following script. Sometimes, it runs fine and others it gets stuck. What could be wrong here?
#!/usr/bin/env expect
# set Variables
set timeout 60
set ipaddr [lindex $argv 0]
# start telnet connection
spawn telnet $ipaddr
match_max 100000
# Look for user prompt
expect "username:*"
send -- "admin\r"
expect "password:?"
# Send pass
send "thisisthepass\n"
# look for WWP prompt
expect ">"
send "sendthiscommand\r"
expect ">"
send "exit\r"
interact
The script runs fine till the end, but sometimes it gets stuck during login. This behavior is present even with the same IP: for example, it may run 1 out of 5 tries for the same IP.
I have tried adding some sleep between sending of the user and password, but it's still the same. I have also tried without expect, by sending directly the password string after the user one but still the same: sometimes the script runs fine but others it asks again for the password as if it's incorrect...
username: admin
password:
username:
Things I would do:
change send "thisisthepass\n" to send "thisisthepass\r"
include exp_internal 1 somewhere at the top of your script, and see what is going on when you have a failed attempt
exp_internal 1 will enable debugging with lots of good information on what is going on with expect's pattern matching. You can share it here and I'll be glad to take a look at it.
Are you sure the password prompt has an extra character after it (your ? in expect "password:?". Is it always there? Any chance different devices have slightly different password prompts?

Find UID from EUID

I am having a AIX 5.3 host to which we login and when needed uses pbrun tool to become root.Now the question is how do I find from command line as what user I have logged in to get this privileged/root user. If I am not wrong how do I find UID from my current EUID. Tried whoami and who am i both gives output as root.
"who am i" is coming from utmp. If utmp shows you as root then your pbrun tool must be changing it from what it was when you first logged in.
You could do:
ps l $$
which prints out a line with the PID and PPID. Take the PPID and do that again:
ps l <PPID>
The UID column is your numeric user id. If the PPID shows as 1, then pbrun did an exec rather than a folk / exec (which implies that it is a function or alias within your shell). In that case, you could revert to "last" which will show who logged in to which tty at what time.
======
Another idea. You can get the terminal the program is executing on via ps. This is called the controlling terminal. You can also get it via the "tty" command:
tty
/dev/pts/18
Now, feed that to "last" but remove the leading /dev/ part and take the first hit:
last pts/18 | head -1
myname pts/18 myhost.mydomain.com Nov 14 10:22 still logged in.
That is the last person to log into that particular terminal. Will that work?