`ssh host command` can't return the result of the command - perl

There are several computers , I want to use who command to see who is online . I write a script can let me check all the computers but it seems don't return the information of who is online ....
The computer just pause .
I try type this command => ssh f-001 who , and it works . But when I write it to the script , it fails .
here is my code
#Hosts = ("f-001","f-002","f-003","f-004","f-005");
for($i=0;$i<=$#Hosts;$i++)
{
`ssh $Hosts[$i] who`;
getc();
}
thanks
~
~

The results aren't displayed because while you're executing the command, you're not actually displaying its output; you'd need to do something like
print `ssh $Hosts[$i] who`;
Assuming you're using ssh-agent, Kerberos, or something else that lets you login without giving a password, the pause is just the `getc().

Use system() instead:
#Hosts = ("f-001","f-002","f-003","f-004","f-005");
foreach $host (#Hosts)
{
system ("ssh $host who");
}
And please do not iterate with $i.
Run3 is even more convenient.

I would like to add one thing over here is that if you want to do further processiong with the data coming out from the command then remember you need to capture the output like
my #users = `ssh $Hosts[$i] who`;

I think the answers here cover what you need but would emphasize the value of using foreach e.g.:
foreach my $host ("mail1", "san", "ws100.internal"){ say qx/ping -c1 $host/}
How do you plan to deal with the output? Unless you are watching the terminal you're going to want to log or write the results somewhere. Log::Dispatch is pretty simple but you can make your script log to files, rotate them, send email etc.
If you are going to do a lots of remote execution and monitoring be sure to take a look at Rex https://metacpan.org/pod/Rex (and http://www.rexify.com).

Related

How to run a local program with user input in Perl

I'm trying to get user input from a web page written in Perl and send it to a local program (blastp), then display the results.
This is what I have right now:
(input code)
print $q->p, "Your database: $bd",
$q->p, "Your protein is: $prot",
$q->p, "Executing...";
print $q->p, system("blastp","-db $bd","-query $prot","-out results.out");
Now, I've done a little research, but I can't quite grasp how you're supposed to do things like this in Perl. I've tried opening a file, writing to it, and sending it over to blastp as an input, but I was unsuccessful.
For reference, this line produces a successful output file:
kold#sazabi ~/BLAST/pataa $ blastp -db pataa -query ../teste.fs -out results.out
I may need to force the bd to load from an absolute path, but that shouldn't be difficult.
edit: Yeah, the DBs have an environmental variable, that's fixed. Ok, all I need is to get the input into a file, pass it to the command, and then print the output file to the CGI page.
edit2: for clarification:
I am receiving user input in $prot, I want to pass it over to blastp in -query, have the program blastp execute, and then print out to the user the results.out file (or just have a link to it, since blastp can output in HTML)
EDIT:
All right, fixed everything I needed to fix. The big problem was me not seeing what was going wrong: I had to install Tiny:Capture and print out stderr, which was when I realized the environmental variable wasn't getting set correctly, so BLAST wasn't finding my databases. Thanks for all the help!
Write $prot to the file. Assuming you need to do it as-is without processing the text to split it or something:
For a fixed file name (may be problematic):
use File::Slurp;
write_file("../teste.fs", $prot, "\n") or print_error_to_web();
# Implement the latter to print error in nice HTML format
For a temp file (better):
my ($fh, $filename) = tempfile( $template, DIR => "..", CLEANUP => 1);
# You can also create temp directory which is even better, via tempdir()
print $fh "$prot\n";
close $fh;
Step 2: Run your command as you indicated:
my $rc = system("$BLASTP_PATH/blastp", "-db", "pataa"
,"-query", "../teste.fs", "-out", "results.out");
# Process $rc for errors
# Use qx[] instead of system() if you want to capture
# standard output of the command
Step 3: Read the output file in:
use File::Slurp;
my $out_file_text = read_file("results.out");
Send back to web server
print $q->p, $out_file_text;
The above code has multiple issues (e.g. you need better file/directory paths, more error handling etc...) but should start you on the right track.

Perl, Net::SSH2, how to read a whole data from ssh?

I need to read program output using Net::SSH2. My problem is some of that data hided under the bottom of program output. In ssh-mode I need to enter "Return" on my keyboard to look up next. This is awkward for using in a perl-script =).
I know that Net::OpenSSH does it well, but I really need to use Net::SSH2. Does anybody know, how can I get it?
Thnx!
UPD: Some code below
my $ch = $ssh2->channel();
$ch->blocking(0);
$ch->shell();
print $ch "dir\n";
print $_ while <$ch>;
In this code I print output of command with "--More--" prompt of the terminal at the bottom.
Simple Net::OpenSSH "capture" method returns a whole data at the same time:
my #dirlist = $ssh->capture('dir');
Is it possible to do same thing using Net::SSH2?

perl expect running 30 commands remotely

following loop does not produce the output IF I do not have the last exp->expect(5) line (kind of sleep). I guess this is because the loop is running 'too fast' for the 'sent' command to finish. How can i make sure the next command is run only when previous one is finished? I am collecting output in log file.
any advice, pl.
ty.
use Expect;
...
...
foreach my $cmd (#cmd_array){
$exp->expect(3,
[ qr/($|#)/ => sub { shift->send("$cmd");}]
);
exp->expect(5);
}
Maybe you need to use exp_continue like-
use Expect;
...
...
foreach my $cmd (#cmd_array){
$exp->expect(3,
[ qr/($|#)/ => sub { shift->send("$cmd\n");
exp_continue;}]
);
}
Also, i think the statements inside sub{...} runs only if pattern matches as per qr/($|#)/. Also, I would think that after you type in an input or command you would generally press the return key to execute the command hence the $cmd\n.
Is there a reason you can't just let Net::OpenSSH do this for you?
From its docs, you can use the capture() / capture2() methods
(depending on whether you need to capture just STDOUT or STDOUT and
STDERR from the process).
For example:
for my $command (#commands) {
my $result = $ssh->capture($command);
}
I believe that should work, no?
(I've just tested this with a dead-simple script, and it worked fine for me.)

How do I send a password to a command I start with Perl's Expect.pm?

I have the following script,
#!/usr/bin/perl
use strict;
use warnings;
use Net::SSH::Perl;
use Expect;
my $logs = "logs";
open(LOG,'>>',"$logs") or die "can't logs $!\n";
my $domain = 'domain.com';
my #host = qw/host/;
foreach my $host (#host) {
my $cmd = "passwd user1";
my $sshost = join('.', $host, $domain);
my $ssh = Net::SSH::Perl->new("$sshost");
$ssh->login('root');
$ssh->debug();
my ($stdout, $stderr, $exit) = $ssh->cmd($cmd);
print LOG $stdout,"\n";
}
Now my problem is I don't know how to use Expect to send the password after the $cmd is executed and it's time to key in the password. $stdin won't work in this case since we're using HPUX.
Appreciate any guidance and sample, reading the Expect docs don't result something for me.
I don't think that's possible unfortunately. However, Net::SSH::Expect seems to be able to do what you want.
I summarize: you need Expect, and the ssh module has no use.
I'll be more precise: if I understand your source code, your requirement, in human terms, is something like this: log in to a collection of Unix hosts and use passwd(1) to update root's password on each. Do I have that right?
I expect there's frustration in all directions, because variations of this question have been answered authoritatively for at least two decades. That's no reflection on you, because I recognize how difficult it is to find the correct answer.
While Net::SSH is a fine and valuable module, it contributes nothing to the solution of what I understand to be your requirements. You need Expect.
As it turns out, the standard distribution of the Tcl-based Expect includes an example which addresses your situation. Look in http://www.ibm.com/developerworks/aix/library/au-expect/ > for the description of passmass.
Identical functionality can be coded in Expect.pm, of course. Before I exhibit that, though, I ask that original questioner lupin confirm I'm on track in addressing his true requirements.
i think i had a similar issue getting into privileged exec mode with cisco routers, which similarly asks for a password when "en" is invoked. i got around it with a special subroutine:
sub enable { my ($expect_session, $password) = #_;
$expect_session->send("en\n");
$expect_session->expect($timeout,
[ qr/[Pp]assword:/,
sub {
my $expect_session = shift;
$expect_session->send("$password","\n");
exp_continue;
} ],
-re => $prompt,
);
}
but i think the issue is that you're not using Perl's Expect as it's intended to be used. An Expect session be created to manage the SSH connection, then commands are sent to through it. you don't need Net::SSH:Perl at all. here's my $expect_session definition:
my $expect_session = new Expect();
$expect_session->log_stdout(0); # let's keep things quiet on screen; we only want command output.
$expect_session->log_file(".expectlog");
$expect_session->spawn("/usr/bin/ssh -o StrictHostKeyChecking=no $username\#$host")
or die ("\nfor some reason we can't establish an SSH session to $host.\n
it's something to do with the spawn process: $!\n");
there might be a few pieces missing, but hopefully this will get you moving in the right direction. it's a complicated module which i don't understand fully. i wish you the best in luck getting it to do what you want.
Net::OpenSSH can be combined with Expect to do that easyly.
Actually the module distribution contains a sample script that does just that!

How can I quickly find the user's terminal PID in Perl?

The following snippet of code is used to find the PID of a user's terminal, by using ptree and grabbing the third PID from the results it returns. All terminal PID's are stored in a hash with the user's login as the key.
## If process is a TEMINAL.
## The command ptree is used to get the terminal's process ID.
## The user can then use this ID to peek the user's terminal.
if ($PID =~ /(\w+)\s+(\d+) .+basic/) {
$user = $1;
if (open(PTREE, "ptree $2 |")) {
while ($PTREE = <PTREE>) {
if ($PTREE =~ /(\d+)\s+-pksh-ksh/) {
$terminals{$user} = $terminals{$user} . " $1";
last;
}
next;
}
close(PTREE);
}
next;
}
Below is a sample ptree execution:
ares./home_atenas/lmcgra> ptree 29064
485 /usr/lib/inet/inetd start
23054 /usr/sbin/in.telnetd
23131 -pksh-ksh
26107 -ksh
29058 -ksh
29064 /usr/ob/bin/basic s=61440 pgm=/usr/local/etc/logon -q -nr trans
412 sybsrvr
I'd like to know if there is a better way to code this. This is the part of the script that takes longest to run.
Note: this code, along with other snippets, are inside a loop and are executed a couple of times.
I think the main problem is that this code is in a loop. You don't need to run ptree and parse the results more than once! You need to figure out a way to run ptree once and put it into a data structure that you can use later. Probably be some kind of simple hash will suffice. You may even be able to just keep around your %terminals hash and keep reusing it.
Some nitpicks...
Both of your "next" statements seem
unnecessary to me... you should be
able to just remove them.
Replace
$terminals{$user} = $terminals{$user} . " $1";
with:
$terminals{$user} .= " $1";
Replace the bareword PTREE which you
are using as a filehandle with
$ptreeF or some such... using
barewords became unnecessary for
filehandles about 10 years ago :)
I don't know why your $PID variable
is all caps... it could be confusing
to readers of your code because it
looks like there is something
special about that variable, and
there isn't.
I think you'll get the best performance improvement by avoiding the overhead of repeatedly executing an external command (ptree, in this case). I'd look for a CPAN module that provides a direct interface to the data structures that ptree is reading. Check the Linux:: namespace, maybe? (I'm not sure if ptree is setuid; that may complicate things.)
The above advice aside, some additional style and robustness notes based on the posted snippet only (forgive me if the larger code invalidates them):
I'd start by using strict, at the very least. Lexical filehandles would also be a good idea.
You appear to be silently ignoring the case when you cannot open() the ptree command. That could happen for many reasons, some of which I can't imagine you wanting to ignore, such as…
You're not using the full path to the ptree command, but rather assuming it's in your path—and that the one in your path is the right one.
How many users are on the system? Can you invert this? List all -pksh-ksh processes in the system along with their EUIDs, and build the map from that - that might be only one execution of ps/ptree.
I was thinking of using ps to get the parents pid, but I would need to loop this to get the great-grandparent's pid. That's the one I need. Thanks. – lamcro
Sorry, there are many users and each can have up to three terminals open. The whole script is used to find those terminals that are using a file. I use fuser to find the processes that use a file. Then use ptree to find the terminal's pid. – lamcro
If you have (or can get) a list of PIDs using a file, and just need all of the grand-parents of that PID, there's an easier way, for sure.
#!perl
use warnings;
use strict;
#***** these PIDs are gotten with fuser or some other method *****
my($fpids) = [27538, 31812, 27541];
#***** get all processes, assuming linux PS *****
my($cmd) = "ps -ef";
open(PS, "$cmd |") || die qq([ERROR] Cannot open pipe from "$cmd" - $!\n);
my($processlist) = {};
while (<PS>) {
chomp;
my($user, $pid, $ppid, $rest) = split(/ +/, $_, 4);
$processlist->{$pid} = $ppid;
}
close PS;
#***** lookup grandparent *****
foreach my $fpid (#$fpids) {
my($parent) = $processlist->{$fpid} || 0;
my($grandparent) = $processlist->{$parent} || 0;
if ($grandparent) {
#----- do something here with grandparent's pid -----
print "PID:GRANDPID - $fpid:$grandparent\n";
}
else {
#----- some error condition -----
print "ERROR - Cannot determine GrandPID: $fpid ($parent)\n";
}
}
Which for me produces:
ERROR - Cannot determine GrandPID: 27538 (1)
PID:GRANDPID - 31812:2804
PID:GRANDPID - 27541:27538
Have you considered using 'who -u' to tell you which process is the login shell for a given tty instead of using ptree? This would simplify your search - irrespective of the other changes you should also make.
I just did some trivial timings here based on your script (calling "cat ptree.txt" instead of ptree itself) and confirmed my thoughts that all of your time is spent creating new sub-processes and running ptree itself. Unless you can factor away the need to call ptree (maybe there's a way to open up the connection once and reuse it, like with nslookup), you won't see any real gains.