Re: Bash: command output to variable



John W. Krahn wrote:
Steffen Schuler wrote:

Dive wrote:

I have a small script which works like a mini top command. It uses ps to
list top 10 cpu intensive processes. This works ok but what I now want
to do is log when a process hits 10 or more cpu%.

Something like this:

#! /bin/bash
rm toplog
while true
do
clear
ps -eao pcpu,pmem,comm --no-headers --sort=-pcpu | head
a=`ps -eao pcpu,pmem,comm --no-headers --sort=-pcpu | head |
cut -f 2 -d ' '`
if $a >= 10.0 # <-- this is the problem
then
echo -n `date +%T` " ">> toplog
ps -eao pcpu,pmem,comm --no-headers --sort=-pcpu |
head -n 1 >> toplog
fi
sleep 5
done
# EOF

Sample output:

1.5 0.8 slrn
0.8 1.0 gkrellm
0.5 4.2 firefox-bin
0.5 5.1 X
0.1 0.6 vim
0.0 0.3 mrxvt
0.0 0.7 mrxvt
0.0 2.1 gaim
0.0 0.6 adesklets
0.0 0.8 fluxbox

[snip]

An optimized perl solution:


Optimized?


--------------- begin(top.pl) -------------------------
#!/usr/bin/env perl


You are calling the env program to run perl. Why not just call perl directly?

#!/usr/bin/perl


use strict;
use warnings;
use diagnostics;
use English;


perldoc English
[snip]
PERFORMANCE
This module can provoke sizeable inefficiencies for regular
expressions, due to unfortunate implementation details. If performance
matters in your application and you don't need $PREMATCH, $MATCH, or
$POSTMATCH, try doing

use English qw( -no_match_vars ) ;

. It is especially important to do this in modules to avoid penalizing
all applications which use them.


our $BSD_STYLE = 0; # TODO: should be set to the correct value
my $logfile = "toplog";
$English::OUTPUT_AUTOFLUSH = 1;
my $clear_string = `clear`;

open(LOG, ">$logfile") or
die "can't open \"$logfile\": $!";

while (1) {
my $first_line = "\n";
my $with_great_value = 0;
print $clear_string;
if (open(PSOUT,
"ps -eao pcpu,pmem,comm --no-headers --sort=-pcpu |")) {
for (my $i = 0; $i < 10; ++$i) {
if (defined($_ = <PSOUT>)) {
print;
if ($i == 0) {
$first_line = $_;
s/^\s*//;
my @F = split /\s+/;


s/^\s*// modifies every string whether it begins with whitespace or not. For
efficiency just use split with no arguments:

my @F = split;

But since you are only using $F[0] you can simply do:

my ( $F ) = split;

Which is more efficient because it stops splitting after it fills the
variable(s) in the list.



$with_great_value = 1 if ($F[0] >= 10.0);
}
}
if ($with_great_value) {
my @T = localtime(time);
printf LOG "%02d:%02d:%02d %s", @T[2,1,0], $first_line;


The POSIX::strftime function is usually more efficient:

use POSIX 'strftime';
print strftime( '%T', localtime ), " $first_line";

Even if you don't use that you can avoid creating the @T array:

printf LOG '%02d:%02d:%02d %s', ( localtime )[ 2, 1, 0 ], $first_line;


}
}
close(PSOUT);


You should also verify that the piped open filehandle was closed correctly:

perldoc -f close



}
my $key = getKeyQuiet(5);


The Perl FAQ has some code that do that without running an external program:

perldoc -q "How can I read a single character"


if ($key =~ /q/i) {
exit 0;
}
}

# reads a key without echo and with a timeout of $timeout seconds
sub getKeyQuiet
{

[snip]




John

Thank you very much John for your corrections of my code. You have done a superb job. The corrected code follows:

------------------------- (top.pl) ---------------------------------
#!/usr/bin/env perl

# more optimal but less portable as first line:
#!/usr/bin/perl

# to quit the command you have to enter 'q' or 'Q'

# some pragmas to help error finding
use strict;
use warnings;
use diagnostics;


# perlvar: '-no_match_vars' is recommended for performance reasons
use English '-no_match_vars';

use POSIX 'strftime';

# should be installed from CPAN
use Term::ReadKey;

my $logfile = "toplog";
$English::OUTPUT_AUTOFLUSH = 1;
my $clear_string = `clear`;

open(TTY, "</dev/tty");
ReadMode "normal";

open(LOG, ">$logfile") or
die "can't open \"$logfile\": $!";

while (1) {
my $first_line = "\n";
print $clear_string;
if (open(PSOUT,
"ps -eao pcpu,pmem,comm --no-headers --sort=-pcpu |")) {
for (my $i = 0; $i < 10; ++$i) {
if (defined($_ = <PSOUT>)) {
print;
my ($F) = split;
print LOG strftime( '%T', localtime ), " $_" if $F >= 10.0;
}
}
} else {
die "can't open pipe from command: $!";
}

my $key = readKeyQuit(5);
if (defined($key) and $key =~ /q/i) {
exit 0;
}
}

close(PSOUT) or die "cant't close piped command: $!";
close(LOG) or die "can't close \"$logfile\": $!";
close(TTY) or die "can't close \"/dev/tty\": $!";


# reads key with a timeout of 5 seconds without echo
sub readKeyQuit
{
my $timeout = shift;
my $key = '';
eval {
# start alarm handler after $timeout seconds
local $SIG{ALRM} = sub { die "alarm clock restart" };
alarm $timeout;

ReadMode "raw";

# ReadKey 5, *TTY; doesn't seem to work
$key = ReadKey 0, *TTY;

ReadMode "normal";

# start alarm handler immediately
alarm 0;
};

# if the Perl syntax error message string exists and is not
# "alarm clock restart" then die
if ($EVAL_ERROR and $EVAL_ERROR !~ /alarm clock restart/) { die };

return $key;
}
---------------------- end(top.pl) -------------------------------

Greetings from Munich,

Steffen
.



Relevant Pages

  • Re: Bash: command output to variable
    ... ReadMode "normal"; ... # reads key with a timeout of 5 seconds without echo ... alarm $timeout; ... # if the Perl syntax error message string exists and is not ...
    (comp.unix.shell)
  • Re: Bash: command output to variable
    ... list top 10 cpu intensive processes. ... An optimized perl solution: ... # reads a key without echo and with a timeout of $timeout seconds ... alarm $timeout; ...
    (comp.unix.shell)
  • Re: Time out question
    ... There are quite a lot of problems with alarm() ... from time_limit import time_limit, TimeOut ... raise TimeOut() ... # Install new handler remembering old ...
    (comp.lang.python)
  • Re: How to time out a forked command but still see output?
    ... Haven't done Perl in years. ... I tried a few things involving alarm() and eval but couldn't get them ... I also tried something like redirecting CMD to STDOUT but when I do ... die "\nTimed out command $command after waiting ...
    (comp.lang.perl.misc)
  • Re: creating a socket connection timeout in "C" linux
    ... What makes alarm() unreliable? ... The 'easy, unreliable timeout' is setting ... the SIGALRM may actually be posted to the process before the connect ... Relying on SIGALRM to interrupt a blocked system call could ...
    (comp.unix.programmer)