How can I execute all script parameters that use here doc? - bash

I have a simple script, which I use throughout my code base, called sshpass.
#!/bin/bash
expect << EOF
spawn $#
expect {
"*assword" {
send "$SSHPASS\n"
}
}
expect eof
EOF
The way I'm currently utilizing this script is like this -
./sshpass scp archive.tgz $SERVER:$DIR
This works quite well when the SSH commands are one-liners. My issue is when I attempt to execute a command through sshpass that uses a here doc.
./sshpass ssh $user#$server /bin/bash << EOF
echo "do this..."
echo "do that..."
echo "and the other..."
EOF
The above fails because $# only parses out ssh $user#$server /bin/bash.
Please no comment with regards to how I'm handling my SSH authentication. When forced to use Cygwin there are specific things, such as key authentication and administrative privileges, that simply do not work.

The heredoc will replace stdin for your script. If you want it to be accessed as a parameter, use command substitution like
./sshpass ssh $user#$server /bin/bash $(cat << EOF
echo "do this..."
echo "do that..."
echo "and the other..."
EOF
)
although that probably won't end up doing exactly what you want, because it will be passing each word as it's own positional argument, so you will be running
ssh $user#$server echo "do this..." echo "do that..." echo "and the other..."
which will have the first echo get all the rest as arguments. You'll need semi colons at the end of each command, and to have quotes around the whole thing so you don't do some of it remotely and some of it locally. So I should have recommended it as:
./sshpass ssh $user#$server /bin/bash "$(cat << EOF
echo 'do this...';
echo 'do that...';
echo 'and the other...'
EOF
)"
but this still gives me an uneasy feeling as it's very likely easy to "do the wrong thing" with something like this

Related

Trying to Create a remote login tool: "interact" in "expect << EOF" does not work

The use case of this script is I have various servers with different ssh keys. I am trying to write a script so when called will log into the specified server. An example of usage would be:
./ServerLogin.sh Server1
I feel that I am fairly close, but The last part of expect interact is tripping me up. This is a simplified version:
#!/bin/bash
ServerName="$1"
case $ServerName in
"Server1") IP="1.2.3.4" ; keyPath="/path/to/key.pem" ; password="password" ; break ;;
*) echo "Server not recognized" ; exit ;;
esac
/usr/bin/expect << EOD
spawn ssh -i $keyPath user#$IP
expect "*.pem': "
send "$password\r"
interact
EOD
The result of this is it logs in and immediately closes. I want for the session to remain interactable.
Any ideas?
The problem is in expect << EOF. With expect << EOF, expect's stdin is the here-doc rather than a tty. But the interact command only works when expect's stdin is a tty. Your answer is one solution. Another solution is to use expect -c if you prefer not using a tmp file.
expect -c "
spawn ssh -i $keyPath user#$IP
expect \"*.pem': \"
send \"$password\r\"
interact
"
After toying around with it some more, I found a working solution. Basically create the expect script and run it. Why it works like this and not in the original question is beyond me. But it works and I will use this for the time being. Thanks everyone for the help!
Working Solution:
#!/bin/bash
ServerName="$1"
case $ServerName in
"Server1") IP="1.2.3.4" ; keyPath="/path/to/key.pem" ; password="password" ; break ;;
*) echo "Server not recognized" ; exit ;;
esac
function WriteExp {
echo "#!/usr/bin/expect"
echo "spawn ssh -i $keyPath ubuntu#$IP"
echo "expect \"*.pem': \""
echo "send \"$password\\r\""
echo "interact"
}
WriteExp > $ServerName.exp
chmod 755 $ServerName.exp
/usr/bin/expect $ServerName.exp
# Cleanup the evidence
rm $ServerName.exp
Using just tcl/expect, instead of a hybrid of shell and it, makes for much cleaner code, without any of the potential issues posed by shell variable interpolation:
#!/usr/bin/expect -f
switch -- [lindex $argv 0] {
Server1 {
set IP 1.2.3.4
set keyPath /path/to/key.pem
set password "password"
}
default {
puts stderr "Server not recognized"
exit 1
}
}
spawn ssh -i $keyPath user#$IP
expect "*.pem': "
send "$password\r"
interact

Testing account existence using expect

I have a list of 400 servers and I like to check unix account existence with expect to loop it
I wrote a bash script that uses expect command but it returns me error message that I don't understand the meaning
#!/bin/bash
fic_serv="test.txt"
echo "Passwd"
stty -echo
read -s passwd
stty echo
suffix="suffix"
account="acc"
for server in `cat $fic_serv`
do
prompt="[$acc#$server ~]$ "
expect -c "
spawn ssh -o StrictHostKeyChecking=no $account#$server.$suffix
expect "Password: "
send "$passwd\r"
expect $prompt
send "logout\r"
"
done
[acc#serv ~]$ couldn't read file "
send "passwd\r"
expect [acc#server ~]$
send "logout\r"
": no such file or directory
(I modified the value)
You should use while, not for, to parse files in Bash. Use a "redirect" to treat a file as standard input and read one line at a time.
while read server; do
...
done < $fic_serv
Your major problem is Expect interprets your "s as "end of script". Escape them, as in \", or use {}, as in:
expect -c "
spawn ssh -o StrictHostKeyChecking=no $account#$server.$suffix
expect {Password: }
send {$passwd\r}
expect $prompt
send {logout\r}
"
If you have 400 servers to manage, I strongly recommend you use ansible.
You could just put the list of hosts into a file, let's call it inventory, and run the following command:
ansible -i inventory -m shell -a "id acc" all
Using here-docs in the shell to embed code for another language is usually better than quoting hell, and sharing variables through the environment is easier and safer than parameter expansion:
export account passwd
while IFS= read -r server; do
export prompt="[$acc#$server ~]$ "
export host="$server.$suffix"
expect << 'END_EXPECT'
spawn ssh -o StrictHostKeyChecking=no $env(account)#$env(host)
expect "Password: "
send "$env(passwd)\r"
expect $env(prompt)
send "logout\r"
expect eof
END_EXPECT
done < "$fic_serv"
As shown, I like to indent the heredoc to make it more obvious.
And depending on the error message or login prompt, there can be more logic to indicate that the account name and/or password are incorrect.

Input to proprietary command prompt in shell script

I want to know how we can provide inputs to command prompts which change. I want to use shell scripting
Example where '#' is usual prompt and '>' is a prompt specific to my program:
mypc:/home/usr1#
mypc:/home/usr1# myprogram
myprompt> command1
response1
myprompt> command2
response2
myprompt> exit
mypc:/home/usr1#
mypc:/home/usr1#
If I understood correctly, you want to send specific commands to your program myprogram sequentially.
To achieve that, you could use a simple expect script. I will assume the prompt for myprogram is noted with myprompt>, and that the myprompt> symbol does not appear in response1 :
#!/usr/bin/expect -f
#this is the process we monitor
spawn ./myprogram
#we wait until 'myprompt>' is displayed on screen
expect "myprompt>" {
#when this appears, we send the following input (\r is the ENTER key press)
send "command1\r"
}
#we wait until the 1st command is executed and 'myprompt>' is displayed again
expect "myprompt>" {
#same steps as before
send "command2\r"
}
#if we want to manually interract with our program, uncomment the following line.
#otherwise, the program will terminate once 'command2' is executed
#interact
To launch, simply invoke myscript.expect if the script is in the same folder as myprogram.
Given that myprogram is a script, it must be prompting for input with something like while read IT; do ...something with $IT ...;done . Hard to say exactly how to change that script without seeing it. echo -n 'myprompt> would be the simplest addition.
can be done with PS3 and select construct
#!/bin/bash
PS3='myprompt> '
select cmd in command1 command2
do
case $REPLY in
command1)
echo response1
;;
command2)
echo response2
;;
exit)
break
;;
esac
done
Or with echo and read builtins
prompt='myprompt> '
while [[ $cmd != exit ]]; do
echo -n "$prompt"
read cmd
echo ${cmd/#command/response}
done

how to add ssh key to host via bash script

I've been trying to automate the creation of a user and configuration of the ssh access.
So far I created a script that access the host and creates the new user via expect, as follows:
expect -c '
spawn ssh '$user'#'$ip';
expect "assword: ";
send "'$passwd'\r";
expect "prompt\n";
send "adduser '$new_user'\r";
...
send "mkdir /home/'$new_user'/.ssh\r";
expect "prompt\n";
send "exit\r";
'
This works fine, after that I need to add the .pub key file to the authorized keys file in the host, there is where hell started.
I tried:
ssh_key='/home/.../key.pub'
content=$(cat $ssh_key)
expect -c '
spawn ssh '$user'#'$ip' "echo '$content' >> /home/'$new_user'/.ssh/authorized_keys;
expect "password:";
...
'
and got:
missing "
while executing
"spawn ssh root#000.00.00.00 ""
couldn't read file "<ssh .pub key content> ...
I tried also:
cat $ssh_key | ssh $user#$ip "cat >> /home/$new_user/.ssh/authorized_keys"
Without success, I only get the password query blinking, I can't connect the expect with this last method.
I'm going to ignore the larger problems here and focus specifically on your question. (There are larger problems: Don't use expect here -- if you rely on sshpass instead you can simplify this script immensely).
Right now, when you close your single quotes, you aren't starting any other kind of quotes. That means that when you substitute a variable with whitespace, you end the -c argument passed to expect.
Instead of doing this:
'foo'$bar'baz'
do this:
'foo'"$bar"'baz'
...so your script will look more like:
ssh_key='/home/.../key.pub'
content=$(<"$ssh_key")
expect -c '
spawn ssh '"$user"'#'"$ip"' "echo '"$content"' >> /home/'"$new_user"'/.ssh/authorized_keys;
expect "password:";
...
'
In terms of avoiding this altogether, though, consider something more like the following:
#!/bin/bash
# ^^^^- NOT /bin/sh
content=$(<"$ssh_key") # more efficient alternative to $(cat ...)
# generate shell-quoted versions of your variables
# these are safe to substitute into a script
# ...even if the original content contains evil things like $(rm -rf /*)
printf -v content_q '%q' "$content"
printf -v new_user_q '%q' "$new_user"
# use those shell-quoted versions remotely
sshpass -f"$password_file" ssh "$host" bash -s <<EOF
adduser ${new_user_q}
printf '%s\n' ${content_q} >>/home/${new_user_q}/.ssh/authorized_keys
EOF

how to use expect inside shell script

I have a bash+expect script which has to connect normal user, i want
to read the specific file and store into the variable to be used
after while that specific file in root user. How can i get the value ?
My script is:
#!/bin/bash
set prompt ">>> "
set command ls /root/test1
expect << EOF
spawn su root
expect "password:"
send "rootroot\r"
expect "$prompt\r"
send "$command\r"
expect "$prompt\r"
expect -re "(.*)\r\n$prompt\r\n"
EOF
echo "$command"
if [ ! -f "$command" ]; then
echo "file is not exist"
else
echo "file is exist"
fi
whenever i'm execute my shell script it show following output:
ls: /root/: Permission denied
file is not exist
basically test is there but it is showing "file is not exist"
This question is very old but i hope someone gets help from this answer.
--> You should use #!/usr/bin/expect or #!/bin/expect to use expect properly, expect<<EOF might work but thats not conventional way to write script.
--> You script should end with EOF statement . Ex.
#!/usr/bin/expect << EOF
<some stuff you want to do>
EOF
--> Some basic thing about spawn. Whatever you write in spawn will execute but it will not have effect on entire script. Its not like environment variables.
In short, spawn will start new process and your command is not under spawn process.
Ex.
#!/usr/bin/expect
spawn bash -c "su root '<your-cmd-as-root>'"
<some-expect-send-stuff-etc>
Now in your script, $command should be write inside spawn like i showed in above example.

Resources