Get one line of user input and then execute it as Bash commands - linux

I have wrote a expect script that helps to execute commands in remote machine. When the execution is completed, I want to get one line of user input and then send it to the remote bash, here is the code snippet:
#! /usr/bin/env expect
...
spawn ssh -l $user $host
...
send_tty -- "Enter your command: "
set timeout -1
# match only printable characters (prevent from pressing TAB)
expect_tty eof exit -re {([[:print:]]*)\n}
send_tty -- "\n"
set timeout 10
# send the command to remote shell
send "$expect_out(1,string)"
expect "$GENERAL_PROMPT"
However, if the input is something like: ls /", my program will be blocked because the remote shell expects to get more characters by prompting the string "> ". Actually, I hope bash won't prompt for more input instead of just printing error message:
$ read COMMAND
ls /"
$ eval "$COMMAND"
bash: unexpected EOF while looking for matching `"'
bash: syntax error: unexpected end of file
Can I achieve this in my script?

#!/usr/bin/expect
set prompt "#|%|>|\\\$ $"; # A generalized prompt to match known prompts.
spawn ssh -l dinesh xxx.xx.xx.xxx
expect {
"(yes/no)" { send "yes\r";exp_continue}
"password"
}
send "mypassword\r"
expect -re $prompt
send_tty -- "Enter your command: "
set timeout -1
# match only printable characters (prevent from pressing TAB)
expect_tty eof exit -re {([[:print:]]*)\n}
send_tty -- "\n"
set timeout 10
puts "\nUSER INPUT : $expect_out(1,string)"
# send the command to remote shell
# Using 'here-doc', to handle possible user inputs, instead of quoting it with any other symbol like single quotes or backticks
send "read COMMAND <<END\r"
expect -re $prompt
send "$expect_out(1,string)\r"
expect -re $prompt
send "END\r"
expect -re $prompt
# Since we want to send the literal dollar sign, I am sending it within braces
send {eval $COMMAND}
# Now sending 'Return' key
send "\r"
expect -re $prompt
Why 'here-doc' used ?
If I have used backticks or single quotes to escape the commands, then if user gave backticks or single quotes in the commands itself, then it may fail. So, to overcome that only, I have added here-doc.
Output :
dinesh#MyPC:~/stackoverflow$ ./zhujs
spawn ssh -l dinesh xxx.xx.xx.xxx
dinesh#xxx.xx.xx.xxx's password:
[dinesh#lab ~]$ matched_literal_dollar_sign
Enter your command: ls /"
USER INPUT : ls /"
read COMMAND <<END
> ls /"
> END
[dinesh#lab ~]$ eval $COMMAND
-bash: unexpected EOF while looking for matching `"'
-bash: syntax error: unexpected end of file
[dinesh#lab ~]$ dinesh#MyPC:~/stackoverflow$
Update :
The main reason for using here-doc is due to the fact that it makes the read to act as non-blocking command. i.e. We can proceed quickly with next command. Else, we have to wait till the timeout of Expect. (Of course, we could change the timeout value dynamically.)
This is just one way of doing it. You can alter it if you want, with simply having the read command.

I think this would be a good case for interact -- get expect to step aside and let the user interact directly with the spawned program.
spawn ssh -l $user $host
#...
send_user "You are now about to take control: type QQQ to return control to the program\n"
interact {
QQQ return
}
send_user "Thanks, I'm back in charge ...\n"

This is a one line version
read > export cmd ; eval $cmd ; unset cmd

Related

Use Expect script with Expect/send

I want to get the value from the SQL command below. But I've got an error message every time.
The Expect code:
#!/usr/bin/expect
set timeout 60
spawn ssh -i id_rsa user#xxx.xxx.xxx.xxx
sleep 10
send -- "sudo su\r"
sleep 10
expect '[sudo] password for user: '
send -- "secret\r"
sleep 2
send "mysql -u root\r"
sleep 2
send -- "use nextcloud;\r"
sleep 2
send -- "SELECT * FROM oc_accounts;\r"
Error message:
spawn ssh -i id_rsa user#xxx.xxx.xxx.xxx
invalid command name "sudo"
while executing
"sudo"
invoked from within
"expect '[sudo] password for user: ' "
(file "./test.sh" line 8)
Single quotes have no special meaning in Expect. Expect is an extension of Tcl, where braces are the non-interpolating quoting mechanism.
You get the "invalid command name" error because square brackets are the Tcl command substitution syntax.
You want
expect {[sudo] password for user: }
Instead of sleep, with idiomatic Expect you should expect some pattern: your remote shell's prompt, the MySQL prompt, etc.
#pynexj has a good point. Try
expect -exact {[sudo] password for user:}

How do I ssh into a machine, wait for a prompt, and then send a bunch of commands to it?

How do I do the following?
SSH to a machine, probably using expect since this is a script and I can't type the password for it
Wait for a prompt from the machine. The prompt is ->
Then send a bunch of commands from the original host to the machine I ssh'd to. Note that the output from these commands can contain the characters ->
Exit
Here are the contents of some-commands.txt:
first command
second command
third command
Here are the contents of the expect script:
#!/usr/bin/expect
set f [open "some-commands.txt"]
set cmds [split [read $f] "\n"]
close $f
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no root#machine
interact -o -nobuffer -re "Password:" return
send "password\r"
# look for the prompt
set prompt "\n-> "
foreach cmd $cmds {
send "$cmd\r";
# The following works, except for the commands
# whose output include ->
interact -o -nobuffer -re "-> " return
}
The problem is that the interact command captures the -> from the command output instead of the prompt, which hasn't yet arrived at that point.
I'm used to accomplish the same thing by doing something like:
ssh -t -t -C user#host 'bash -s' < my_shell_script.sh param1 paramX
Where my_shell_script.sh is an simple shell script.
The trick here is use multiple -t to force pseudo-terminal over ssh and the -s option to bash witch makes it reads the commands from the standard input.

expect, interact and then again expect

There are several posts regarding the same, but i still not able to make my expect script work properly. My intention is to automate everything but leave the password enter for the user. So there are 3 parts of the script:
automated login
give the user interaction to enter the password
give control back to Expect script to continue work
So i have script which will be spawned and which have 3 read commands. First and last should be filled by Expect and second one i would like to enter my self:
#!/bin/ksh
read user?User:
echo "Expect entered the username $user"
read pass?Password:
echo "User entered the password $pass"
read command?"Shell>"
echo "Expect entered the command $command"
My expect script:
#!/usr/bin/expect
spawn ./some_script
expect User
send I-am-expect\r
expect Password
interact
expect Shell
send I-am-expect-again
Unfortunately after i have entered the password the script does not continue and left in the interact mode:
[root#localhost ~]# ./my-expect
spawn ./some_script
User:I-am-expect
Expect entered the username I-am-expect
Password:i am user
User entered the password i am user
Shell>
And finally when i entering something on the "Shell" and pressing [ENTER] expect exits with the error:
Expect entered the command
expect: spawn id exp4 not open
while executing
"expect Shell"
(file "./my-expect" line 7)
[root#localhost ~]#
I appriciate any explanation or resolution of this issue. I am using expect version 5.45
You can read (expect_user) the user's password by yourself and then send it to the spawn'ed program. For example:
[STEP 101] # cat foo.exp
proc expect_prompt {} \
{
global spawn_id
expect -re {bash-[.0-9]+(#|\$)}
}
spawn ssh -t 127.0.0.1 bash --noprofile --norc
expect "password: "
stty -echo
expect_user -timeout 3600 -re "(.*)\[\r\n]"
stty echo
send "$expect_out(1,string)\r"
expect_prompt
send "exit\r"
expect eof
[STEP 102] # expect foo.exp
spawn ssh -t 127.0.0.1 bash --noprofile --norc
root#127.0.0.1's password:
bash-4.3# exit
exit
Connection to 127.0.0.1 closed.
[STEP 103] #
The interact should be given with proper condition for the exit criteria.
The following script will execute the user commands in the shell
exeCmds.sh
#!/bin/bash
read -p "User: " user
echo "Expect entered the username $user"
read -p "Password: " pass
echo "User entered the password $pass"
while :
do
# Simply executing the user inputs in the shell
read -p "Shell> " command
$command
done
automateCmdsExec.exp
#!/usr/bin/expect
spawn ./exeCmds.sh
expect User
send dinesh\r
expect Password
send welcome!2E\r
expect Shell>
puts "\nUser can interact now..."
puts -nonewline "Type 'proceed' for the script to take over\nShell> "
while 1 {
interact "proceed" {puts "User interaction completed.";break}
}
puts "Script take over the control now.."
# Now, sending 'whoami' command from script to shell
send "whoami\r"
expect Shell>
# Your further code here...
The script automateCmdsExec.exp will address the login needs of the bash script and when the prompt arrives, it will hand over the control to user.
We should define an exit criteria for the interact for which I have used the word proceed. (You can alter it as per your need).
Once interact matched the word proceed. it will return the control back to the expect script.
For demo purpose, I kept one more send-expect pair of command.
i.e.
send "whoami\r"
expect Shell>
You can keep your further code below the interact, thus it can be executed by script.

how to acquire the return value of a command in ssh accessed by expect?

After i use expect access ssh, and run particular command in it. How could i get the actually return value of that command and set it to the return value of expect or an env variable(better)?
I am not so familiar with expect, so a few lines to show how the capture thing works would help a lot. Many thanks.
You do it the same way you would sitting at a terminal: echo $? (I assume your remote shell is sh/ksh/bash/...)
#!/usr/bin/expect -f
set host remote_host
set user remote_username
set prompt {\$ $}
set cmd {grep not-found /etc/passwd}
log_user 0
spawn ssh -l $user $host
expect -re $prompt
send "$cmd\r"
expect -re $prompt
send -- "echo \$?\r"
expect -re "\r\n(\\d+)\r\n.*$prompt"
set rc $expect_out(1,string)
send -- "exit\r"
expect eof
puts "return code from '$cmd' on $host = $rc"

expect script inside bash script

I am really stumped!
I DONT WANT TO USE RSA AUTH SO PLEASE ABSTAIN.
#!/bin/bash
echo "Enter the server password"
read password
cd /home/mike/
# this is included the code below ->
/usr/bin/expect << EOD
set timeout -1
spawn scp file.txt server.com:/home
expect {
timeout { send_user "TIME OUT\n"; exit 1 }
"*assword: "
}
send "$password\r"
expect {
"s password: " { send_user "\nIncorrect Password. Login Failed.\n"; exit 1 }
"100%"
}
sleep 1
send "exit\r"
expect eof
This works and file.txt gets transferred to the server but I get this warning message ->
" line 44: warning: here-document at line 22 delimited by end-of-file (wanted `EOD')"
When I add EOD at the end after 'expect eof' it gives me this error ->
send: spawn id exp4 not open
while executing
"send "exit\r"
Any help would be appreciated.
I am saying again I cant use pubkey auth so please dont suggest that.
By replacing your last line:
expect eof
with:
EOD
I don't get the complaint anymore. Note that you cannot have any tabs (and maybe spaces as well?) before your EOD tag (which you defined in your initial /usr/bin/expect line.
Instead of trying to script this using expect, you can simply use curl to copy files to/from a remote host via scp or sftp, and pass the authentication credentials to the curl command like so:
curl sftp://remotehost.domain.com/path/to/remote/file --user username:password -o /path/to/local/file

Resources