"command not found" errors in expect script executed by shell script - linux

I am trying to implement "shell script calling expect script" so that it does not prompt the user for entering ssh password every time. I started with Using a variable's value as password for scp, ssh etc. instead of prompting for user input every time and understood that I should have a .sh file and a .exp file. I have expect installed in my system (running expect -v shows expect version 5.43.0).
In my upload-to-server.sh file I have
cd $SOURCE_PATH/shell
./password.exp $DESTINATION_PATH $SSH_CREDENTIALS $PROJECT_INSTALLATION_PATH $PASSWORD
And in my password.exp file I have
#!/usr/bin/expect -f
set DESTINATION_PATH [lindex $argv 0];
set SSH_CREDENTIALS [lindex $argv 1];
set PROJECT_INSTALLATION_PATH [lindex $argv 2];
set PASSWORD [lindex $argv 3];
spawn scp $DESTINATION_PATH/exam.tar $SSH_CREDENTIALS':/'$PROJECT_INSTALLATION_PATH
expect "password:"
send $PASSWORD"\n";
interact
On running the upload-to-server.sh file I get the following error -
./password.exp: line 9: spawn: command not found
couldn't read file "password:": no such file or directory
./password.exp: line 11: send: command not found
./password.exp: line 12: interact: command not found
I arrived at the above code (in the exp file) from multiple sources (without understanding much basics). In one source the code is like this
#!/usr/local/bin/expect
spawn sftp -b cmdFile user#yourserver.com
expect "password:"
send "shhh!\n";
interact
Whereas in another source like this
#!/usr/local/bin/expect -f
set TESTCASE_HOME [lindex $argv 0];
set TESTCASE_LIST [lindex $argv 1];
set PASSWORD [lindex $argv 3];
set timeout 200
spawn $TESTCASE_HOME/dobrt -p $TESTCASE_HOME/$TESTCASE_LIST
expect "*?assword:*" {send -- "$PASSWORD\r";}
expect eof
There are some differences there -
there is an extra -f in the #!/usr/local/bin/expect line
expect "?assword:" {send -- "$PASSWORD\r";} is different from expect "password:"
send "shhh!\n";
interact replaced with expect eof.
This is my first expect script so don't have much idea what to code. Any pointers?
Thanks,
Sandeepan

Don't do any of this! You should use public key authentication as the comment above suggests. The way you're going leaves passwords in the clear and is fragile.
Public key authentication is way easier to setup, for example: setup instructions

Are you sure you're doing
./script.exp
And not
. ./script.exp
?? The latter would have the shell trying to interpret the expect program.
Fully agree that ssh keys are the correct solution though.

Related

How to turn off stderr in Expect script or stop "Connection to ... closed" message after exiting?

So I have been trying to learn bash scripting with expect and I'm running into an annoying problem where even after I turn off output by setting log_user to 0, the logout message for when I exit the ssh server is still printed as "logout
Connection to blah.blah.blah closed". Going the hackish way and issuing the exit command inside the script to expect is undesirable, and neglects to print a new line in the terminal, leaving an unsightly terminal prompt behind. How do I suppress or turn off this connection closed message in my expect script? Here's a template of the code for my expect script:
#!/usr/bin/expect -f
#-f flag tells expect to read commands from file
# Set Password Prompt and Password
set PASSP "password:"
set PASSW "your_password_here\n"
# Set SFTP Prompt
set SFTP "sftp> "
# Set Remote Prompt and Project Directories
# The GL server is super weird, so make sure RPROMPT ends in the
# name of the project compilation directory and PROJDIR is the
# relative path (from home but without the tilde) of the main
# project directory.
set RPROMPT "projdir_here]"
set PROJDIR "path_to_projdir_here"
# Set SSH Address
set ADDRESS your_username_here#your_address_here
# Annoying long output turned off
log_user 0
send_user "Logging in...\n"
spawn sftp $ADDRESS
expect $PASSP
send $PASSW
expect $SFTP
send_user "Uploading necessary files...\n"
send "cd $PROJDIR\n"
expect $SFTP
send "put *.cpp .\n"
expect $SFTP
send "put *.h .\n"
expect $SFTP
send "put Makefile .\n"
expect $SFTP
send "exit\n"
spawn ssh $ADDRESS
expect $PASSP
send $PASSW
expect "]"
send "cd $PROJDIR\n"
expect $RPROMPT
send_user "Cleaning remote directory...\n"
send "make clean\n"
# Output turned on temporarily so compiler feedback can be determined
expect $RPROMPT
log_user 1
send_user "Compiling on server...\n"
send "make\n"
# Turn off output and submit (this assumes that your makefile has a 'submit'
# target with the command to submit your file there)
expect $RPROMPT
log_user 0
send_user "\nSubmitting Requisite Files...\n"
send "make submit\n"
expect $RPROMPT
send_user "Quitting...\n"
# I can't figure out how to turn off stderr in expect so that an annoying 'connection closed'
# message is printed even the log_user has been set to 0
send "logout\n"
# Transfer control to user
interact
You can see that at the end of the program, before I use the interact command, that the offending message is printed after "logout\n" is sent and the connection to the server is closed. This happens despite log_user being set to 0 above. How on earth do I turn off stderr output inside my script? I've even looked at the manual pages for expect at expect man page but there didn't seem to be much useful there. I don't understand how to turn off stderr inside an expect script so that this message isn't printed, help would be much appreciated! I really just want the script to be quiet when I'm logging out. Because it's pretty obvious that I issued a logout command, I'm not sure why this output goes to stderr. Is there a command in ssh that won't output a "Connection to...closed" message when the user exits? It would be better if I could just pipe stderr to oblivion or suppress it somehow. The normal ways don't seem to work because this is an expect script.
It is just the incorrect use of log_user. It should be used as,
log_user 0
send "logout\r"
expect eof
log_user 1
Note that I am expecting for eof pattern after sending logout\r, because it will lead to the closure of the connection. Always use \r in place of \n while using the send command.
Read here to know more about the log_user to suppress the output.
Instead of having a prompt to defined as a static string, it can be generalized as,
# We escaped the `$` symbol with backslash to match literal '$'
# The last dollar sign represents line-end.
set prompt "#|>|%|\\\$ $";
While the expect is used, we have to accompany with -re flag to specify that as a regular expression.
expect -re $prompt
SshDemo.exp
#!/usr/bin/expect
set prompt "#|>|\\\$ $"
spawn ssh dinesh#myserver
expect {
"(yes/no)" { send "yes\r";exp_continue}
"password"
}
send "welcome123\r"
expect -re $prompt
# Simply executing 'ls -l' command...
send "ls -l\r"
expect -re $prompt
log_user 0
send "logout\r"
expect eof
log_user 1
Here's my new, improved script based on the suggestions above. I also decided to use scp instead of sftp because it's much more concise and I'm only uploading files.
#!/usr/bin/expect -f
#-f flag tells expect to read commands from file
# Set Password Prompt and Password
set PASSP "password: "
set PASSW "your_password_here"
# Set Remote Prompt and Project Directories
# The ssh server is super weird, so make sure PROJDIR is the
# directory name of the main project directory and PATH is
# the relative path to the project directory from home (with the tilde)
set PROJDIR "your_projdir_name_here"
set PATH "your_projdir_path_here"
# Set SSH Address
set ADDRESS your_username_here#source_hostname_here
# Turn off annoying output
log_user 0
send_user "Logging in and uploading necessary files...\n"
# upload files via scp, (shows upload prompt for user, concise)
spawn bash -c "scp *.cpp *.h Makefile $ADDRESS:$PATH"
expect $PASSP
send "$PASSW\r"
interact
spawn ssh $ADDRESS
expect $PASSP
send "$PASSW\r"
expect "]"
send "cd $PATH\r"
expect "$PROJDIR]"
send_user "\nCleaning remote directory...\n"
send "make clean\r"
# Output turned on temporarily so compiler feedback can be determined
expect "$PROJDIR]"
log_user 1
send_user "Compiling on server...\n\n"
send "make\r"
# Turn off output and submit (this assumes that your makefile has a 'submit'
# target with the command to submit your file there)
expect "$PROJDIR]"
log_user 0
send_user "\n\nSubmitting Requisite Files...\n"
send "make submit\r"
expect "$PROJDIR]"
send_user "Quitting...\n"
send "logout\r"
expect eof
# Exit
exit

output from expect is not complete

I have this script in order to make rsync with another server, the propossal is to get arround 1600 files, this script as two variables, date and hour, so, I send these two values with shell script then I pass the values.
Expect script:
#!/usr/bin/expect
set DIR_DATE [lrange $argv 0 0]
# Define date variable.
set DT_HOUR [lrange $argv 1 1]
# Define hour variable.
spawn rsync -avzb -e ssh user#IPSERVER:/SOURCE_DIR/$DIR_DATE/A$DIR_DATE.$DT_HOUR*.txt /TARGET_DIR/$DIR_DATE/
expect "password:"
send "PASSWORD\r"
#interact
expect eof
And the shell script is:
#!/bin/bash
***HERE is the code to get date and hour variable: DT_DATE,DT_HOUR; then, next step is run the expect script***
${DIR_BIN}/rsync_expect.sh ${DT_DATE} ${DT_HOUR}
You can note, in the expect script, I comment "interactive" word, because:
If it is not comment and run manually the script, it runs very well, it get 1600 files.
If it is commented, and run through the shell script, it only get arround 50 files and that's off.
So, any sugestion in order to get all files using shell scripts?
You're probably timing out: 50 files is probably what you can transfer in (default) 10 seconds. Add this before you spawn: set timeout -1
".sh" is a bad file extension for an expect script: use ".exp"
The lrange command returns a list. Use lassign $argv DIR_DATE DT_HOUR

Tcl expects output from tclsh to shell script

I'm searching for last few days for solution to my problem so finally I came here.
I got to write a Tcl script that should be opened from a Linux/Debian system. The problem is, that the script should log into a Cisco router and then go into the special tclsh mode, where it should get the SNMP value which represents txload. After a moment I get the value in tclsh mode (which supports SNMP), but I can't get it back to my script. It still exists only in router tclsh mode. My script should change some other values depending on the value I already have in tclsh, but tclsh is not needed anymore.
So here's my question: How can I get the $ifnum value from tclsh but I could still use in that script for some loops, because I need to make some loops where the $ifnum will be the condition?
As you can see, the script is not finished yet, but as I said, I'm stuck here and trying to figure it out:
#!/usr/bin/expect -f
set gateway [lindex $argv 0]
set serial [lindex $argv 1]
set timeout 5
spawn telnet $gateway
expect "Password:"
send "cisco\r"
expect "*>"
send "en\r"
expect "Password:"
send "cisco\r"
expect "*#"
send "set ifnumstr \[snmp_getone odczyt 1.3.6.1.4.1.9.2.2.1.1.24.6\]\r"
expect "*}"
send "regexp \{val='(\[0-9\]*)'\} \$ifnumstr \{\} ifnum \r"
expect "*#"
send "puts \$ifnum \r"
expect "*#"
I'd make it print out the thing you're looking for between markers, and then use expect in its RE matching mode to grab the value.
send "puts \"if\\>\$ifnum<if\"\r"
expect -re {if>(.*?)<if}
# Save the value in a variable in the *local* script, not the remote one
set ifnum $expect_out(1,string)

Passing Variable with Special characters as password breaks bash script. How do I Sanitize special characters in bash?

I am looking for a solution to sanitizing variables in my bash password change script.
The script below is working, however I have found that some "Special Characters" will break my script. I am not controlling at the moment what Characters are being passed through. I am either looking to sanitize the variables before passing them through, or pushing the variables as a whole untouched. I have tried using '${PASS}' in place of "${PASS}" however the script would not complete when this was the case.
I would appreciate any recommendations anyone could offer. I have tried searching for the answer to this question before posting but didn't find anything relative so i am sorry if this has been answered elsewhere.
#!/bin/bash
# Two variables are passed, Username and new Password.
USERNAME=$1
PASS=$2
expect << EOF
spawn passwd ${USERNAME}
expect "Enter new UNIX password:"
send "${PASS}\r"
expect "Retype new UNIX password:"
send "${PASS}\r"
expect eof;
EOF
expect << EOF
spawn htdigest /.passwd "Authenticated Users" ${USERNAME}
expect "New password:"
send "${PASS}\r"
expect "Re-type new password:"
send "${PASS}\r"
expect eof;
EOF
expect << EOF
spawn htpasswd /squiddb ${USERNAME}
expect "New password:"
send "${PASS}\r"
expect "Re-type new password:"
send "${PASS}\r"
expect eof;
EOF
Thank you in advance!
Send the username and password to the expect scripts via command-line arguments to expect instead. As done now, a double quote would confuse expect since the here-to document is interpolated fully before sent to expect's stdin.
A password like 'hej"' without the single quotes would lead to a send command for expect looking like this:
send "hej"\r"
expect will not enjoy that.
You can access the argument via argv, beware of quoting. Do not that you will expose the username and password to anyone doing "ps" on that box if you pass them as arguments on the command line to expect. But you already do that when calling the script in the question...
Why not use the Expect shell directly for doing this.
#!/usr/bin/expect
set timeout 20
set user [lindex $argv 0]
set password [lindex $argv 1]
spawn passwd $user
expect "Enter new UNIX password:"
send "$password\r";
expect "Retype new UNIX password:"
send "$password\r";
wait 1
spawn htdigest /.passwd "Authenticated Users" $user
expect "New password:"
send "$password\r"
expect "Re-type new password:"
send "$password\r"
wait 1
spawn htpasswd /squiddb $user
expect "New password:"
send "$password\r"
expect "Re-type new password:"
send "$password\r"
exit 0
Execute the above like
./SCRIPTNAME.exp user password

How can EXPECT interpret an escaped character to a command character

I'd like to be able to pass in a long command to expect. It's a multiple command somehow. First here's my expect script
#!/usr/bin/expect -f
set timeout -1
spawn telnet xxx.xxx.xxx.xxx
expect "*?username:*"
send "someusername\r"
expect "*?assword:*"
send "somepassword\r"
# Here's the command I'd like to pass from the command prompt
set command [lindex $argv 0]
send "$command\r"
send "exit\r"
I would then run this script as so:
./expectscript "mkdir /usr/local/dir1\ncd /usr/local/dir1\ntouch testfile"
Notice that I put "\n" to initiate an enter as though I'm processing the command before moving to the next.
I know you could separate the commands with ";", but for this particular exercise, I'd like to be able have expect interpret the "\n" with a "\r" so that, expect would behave as though it were like this:
send "mkdir /usr/local/dir1\r"
send "cd /usr/local/dir1\r"
send "touch testfile\r"
The question then becomes how can expect interpret the "\n" to be "\r"? I've tried putting the "\r" in the argument instead of "\n", but that doesn't work.
Thanks for the input.
When I do a simple experiment, I find that the \n in the argument is not converted by my shell (bash) into a newline; it remains a literal. You can check this out for yourself by just using puts to print out the command line argument, like this:
puts [lindex $argv 0]
Working around this requires a little bit of work to split things. Alas, Tcl's split command does not split on multi-character sequences (it splits on many different characters at once instead) so we'll need a different approach. However, Tcllib has exactly what we need: the splitx command. With that, we do this (based on #tensaix2j's answer):
#!/usr/bin/expect -f
package require Expect;   # Good practice to put this explicitly
package require textutil::split; # Part of Tcllib
# ... add your stuff here ...
foreach line [textutil::split::splitx [lindex $argv 0] {\\n}] {
send "$line\r"
# Wait for response and/or prompt?
}
# ... add your stuff here ...
If you don't have Tcllib installed and configured for use with Expect, you can also snarf the code for splitx directly out of the code (find it online here) as long as you internally acknowledge the license it's under (standard Tcl licensing rules).
foreach cmd [ split $command \n ] {
send "$cmd\r\n"
}

Resources