Ruby Net/SSH exec - get pid of remote process - ruby

I am launching a nohup remote script with Ruby Net/SSH.
Net::SSH.start(ip_address, user, options) do |ssh|
script = File.join(remote_path, 'run_case.py')
cmd = "nohup python #{script} #{args} < /dev/null &"
ssh.exec(cmd)
end
All stdout and stderr is saved to a file on the remote machine.
Is it possible to get the PID of the remote script so that I can kill it if needed?
EDIT 1:
I have modified the script as suggested.
Net::SSH.start(ip_address, user, options) do |ssh|
script = File.join(remote_path, 'run_case.py')
cmd = "nohup python #{script} #{args} < /dev/null & echo $! > save_pid.txt"
ssh.exec(cmd)
pid = ssh.exec!("cat save_pid.txt")
puts mesh_pid
end
It complains that it cannot find the file. Is this because the file does not exist yet? I would prefer to avoid any sleep command if possible
EDIT 2: Maybe this is not elegant but it works. I have created a second ssh session and used pgrep.
Net::SSH.start(ip_address, user, options) do |ssh|
script = File.join(remote_path, 'run_case.py')
cmd = "nohup python #{script} #{args} < /dev/null &"
ssh.exec(cmd)
end
Net::SSH.start(ip_address, user, options) do |ssh|
cmd = "python #{script} #{args}"
mesh_pid = ssh.exec!("pgrep -f '#{cmd}'")
puts mesh_pid
end

You should be able to determine the PID (and store it in a file) as follows:
Net::SSH.start(ip_address, user, options) do |ssh|
script = File.join(remote_path, 'run_case.py')
cmd = "nohup python #{script} #{args} < /dev/null & echo $! > save_pid.txt"
ssh.exec(cmd)
end
In a script, $! represents the PID of the last process executed. If you need to kill the process, you can do it via:
kill -9 `cat save_pid.txt`
rm save_pid.txt

Related

How to change rvm gemset over ssh on os x server

ok
I don't know how to change rvm version over ssh under os x server.
What I do:
Login on server over ssh
run script (below) and catch error
Error: 'rvm is not a funciton, many-many-words'
What I have as script:
use File::Spec;
my $server_directory = File::Spec->catfile($ENV{HOME},'MyProject');
my $exec_file = File::Spec->catfile($server_directory,'run_script.rb');
my $run_ruby_script = qq'bundle exec ruby $exec_file'.' '.join(' ',#ARGV);
# reload bash profile
print qx(source $ENV{HOME}/.bash_profile);
print qx(source $ENV{HOME}/.bashrc);
# reload ruby
print qx(source $ENV{HOME}/.rvm/scripts/rvm);
my $ruby_setup = qq([[ -s "$ENV{HOME}/.rvm/scripts/rvm" ]] && source "$ENV{HOME}/.rvm/scripts/rvm");
print $ruby_setup. "\n";
# change directory
chdir($server_directory);
# configure gemset name
my $version = qx(cat .ruby-version);
chomp($version);
my $gemset = qx(cat .ruby-gemset);
chomp($gemset);
my $change_rvm_gemset = qq(rvm use $version\#$gemset);
print qx($ruby_setup && $change_rvm_gemset);
print qx(rvm current);
Ok, after all.
def exec_via_bash(line)
puts %Q(#{line})
exec = 'bash -c "#{line}"'
puts `#{exec}`
end
def RubySetup
# reload bash profile
homedir = ENV['HOME']
exec_via_bash %Q(source #{homedir}/.bash_profile);
exec_via_bash %Q(source #{homedir}/.bashrc);
# reload ruby
exec_via_bash %Q(source #{homedir}/.rvm/scripts/rvm);
ruby_setup = %Q([[ -s "#{homedir}/.rvm/scripts/rvm" ]] && source "#{homedir}/.rvm/scripts/rvm")
puts ruby_setup
ruby_setup
end
if ARGV.empty?
puts "there is not enough arguments passed. maybe you forget ruby file to exec?"
exit(1)
end
ruby_script_path = ARGV.shift;
exec_file_absolute_path = File.expand_path(ruby_script_path)
unless File.exists? exec_file_absolute_path
puts "file #{exec_file_absolute_path} doesn't exists!"
exit(1)
end
exec_file_directory = File.dirname(exec_file_absolute_path)
exec_bundle = %Q'bundle exec ruby #{exec_file_absolute_path}' + ' ' + ARGV.join(' ')
# change directory
Dir.chdir(exec_file_directory);
# print %x(ls);
# configure gemset name
version = %x(cat .ruby-version).strip;
gemset = %x(cat .ruby-gemset).strip;
change_rvm_gemset = %Q(rvm use #{version}\##{gemset});
ruby_setup = RubySetup()
exec_bash_login_line = [ruby_setup, change_rvm_gemset, exec_bundle].join ' && ';
puts 'exec bash login line: ' + exec_bash_login_line
forced = %Q(bash --login -c '#{exec_bash_login_line}');
puts forced, "\n";
puts %x(#{forced});
ok, this script is not a kind of beauty, but it works well.
Example of usage?
ruby script.rb ~/bla/bla/bla/run_your_program.rb --first_argument --second_argument a,b,c --etc
As I said before:
I've already on the server via ssh.
So, I need to run scripts via launchd.
And I should do it with
# part of launchd worker
<string>bash</string>
<string>-c</string>
<string>ruby ~/PathToCharmScript.rb -r a</string>
P.S:
Please, help me with improvements of this script for others!
Here is a gist: wow_this_works

Escaping an ampersand character ('&') in a password for a Ruby script

I have a password like 'X&Y' and I am trying to run a Ruby script that opens an SSH session, but the script breaks at the & character like :
*server: X
*server: bash: Y: command not found
Escaping the character like & doesn't help either. Ideas appreciated!
The code where it happens is at the ssh.exec:
pass="X\&Y"
Net::SSH.start( host_name, user, :password => pass ) do |ssh|
#do stuff
command = "sudo -S rm file"
cmd = "#{pass}|#{command}"
ssh.exec(cmd) do |ch, stream, data|
puts "*server:" + data.inspect
end
end
You can use ssh like without & getting any special meaning:
ssh -t -t user#localhost "echo 'abc&def'"
abc&def
Connection to localhost closed.

How to get the pid of external process

I ran the external python script by system(run_command)
But I want to get the pid of the running python script,
So I tried to use fork and get the pid,
But the returned pid was the pid of the fork's block, not the python process.
How could I get the pid of the python process, thanks.
arguments=[
"-f #{File.join(#public_path, #streaming_verification.excel.to_s)}",
"-duration 30",
"-output TEST_#{#streaming_verification.id}"
]
cmd = [ "python",
#automation[:bin],
arguments.join(' ')
]
run_command = cmd.join(' ').strip()
task_pid = fork do
system(run_command)
end
(Update)
I tried to use the spawn method.
The retuned pid was still not the pid of the running python process.
I got the pid 5177 , but the actually pid,I wanted, is 5179
run.sh
./main.py -f ../tests/test_setting.xls -o testing_`date +%s` -duration 5
sample.rb
cmd = './run.sh'
pid = Process.spawn(cmd)
print pid
Process.wait(pid)
According to Kernel#system:
Executes command… in a subshell. command… is one of following forms.
You will get pid of subshell, not the command.
How about using Process#Spwan? It returns the pid of the subprocess.
run_command = '...'
pid = Process.spawn(cmd)
Process.wait(pid)

Read STDOUT and STDERR from subprocess continiously

I'm using IO.popen to start a subprocess, but I only get the result of everything that happened in the time it took for the subprocess to run (sometimes 5 minutes or whatever) when the subprocess exits. I really need to be able to see everything the subprocess writes to stderr and stdout as-and-when it happens.
So far I could not find anything that works like this, but I'm sure it's possible.
if you need to get output in real time i would recommend to use stdlib PTY instead of popen
something like this:
require 'pty'
cmd = 'echo a; sleep 1; cat /some/file; sleep 1; echo b'
PTY.spawn cmd do |r, w, pid|
begin
r.sync
r.each_line { |l| puts "#{Time.now.strftime('%M:%S')} - #{l.strip}" }
rescue Errno::EIO => e
# simply ignoring this
ensure
::Process.wait pid
end
end
exit "#{cmd} failed" unless $? && $?.exitstatus == 0
> 33:36 - a
> 33:37 - cat: /some/file: No such file or directory
> 33:38 - b
this way you get output instantly, just as in terminal
You might want to use Open3.popen3 from standard library, it gives access to stdin, stdout, and stderr as streams.

How to run multiple external commands in the background in ruby

Given this Unix shell script:
test.sh:
#!/bin/sh
sleep 2 &
sleep 5 &
sleep 1 &
wait
time ./test.sh
real 0m5.008s
user 0m0.040s
sys 0m0.000s
How would you accomplish the same thing in Ruby on a Unix machine?
The sleep commands are just an example, just assume that they are long running external commands instead.
Straight from Process#waitall documentation:
fork { sleep 0.2; exit 2 } #=> 27432
fork { sleep 0.1; exit 1 } #=> 27433
fork { exit 0 } #=> 27434
p Process.waitall
Of course, instead of using Ruby's sleep, you can call whichever external command using Kernel#system, or backtick operator.
#!/usr/bin/env ruby
pids = []
pids << Kernel.fork { `sleep 2` }
pids << Kernel.fork { `sleep 5` }
pids << Kernel.fork { `sleep 1` }
pids.each { |pid| Process.wait(pid) }
To answer my own question (just found out about this):
​#!/usr/bin/ruby
spawn 'sleep 2'
spawn 'sleep 5'
spawn 'sleep 1'
Process.waitall
On ruby 1.8 you need to install the sfl gem and also require this:
require 'rubygems'
require 'sfl'

Resources