could not able to spawn(ssh) using expect - tcl

while executing
$expect filename.exp user pwd ip
I got the error
could not execute "ssh -l user ip": no such file or directory
The contents of filename.exp are this:
#!/opt/sfw/bin/expect -D
set OMC_Usr [lindex $argv 0]
set OMC_Pwd [lindex $argv 1]
set OMC_IP [lindex $argv 2]
set cmd "ssh -l $OMC_Usr $OMC_IP"
spawn $cmd
expect "Password:"
send "$OMC_Pwd\r"
interact
help me to proceed
Thanks...

The problem is that spawn needs the command and its arguments passed to it as multiple Tcl arguments, just like the standard Tcl exec command. The fix is to do this directly:
spawn ssh -l $OMC_Usr $OMC_IP
Or, if you prefer (and you've got Tcl 8.5):
set cmd "ssh -l $OMC_Usr $OMC_IP"
spawn {*}$cmd
Or, if you've got 8.4 or before:
eval spawn [lrange $cmd 0 end]
But don't do this:
eval spawn $cmd
because that will break unexpectedly if you have a Tcl metacharacter in the username (or IP address, but that's very unlikely).
Of course, the real fix is to set up an RSA keypair and use ssh-agent to manage it. Like that, you won't need to pass passwords on any command line; that's important because the command line of a process is public information about the process. Really. You can find it out with something trivial like ps -efww (or the equivalent for your OS). Environment variables are just as insecure too; there's an option to ps to show them too.

Related

Expect script should not end

I need some help with my script. I have written a piece of code where the script uses ssh to remote host and executes a tail command and need to stay there. It should not come out of the script.
#!/usr/bin/tclsh
package require Expect
set IP [lindex $argv 0]
set spawnID [spawn ssh root#$IP]
expect {
password: {
send "XXXXXXX\r"
exp_continue
}
"# "
}
send "kubectl -it exec server-0 bash\r"
expect "# "
send "tail -f Server.log\r"
expect "$"
The tail -f command should stay but its coming out of the script in few seconds.
Its printing the logs as expected but comes out of the session in 10 seconds.
Requirement is to stay there unless the user hits "ctrl c"

How to put sleep command in a tcl script?

I'm trying to put a sleep command in a tcl script to wait 10 seconds before send the disconnect command:
#!/usr/bin/expect -f
set n 0
if { $argc != 1 } {
puts "The script requires the number of bot."
puts "Please try again."
exit 1
} else {
set n [lindex $argv 0]
}
# Spawn Python and await prompt
spawn mono --debug pCampBot.exe -loginuri http://127.0.0.1:9000 -s home -n $n -firstname test -lastname bot_ -password 123 -b p
expect "pCampbot#"
# Send Python statement and await prompt
send "conn\n"
expect "pCampbot#"
sleep 10
send "disconn\n"
expect "pCampbot#"
# Pass control to user so he can interact with Python
interact
The problem is that sleep command it's like it's executed after the spawn command, that is it waits 10 seconds and after send "conn" and "disconn" commands sequentially.
I don't know why it behaves like that and i don't know how i can solve this problem.
Tcl uses the after command for this, provided you give it a single argument that is an integer number of milliseconds. To sleep for 10 seconds, you do:
after 10000

Fish shell: Check if argument is provided for function

I am creating a function (below) with which you can provide an argument, a directory. I test if the $argv is a directory with -d option, but that doesn’t seem to work, it always return true even if no arguments are supplied. I also tried test -n $argv -a -d $argv to test is $argv is empty sting, but that returns test: Missing argument at index 1 error. How should I test if any argument is provided with the function or not? Why is test -d $argv not working, from my understanding it should be false when no argument is provided, because empty string is not a directory.
function fcd
if test -d $argv
open $argv
else
open $PWD
end
end
Thanks for the help.
count is the right way to do this. For the common case of checking whether there are any arguments, you can use its exit status:
function fcd
if count $argv > /dev/null
open $argv
else
open $PWD
end
end
To answer your second question, test -d $argv returns true if $argv is empty, because POSIX requires that when test is passed one argument, it must "Exit true (0) if $1 is not null; otherwise, exit false". So when $argv is empty, test -d $argv means test -d which must exit true because -d is not empty! Argh!
edit Added a missing end, thanks to Ismail for noticing
In fish 2.1+ at least, you can name your arguments, which then allows for arguably more semantic code:
function fcd --argument-names 'filename'
if test -n "$filename"
open $filename
else
open $PWD
end
end
if not set -q argv[1]
echo 'none'
else
echo 'yes'
end
From the man set page:
set ( -q | --query ) [SCOPE_OPTIONS] VARIABLE_NAMES...
o -q or --query test if the specified variable names are defined. Does not output anything, but the builtins exit status is the number of variables specified
that were not defined.
$argv is a list, so you want to look at the first element, if there are elements in that list:
if begin; test (count $argv) -gt 0; and test -d $argv[1]; end
open $argv[1]
else
open $PWD
end
Maybe is non related, but i would like to add another perspective for the question.
I want to broaden the insight to a wider scope the scope of testing the shell code with the libraries developed in the fisherman group.
With mock you can check if the open command is called safely without side effects.
Example.:
function fcd
if count $argv > /dev/null
open $argv
else
open $PWD
end
end
mock open 0 "echo \$args"
fcd "cool" # echoes cool
mock open 0 "echo \$args"
fcd # echoes $PWD
Is a recent library, but it could help to test things that might be dangerous, like for example rm
mock rm 0 "echo nothing would happen on \$args"
rm "some file" # simply echoes the message with a list of the files that would been affected
I hope that it gives a more out of the box point of view
P.S.: Sorry for the blatant publicity, but i think that is a cool idea that would be nice to be adopted by shell scripters, to test and add sustainability to shell scripts is nuts. :P
EDIT: i recently noticed a bug about the sample i posted. Please do not use rm *, because the asterisk is not treated as a param, instead fish shell expands asterisk to the list of files found and the command only mocks the first call, this means that the first file would be ignored by the mock, but all the subsequent files will get erased so please be careful if trying the sample and use a single file for the example not the wildcard.

rsync error: when using source which contain "*" character

In my TCL script I use rsync command and unfortunately it is refusing to sync path contain "*" character.
Code Example:
set srs "/users/home/username/common/*"
catch {exec rsync -av $src /tmp} res
puts $res
Output:
building file list ... done
sent 29 bytes received 20 bytes 98.00 bytes/sec
total size is 0 speedup is 0.00
rsync: link_stat "/users/home/username/common/*" failed: No such file or directory (2)
rsync error: some files could not be transferred (code 23) at main.c(977) [sender=2.6.9]
while executing
"exec rsync -rLptgov $src /tmp "
The canonical way of writing that is:
set srs "/users/home/username/common/*"
catch {exec rsync -av {*}[glob $src] /tmp} res
puts $res
This is because Tcl doesn't expand glob metacharacters by default; it's safer that way and easier to write correct code (it's hard to write good shell code that is resistant to problems in this area) but it does mean that you need to do a little extra work. The extra work is:
Ask for glob expansion of $srs with the glob command. That returns a list.
Ask for expansion of the list out of the glob by using the {*} pseudo-operator (it's not an operator — it's technically a kind of syntax — but it works a lot like one).
If you're using Tcl 8.4 (or before!) then you do it a bit different way:
set srs "/users/home/username/common/*"
catch {eval [list exec rsync -av] [glob $src] [list /tmp]} res
puts $res
OK, that's sometimes often shortened by leaving out the list bits (and there are many more obscure ways of writing it!) but that's a bad habit as it can cause huge problems when dealing with variables with values that aren't “nice”, e.g., with spaces in pathnames. If you have 8.5 or later, use {*}. Really.
To elaborate on what GrAnd said, you have to build the list of source files explicitly, probably using glob, so you'd do it like this
set cmd [concat [list exec rsync -av] \
[glob -nocomplain /users/home/username/common/*]]
lappend cmd /tmp
catch {eval $cmd} res
puts $res
or, alternatively, use {*} to expand the agument list for exec (this is for Tcl >= 8.5), like this:
set cmd [concat [list rsync -av] \
[glob -nocomplain /users/home/username/common/*]]
lappend cmd /tmp
catch {exec {*}$cmd} res
puts $res
Update: fixed example so that it really works.
That's because tcl interpreter does not expand the '*' symbol as shell does.
For example:
$ tclsh
% cd /usr
% exec ls -d *
ls: *: No such file or directory
% exec sh -c "ls -d *"
X11R6
bin
docs
etc
...

Expect Tcl script - Error passing quoted argument using spawn

I just wrote a very simple Expect script for wrapping around rsync, but it seems to be giving me trouble. Basically, I am automating the SSH login prompt called from rsync. I also have to pass arguments through rsync to SSH so it doesn't do the host key checking. I am well aware of SSH authentication keys and ssh-keygen, but I have good reasons for doing things this way so no lectures on passing passwords on the command-line.
Script
#!/usr/local/bin/expect -f
if {$argc != 5} {
puts "usage: remoteCopy {remotehost, username, password, localfile, remoteloc}"
exit 1
}
set remotehost [lindex $argv 0]
set username [lindex $argv 1]
set password [lindex $argv 2]
set localfile [lindex $argv 3]
set remoteloc [lindex $argv 4]
set timeout -1
spawn rsync -e \"ssh -q -o StrictHostKeyChecking=no\" $localfile $username#$remotehost:$remoteloc
expect "Password"; send "$password\r"
Here is the complete output from the script:
Output
avoelker#localhost $ ./remoteFileCopy.tcl remotehost remoteuser remotepass ~/localfile /tmp/
spawn rsync -e "ssh -q -o StrictHostKeyChecking=no" /localhost/home/avoelker/localfile remoteuser#remotehost:/tmp/
rsync: Failed to exec "ssh: No such file or directory (2)
rsync error: error in IPC code (code 14) at pipe.c(83)
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: error in rsync protocol data stream (code 12) at io.c(434)
send: spawn id exp6 not open
while executing
"send "$password\r""
(file "./remoteFileCopy.tcl" line 17)
avoelker#localhost $
It appears to me that rsync is trying to execute "ssh instead of just ssh, however, copying and pasting the first line of output from Expect's stdout (rsync -e "ssh -q -o StrictHostKeyChecking=no" /localhost/home/avoelker/localfile remoteuser#remotehost:/tmp/) directly into the shell works perfectly fine, so I know it isn't a problem with rsync.
When I remove the -e argument entirely from the spawn rsync line, the Expect script executes without errors, but I would have to add in host key checking to the script, which I do not want to do.
Does someone more experienced with Expect/Tcl know what I am doing wrong?
Drop your backslashes. The spawn line should just say:
spawn rsync -e "ssh -q -o StrictHostKeyChecking=no" $localfile $username#$remotehost:$remoteloc
After all, you don't want rsync to see a command with a quote in it, do you?