Can Ruby interact with the shell sh?


#1

Hello,

I have to establish an VPN connection, currently I’m doing with another
app that can interact with the shell (on OS X), but now I want to
refactor all of this and use Ruby for that.

What I’m trying is a lot of information about irb but that’s not what I
want.

In pseudo code:

open a shell promtp
issue a command
read the answer
issue a command

thanks!

r.


#2

Raimon Fs wrote:

Hello,

I have to establish an VPN connection, currently I’m doing with another
app that can interact with the shell (on OS X), but now I want to
refactor all of this and use Ruby for that.

What I’m trying is a lot of information about irb but that’s not what I
want.

In pseudo code:

open a shell promtp
issue a command
read the answer
issue a command

thanks!

r.

If you just want responses from commands, you can simply use lines like
response = command
On the other hand, if you need to interact multiple times with a program
other than the shell, during the course of it’s runtime, I think you’d
want to use IO.popen.


#3

If you just want responses from commands, you can simply use lines like
response = command
On the other hand, if you need to interact multiple times with a program
other than the shell, during the course of it’s runtime, I think you’d
want to use IO.popen.

I have to interact with the shell:

This is the code I’m using in another language:

me.Execute “sh”

me.Write “vpnclient connect customer_net user xxxxxxx pwd
xxxxxxxxxx”+Chr(13)

and then I have to read the answers that are coming from the shell, and
when it asks something, write again to it:

here I’m waiting for a /y/n) and I have to write to it ‘y’ (yes), and
then the vpn connection is established.

I’m not following you with the response = command

All info that I have is about irb, but I want the unix ‘shell’

thanks,

r.

Dim data as String
data=me.ReadAll

if data.Right(7) = "(y/n): " then

me.Write "y"+Chr(13)

elseif data.InStr(“Your VPN connection is secure”) > 0 then

connected=True

app.print "VPN connection established"

end if


#4

Choi, Junegunn wrote:

As Adam suggested, you could use a pipe. Open a pipe to the program in
read-write mode, so you can read from it and write to it. A simple
example:

IO.popen(’./some_command’, ‘r+’) do | pipe |
while line = pipe.gets do
puts " ** #{line}"
if line =~ %r{y/n}
pipe.puts ‘y’
end
end
end

But he was referring using a pipe if the program was other than the
shell, and I want to interact with the shell.

In other environments I have specific command to open an interactive
shell like me.Execute(“sh”) and so on …

Here is what I want to do in a terminal shell:

The vpnclient is a command, not an application with a double-click
approach.

thanks again for your time!

regards,

r.

MontxMacBookPro:~ montx$ vpnclient connect xxxxxxx user xxxxxxxx pwd
xxxxxxxxx
Cisco Systems VPN Client Version 4.9.01 (0100)
Copyright © 1998-2006 Cisco Systems, Inc. All Rights Reserved.
Client Type(s): Mac OS X
Running on: Darwin 9.6.0 Darwin Kernel Version 9.6.0: Mon Nov 24
17:37:00 PST 2008; root:xnu-1228.9.59~1/RELEASE_I386 i386
Config file directory: /etc/opt/cisco-vpnclient

WARNING:
Using the “pwd” option may allow other users
on this computer to see your password.

Initializing the VPN connection.
Contacting the gateway at xxx.xxx.xxx.xxx
Authenticating user.
Negotiating security policies.
Securing communication channel.

============== WARNING ================
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Do you wish to continue? (y/n): y

Your VPN connection is secure.

VPN tunnel information.
Client address: 10.10.10.1
Server address: xxx.xxx.xxx.xxx
Encryption: 168-bit 3-DES
Authentication: HMAC-SHA
IP Compression: None
NAT passthrough is active on port xxxxxxxxxxxx
Local LAN Access is disabled


#5

As Adam suggested, you could use a pipe. Open a pipe to the program in
read-write mode, so you can read from it and write to it. A simple
example:

IO.popen(’./some_command’, ‘r+’) do | pipe |
while line = pipe.gets do
puts " ** #{line}"
if line =~ %r{y/n}
pipe.puts ‘y’
end
end
end


#6

Raimon Fs wrote:

But he was referring using a pipe if the program was other than the
shell, and I want to interact with the shell.

IO.popen in principle should work with any external program, including a
shell.

IO.popen("/bin/sh",“r+”) do |s|
s.puts “ls /tmp; exit”
puts s.read
end

require ‘expect’ to get a more convenient API for searching for
responses.

However you need to be careful to ensure you don’t deadlock, either
because the shell is trying to send you a large amount of data but
you’re not reading it, or because you’re waiting for a particular string
which doesn’t appear.

You also won’t get any prompts in a non-interactive shell. You could try
/bin/sh -i, but that may cause more problems if the shell expects to be
connected to a pty.

I have a vague idea that there’s a spawn/pty library out there, which
you can google for. That would be safest for commands which chat
interactively.


#7

Thanks to all,

It’s working now …

I’m aware of the dead-lock or unknow things, I’m experimenting with it.

This is because I have to upload some files to an ftp server, but I must
do it trgough a VPN connection.

I want to start this VPN connection manually, and once connected, fire
all the file upload.

Thanks, I’m sure I’ll have lots of new questions, but now I can go
further …

thanks again!

regards,

r.

IO.popen("/bin/sh",“r+”) do |s|

s.puts “/usr/local/bin/vpnclient connect xxxxxx user xxxxxx pwd
xxxxxxxx\n”

while line = s.gets do
puts " ** #{line}"
if line =~ %r{y/n}
s.puts ‘y’
end
end

end


#8

Robert D. wrote:

On Wed, Feb 25, 2009 at 3:26 PM, Raimon Fs removed_email_address@domain.invalid wrote:

all the file upload.

Thanks, I’m sure I’ll have lots of new questions, but now I can go
further …

I would like to warn you about the path you have chosen, though you
will for sure learn lots of things.
If things get too tough, you might want to have a look at pty in the
standard lib.
But if your VPN client allows for port forwarding you should probably
just do that. Use ssh to forward the VPN connection to a local port
and use FTP right away. Often this is configured by the VPN
concentrator but if it is doable, it is the easiest way.
HTH
robert

Hi Robert,

Thanks for your warnings, I’m looking at PTY and I think it’s what I was
looking for.

I don’t have any kind of control about VPN, it’s a customer requisit,
and we can buy the exact hardware as they have an make the VPN
connection by hardware, or launching a utility (by Cisco), to start the
VPN.

The problem is that utility opens a window saying yes/no and you have to
click the button, or connect through the shell. We want to do this
without human interference, as this are jobs done at night, and
automatic.

Thanks again, I’m learning lots of things today …

:slight_smile:

regards,

r.


#9

On Wed, Feb 25, 2009 at 3:26 PM, Raimon Fs removed_email_address@domain.invalid wrote:

all the file upload.

Thanks, I’m sure I’ll have lots of new questions, but now I can go
further …

I would like to warn you about the path you have chosen, though you
will for sure learn lots of things.
If things get too tough, you might want to have a look at pty in the
standard lib.
But if your VPN client allows for port forwarding you should probably
just do that. Use ssh to forward the VPN connection to a local port
and use FTP right away. Often this is configured by the VPN
concentrator but if it is doable, it is the easiest way.
HTH
robert


#10

Raimon Fs wrote:

Thanks to all,

It’s working now …

I’m aware of the dead-lock or unknow things, I’m experimenting with it.

This is because I have to upload some files to an ftp server, but I must
do it trgough a VPN connection.

I want to start this VPN connection manually, and once connected, fire
all the file upload.

Thanks, I’m sure I’ll have lots of new questions, but now I can go
further …

thanks again!

regards,

r.

IO.popen("/bin/sh",“r+”) do |s|

s.puts “/usr/local/bin/vpnclient connect xxxxxx user xxxxxx pwd
xxxxxxxx\n”

while line = s.gets do
puts " ** #{line}"
if line =~ %r{y/n}
s.puts ‘y’
end
end

end

See, this is what I was referring to when I said a program other than
the shell. You’re using the shell here, but you almost certainly don’t
need to be. The program whose output you actually care about if not
/bin/sh, but /usr/local/bin/vpnclient

So, you could change your code in this way:

IO.popen("/usr/local/bin/vpnclient connect xxxxx user xxxxxxx pwd
xxxxxxxxxx",“r+”) do |s|

while line = s.gets do
puts " ** #{line}"
if line =~ %r{y/n}
s.puts ‘y’
end
end

end

I don’t have the vpnclient program on my machine, so I can’t test it,
but it should work exactly the same. Or, if my code is not exactly
correct, I hope at least I’ve conveyed the idea clearly.

Incidentally, there is a command line tool called ‘yes’ that will help
you accomplish what you want as well.

system(“yes | /usr/local/bin/vpnclient connect xxxxx user xxxxxxx pwd
xxxxxxxxxx”)

Of course, that only helps with the immediate situation where the only
interactivity is answering y/n questions and you want to answer all of
them ‘y’, but it where you can use it, it’s a lot simpler.


#11

See, this is what I was referring to when I said a program other than

the shell. You’re using the shell here, but you almost certainly don’t
need to be. The program whose output you actually care about if not
/bin/sh, but /usr/local/bin/vpnclient

So, you could change your code in this way:

IO.popen("/usr/local/bin/vpnclient connect xxxxx user xxxxxxx pwd
xxxxxxxxxx",“r+”) do |s|

while line = s.gets do
puts " ** #{line}"
if line =~ %r{y/n}
s.puts ‘y’
end
end

end

I don’t have the vpnclient program on my machine, so I can’t test it,
but it should work exactly the same. Or, if my code is not exactly
correct, I hope at least I’ve conveyed the idea clearly.

Incidentally, there is a command line tool called ‘yes’ that will help
you accomplish what you want as well.

system(“yes | /usr/local/bin/vpnclient connect xxxxx user xxxxxxx pwd
xxxxxxxxxx”)

Of course, that only helps with the immediate situation where the only
interactivity is answering y/n questions and you want to answer all of
them ‘y’, but it where you can use it, it’s a lot simpler.

thanks a lot for all this usefull information, I think the ‘yes’ command
is the top winner today

:slight_smile:

regards,

r.

ps. I can’t test the code now as the VPN has been blocked today


#12

Adam G. wrote:

See, this is what I was referring to when I said a program other than
the shell. You’re using the shell here, but you almost certainly don’t
need to be. The program whose output you actually care about if not
/bin/sh, but /usr/local/bin/vpnclient

So, you could change your code in this way:

IO.popen("/usr/local/bin/vpnclient connect xxxxx user xxxxxxx pwd
xxxxxxxxxx",“r+”) do |s|

Aside: as far as a I remember, IO.popen invokes a shell anyway and
passes that command to it.

With Kernel#system there is a multi-arg version which bypasses the
shell:

system("/usr/local/bin/vpnclient",“connect”,xxxx, … etc)

I don’t think you can do this with IO.open - but you can with open3 (see
open3.rb in the standard library)