I wrote a simple script to manage the download time ( start and finish ) with wget in Linux-Gnu with Perl. There is no problem and everything works good, except I wish I could read a key from keyboard when the process is running.
I show a simple movement animation on the screen that I do not want to stop it and then read the key.
for example like mplayer or mpv that when you run it on the comman-line, you can press q to exit or s to take a picture from the screen.
A part of the script:
do {
system( "clear" );
($h, $m, $s) = k5mt::second_to_clock( $till_shutdown );
set_screen();
set_screen( 24 );
set_screen();
say "download was started ...";
set_screen();
set_screen( 24 );
set_screen();
printf "till finish: %02d %02d %02d\n", $h, $m, $s;
set_screen();
set_screen( 24 );
set_screen();
say "wget pid: [$wget_pid]";
set_screen();
set_screen( 24 );
set_screen();
$waiting_temp .= $animation[ $counter_animation++ ];
say $waiting_temp;
if( length( $waiting ) == $counter_animation ){
$waiting_temp = "";
$counter_animation = 0;
}
set_screen();
set_screen( 24 );
sleep 1;
$till_shutdown--;
} while( $till_shutdown );
the words waiting till finish..... is shown consecutively ( without interruption ) and I want to read a key like q to exit from the program.
UPDATE
I am looking for a solution with as many option as I want, if I have wanted just for exit from the program I simply tell the user to press Ctrl + C
Is it possible with scripting in Perl? or not? If it is, How?
NOTE: if it is possible without any modules please say the solution
without any module, and if not, okay no problem
However, thank you so much.
Assuming your program has a central loop, or if you can simply fit keyboard checks into the processing, you are better off using Term::ReadKey than trying to fit in fork and handling the necessary inter-process communication
Calling ReadKey(-1) will do a non-blocking read, which will return a single character if a key has been hit, or undef otherwise. (You may supply a second parameter which is the IO channel to be used, but it will default to STDIN.)
I suggest you run this example. I have used sleep 1 as a dummy for your loop processing
use strict;
use warnings 'all';
use feature 'say';
use Term::ReadKey;
my $n;
while () {
my $key = ReadKey(-1);
say "key $key entered" if defined $key;
sleep 1;
say ++$n;
}
This will work for you
use 5.010;
use strict;
use Term::ReadKey;
ReadMode 4; # It will turn off controls keys (eg. Ctrl+c)
my $key;
# It will create a child so from here two processes will run.
# So for parent process fork() will return child process id
# And for child process fork() will return 0 id
my $pid = fork();
# This if block will execute by the child process and not by
# parent because for child $pid is 0
if(not $pid){
while(1){
# Do your main task here
say "hi I'm sub process and contains the main task";
sleep 2;
}
}
# Parent will skip if block and will follow the following code
while (1) {
$key = ReadKey(-1); # ReadKey(-1) will perform a non-blocked read
if($key eq 'q'){ # if key pressed is 'q'
`kill -9 $pid`; # then if will run shell command kill and kill
# the child process
ReadMode 0; # Restore original settings for tty.
exit; # Finally exit from script
} elsif( $key eq 'h' ) {
say "Hey! you pressed $key key for help";
} elsif( $key ne '' ) {
say "Hey! You pressed $key";
}
}
References:
Term::ReadKey
Fork in Perl
If you are not worried about a little delay, you can play with select and alarm to try to handle user input.
Using select you can handle the STDIN and using alarm you can wait for a few seconds or less for user input.
#!/usr/bin/env perl
use common::sense;
use IO::Select;
my $sel = IO::Select->new();
$sel->add( \*STDIN );
$|++;
my $running = 1;
while ($running) {
eval {
local $SIG{ALRM} = sub { die 'Time Out'; };
alarm 0.5;
if ( $sel->can_read(0) ) {
my $input = <STDIN>;
chomp $input;
print "STDIN > $input\n";
$running = 0;
}
};
if ( !$# ) {
select( undef, undef, undef, 0.5 );
print ".";
}
}
print "\nEnd Of Script\n";
I told you to try to use Term::ReadKey :-)
Here we go:
!/usr/bin/env perl
use common::sense;
use Term::ReadKey;
my $running = 1;
my $key;
while ($running) {
ReadMode 4; # Turn off controls keys
while ( not defined( $key = ReadKey(-1) ) ) {
select( undef, undef, undef, 0.5 );
print '.';
}
print "\nSTDIN> $key\n";
ReadMode 0;
}
print "\nEnd Of Script\n";
Now you just have to handle signals like quit or int to exit the loop, but using this you can capture the input and do whatever you want.
Related
How can I read user input using Term::ReadLine without having a newline character printed out when the user presses Enter?
The reason why I want to do this is because I want to read user input from a prompt at the very bottom of the screen (as in less or vim). Currently, pressing Enter causes the screen to scroll down, and that can be an issue. Also, I'd like to avoid having to appeal to ncurses at this point.
Setting
$term->Attribs->{echo_control_characters} to 0, undef, or off doesn't seem to work.
#!perl
use Term::ReadLine;
use Term::ReadKey;
my $term = new Term::ReadLine ('me');
$term->ornaments(0);
$term->Attribs->{echo_control_characters} = 0;
print STDERR "\e[2J\e[s\e[" . ( ( GetTerminalSize() ) [1] ) . ";1H"; # clear screen, save cursor position, and go to the bottom;
my $input = $term->readline('> ');
print STDOUT "\e[uinput = $input\n"; # restore cursor position, and print
I could do that using Term::ReadKey with cbreak as read mode:
#!perl
use Term::ReadKey;
ReadMode 'cbreak';
my ( $k, $input );
print STDERR "> ";
while ( defined ( $k = ReadKey 0 ) and $k !~ /\n/ )
{
$input .= $k;
print STDERR $k;
}
print STDOUT "input = $input\n";
ReadMode 'restore';
But then I wouldn't be able to use Term::ReadLine features, such as history, completion, and line editing.
You can set the rl_getc_function to intercept the carriage return before it is printed as shown in this question. The following works for me:
use strict;
use warnings;
BEGIN {
$ENV{PERL_RL} = "Gnu";
}
use Term::ReadLine;
use Term::ReadKey;
my $term = Term::ReadLine->new('me');
my $attr = $term->Attribs;
$term->ornaments(0);
$attr->{getc_function} = sub {
my $ord = $term->getc($attr->{instream});
if ( $ord == 13 ) { # carriage return pressed
$attr->{done} = 1;
return 0;
}
return $ord;
};
print STDERR "\e[2J\e[s\e[" . ( ( GetTerminalSize() ) [1] ) . ";1H";
my $input = $term->readline('> ');
print STDOUT "\e[uinput = $input\n"; # restore cursor position, and print
I was having a difficulty about perl script..I have a script that includes a loop but never breaks out of it once it gets the exit status zero.The loop will run only once the exit status results a 1 upon checking initially by the "if" statement.
my $a = "/home/vivek/generated_mdsum_reference.out";
my $b = "/home/vivek/generated_mdsum_new.out";
sub CHECK {
print "\n";
print "\n";
print "\n\tGenerating MD5SUM ....";
my $dumpfile = "/home/vivek/file_dump.dmp";
print "\n";
# my $md5sum = system("md5sum $dumpfile");
my $md5sum = `md5sum $dumpfile`;
print "\n";
print "\nChecksum: $md5sum.";
# Put checksum in file
my $ochksumfile = "/home/vivek/generated_mdsum_new.out";
open (my $fh, '>', "$ochksumfile") or die "no file:$!";
my $output = $md5sum;
die "$!" if $?;
$value = (split / /, "$output")[0];
print $fh $value;
my $status =compare($b, $a);
}
my $status =compare($b, $a);
if ( $status == 1 ){
do
CHECK;
until ($status == 0 ) {
print "\n\tfiles are now Ok. Exiting..";
print "\n\t";
}
All the variables I have set in there works fine, I only ended up in the until loop which it keeps on running endlessly which I think it cannot get through until the rest of the function "CHECK"
Please help me guys.
Let's look at the block in question
my $status = compare($b, $a);
if ( $status == 1 ) {
do
CHECK;
until ($status == 0 ) {
print "\n\tfiles are now Ok. Exiting..";
print "\n\t";
}
whether you meant it or not, this is equivalent to
my $status = compare($b, $a);
if ( $status == 1 ) {
do CHECK;
until ( $status == 0 ) {
print "\n\tfiles are now Ok. Exiting..";
print "\n\t";
}
So the contents of your until loop is just two print statements, which aren't going to change the value of $status so it will loop forever
What I think you meant is
my $status = compare($b, $a);
if ( $status == 1 ) {
do {
CHECK;
} until $status == 0;
print "\n\tfiles are now Ok. Exiting..";
print "\n\t";
}
which will repeatedly call CHECK until $status is set to zero
EXCEPT THAT you should always use lower-case characters in your local identifiers. Upper-case is reserved for global identifiers such as package names. In this situation you have unwittingly created a CHECK block
perldoc perlmod says this
Five specially named code blocks are executed at the beginning and at the end of a running Perl program. These are the BEGIN , UNITCHECK , CHECK , INIT , and END blocks.
These code blocks can be prefixed with sub to give the appearance of a subroutine (although this is not considered good style). One should note that these code blocks don't really exist as named subroutines (despite their appearance). The thing that gives this away is the fact that you can have more than one of these code blocks in a program, and they will get all executed at the appropriate moment. So you can't execute any of these code blocks by name.
So because "these code blocks don't really exist as named subroutines" your program will only call CHECK during compilation, and subsequent explicit calls will be ignored
So rename your subroutine to check and chnage your code to this
my $status = compare($b, $a);
if ( $status == 1 ) {
do {
CHECK;
} until $status == 0;
print "\n\tfiles are now Ok. Exiting..";
print "\n\t";
}
and all will be well
I do not understand why the until loop should stop.
In short, you get an md5 sum for a file in your "CHECK" sub.
sub CHECK {
open (my $fh, '>', "/home/vivek/generated_mdsum_new.out");
my $dump_file = "/home/vivek/file_dump.dmp";
my $md5sum = `md5sum $dump_file`;
my $value = (split(" ",$md5sum))[0];
print $fh $value;
close $fh;
compare($a,$b);
# question: what is in $a / $b and what does compare do ?
}
Now you compare $a and $b and return that result.
Assuming $a / $b contain something useful, there is nothing in the until block or CHECK block that I see to change either, so the comparison would keep returning the same result ( assuming "compare" compares and does not change anything, which would be a bad naming if it did ).
So in the until block, do something to influence the CHECK, otherwise you are stuck in a loop.
You should always use lower-case characters in your local identifiers. Upper-case is reserved for global identifiers such as package names. In this situation you have unwittingly created a CHECK block
perldoc perlmod says this
Five specially named code blocks are executed at the beginning and at the end of a running Perl program. These are the BEGIN , UNITCHECK , CHECK , INIT , and END blocks.
These code blocks can be prefixed with sub to give the appearance of a subroutine (although this is not considered good style). One should note that these code blocks don't really exist as named subroutines (despite their appearance). The thing that gives this away is the fact that you can have more than one of these code blocks in a program, and they will get all executed at the appropriate moment. So you can't execute any of these code blocks by name.
So because "these code blocks don't really exist as named subroutines" your program will only call CHECK during compilation. That means explicit calls to CHECK will be ignored, and $status never changes
Change your subroutine to check and all will be well
The problem is here:
until ($status == 0 ) {
print "\n\tfiles are now Ok. Exiting..";
print "\n\t";
}
Nowhere in the loop are you changing the value of $status.
The "do CHECK;" isn't part of the loop.
Would eval a file by the name of the return value of the CHECK sub
If it had actually called that sub.
Which it won't because it isn't actually a subroutine.
The other form of until is:
do {
CHECK; # doesn't work as this is a special name
} until $status == 0;
print "\n\tfiles are now Ok. Exiting..";
print "\n\t";
Which is still a problem as CHECK is a special name for a block that only gets called once at CHECK time, that you can't actually call.
Plus the code in CHECK would always have the same result, so calling it repeatedly doesn't make sense, and could still result in an infinite loop if it didn't work the first time.
This is how I might have written it
This is a first pass of making your code make sense to me, also fixing some of the errors pointed out above.
I also changed $a and $b to $ref and $new since $a and $b are reserved variables.
I improved it by using modules that come with Perl, so that I don't have to check the return values of open() and close() ( autodie ), or rely on conventions of a particular platform ( Digest::MD5, and File::Spec::Functions ).
I assume that you loaded File::Compare.
I removed the setting of $status from the check sub to reduce the use of global variables.
#! /usr/bin/env perl
use strict;
use warnings;
use 5.10.1; # set minimum version which was released in 2009
use autodie;
use File::Spec::Functions qw' catfile catdir rootdir curdir ';
use Digest::MD5;
use File::Compare qw' compare ';
# should be // not ||, but it will work if your dir isn't named "0" or ""
my $basedir = $ENV{HOME} || catdir rootdir, qw' home vivek ';
# try the current directory if it doesn't exist
$basedir = curdir unless -d $basedir;
my $ref = catfile $basedir, 'generated_mdsum_reference.out';
my $new = catfile $basedir, 'generated_mdsum_new.out';
my $dumpfile = catfile $basedir, 'file_dump.dmp';
# forward declare so that we can put them at the end
sub md5_hex_file;
sub md5_sum;
sub check;
#-------------------------------------------------------
if ( compare($new, $ref) != 0 ){
if ( check($dumpfile,$new) == 0 ){
print "\n\tfiles are now Ok. Exiting..\n";
} else {
local $| = 1; # make sure the output is flushed to STDOUT
print "\n\tfiles are NOT OK. Exiting..\n";
exit 1;
}
}
#-------------------------------------------------------
# helper subroutines
sub md5_hex_file {
my ($filename) = #_;
# let Digest::MD5 read the file for us
my $ctx = Digest::MD5->new;
{ # limit scope of $fh
open my $fh, '<', $filename;
binmode $fh;
$ctx->addfile( $fh );
close $fh;
}
$ctx->hexdigest;
}
# no longer necessary
sub md5_sum {
my ($filename) = #_;
# `md5sum -b $filename`
md5_hex_file($filename) . " $filename\n";
}
sub check {
my ( $infile, $outfile ) = #_
print "\n" x 3, "\tGenerating MD5SUM ....\n";
my $md5_hex = md5_hex_file $infile;
print "\n" x 2, "Checksum: $md5_hex.\n";
# Put checksum in file
{
open my $out_fh, '>', $outfile;
print {$out_fh} $md5_hex;
close $out_fh;
}
my $status = compare $new, $ref;
return $status if $status == 0;
# add a newline and hope that fixes it
{
open my $out_fh, '>>', $outfile;
print {$out_fh} "\n";
close $out_fh;
}
return compare $new, $ref;
}
Really I think you could have just used these "one-liners"
$ perl -Mautodie -MDigest::MD5 -e \
'open $fh, q[<], shift;
print Digest::MD5->new->addfile($fh)->hexdigest' \
file_dump.dmp > generated_mdsum_new.out
$ perl -MFile::Compare -e \
'if ( compare(shift(),shift()) == 0 ){
print qq[They match\n]
} else {
print qq[They don\'t match\n]
}' \
generated_mdsum_new.out generated_mdsum_reference.out
I have a subroutine that normally takes 1 second to run. Sometimes, it can run infinitely. I want to move on in the code if the subroutine is taking too long (> 10 seconds) and ignore that run of that subroutine. Here is what I have so far using alarm.
use Win32::OLE;
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
alarm 10; # schedule alarm in 10 seconds
&do_the_subroutine;
alarm 0; # cancel the alarm
};
if ($#) {
$error_string .= $script;
#Do something else if the subroutine took too long.
}
do_the_subroutine{
# use existing instance if Excel is already running
eval {$ex = Win32::OLE->GetActiveObject('Excel.Application')};
die "Excel not installed" if $#;
unless (defined $ex) {
$ex = Win32::OLE->new('Excel.Application', sub {$_[0]->Quit;})
or die "Oops, cannot start Excel";
}
# get a new workbook
$book = $ex->Workbooks->Add;
# write to a particular cell
$sheet = $book->Worksheets(1);
$sheet->Cells(1,1)->{Value} = "foo";
# write a 2 rows by 3 columns range
$sheet->Range("A8:C9")->{Value} = [[ undef, 'Xyzzy', 'Plugh' ],
[ 42, 'Perl', 3.1415 ]];
# print "XyzzyPerl"
$array = $sheet->Range("A8:C9")->{Value};
for (#$array) {
for (#$_) {
print defined($_) ? "$_|" : "<undef>|";
}
print "\n";
}
# save and exit
$book->SaveAs( 'test.xls' );
undef $book;
undef $ex;
}
&do_the_subroutine never returns so I'm not able to move on. I'm also not able to put this block of code inside that subroutine. Any thoughts?
I suspect that what you want to do is simply not natively possible with alarm on Windows.
From perldoc perlport:
alarm Emulated using timers that must be explicitly polled whenever
Perl wants to dispatch "safe signals" and therefore cannot
interrupt blocking system calls. (Win32)
When I run this script it works as wanted as long as I don't redirect STDOUT to a file. When I redirect STDOUT to a file, the output after the ENTER: breaks. Why doesn't the script work any more if I redirect the STDOUT to a file?
#!/usr/bin/env perl
use warnings;
use strict;
use Term::ReadKey;
use Unicode::GCString;
use Term::ANSIScreen qw( :all );
select( *STDERR );
$| = 1;
print YELLOW "YELLOW\n";
print RESET;
print "ENTER:";
my $dummy = <>;
print savepos;
my $str = '';
print_readline( $str );
for my $s ( 33 .. 126 ) {
$str .= ' ' . chr( $s ) x 5;
print_readline( $str );
}
print "\n";
sub print_readline {
my ( $str ) = #_;
my $gcs = Unicode::GCString->new( $str );
my $up = int( $gcs->columns() / ( GetTerminalSize )[0] );
print loadpos;
if ( $up ) {
print "\n" x $up, up( $up );
}
print cldown, savepos, $str;
}
The driver for a TTY might have code to respond to specific character sequences by moving the cursor around the screen, or changing colors, but the default driver for text files has no such code.
If you want to capture the output of a terminal session so that you can watch it in 'instant replay', I know of a command names script which records everything printed to your terminal into a file, which you can then read with an editor, or cat back to the screen to watch it replay.
You may need to make a decision between capturing the output, and using the ANSIScreen module.
I'm running a while loop reading each line in a file, and then fork processes with the data of the line to a child. After N lines I want to wait for the child processes to end and continue with the next N lines, etc.
It looks something like this:
while ($w=<INP>) {
# ignore file header
if ($w=~m/^\D/) { next;}
# get data from line
chomp $w;
#ws = split(/\s/,$w);
$m = int($ws[0]);
$d = int($ws[1]);
$h = int($ws[2]);
# only for some days in the year
if (($m==3)and($d==15) or ($m==4)and($d==21) or ($m==7)and($d==18)) {
die "could not fork" unless defined (my $pid = fork);
unless ($pid) {
some instructions here using $m, $d, $h ...
}
push #qpid,$pid;
# when all processors are busy, wait for child processes
if ($#qpid==($procs-1)) {
for my $pid (#qpid) {
waitpid $pid, 0;
}
reset 'q';
}
}
}
close INP;
This is not working. After the first round of processes I get some PID equal to 0, the #qpid array gets mixed up, and the file starts to get read at (apparently) random places, jumping back and forth. The end result is that most lines in the file get read two or three times. Any ideas?
Thanks a lot in advance,
S.
Are you exiting inside unless ($pid)?
If not, then your child, after running the command, will add $pid of zero to the array and generally continue running what is supposed to be parent process code
I am concerned that your algorithm is not terribly efficient:
Let the base process fork processes 1 to N.
If processes 2 through N complete, before process 1, then no new processes will be started until process 1 completes.
Instead of trying to get the fiddly details of your implementation correct, use Parallel::ForkManager to get working code easily.
use strict;
use warnings;
use Parallel::ForkManager;
my $pm = Parallel::ForkManager->new($MAX_PROCESSES);
while( my $w=<INP> ) {
next if $w=~m/^\D/; # ignore file header
chomp $w;
next unless match_dates( $w,
{ m => 3, d => 15 },
{ m => 7, d => 18 },
{ y => 2008 }, # Added this to show match_dates() capability.
);
my $pid = $pm->start and next;
.. Do stuff in child here ..
$pm->finish; # Terminate child
}
close INP;
# Returns true if ANY of the supplied date templates matches for ALL keys defined in that template.
sub match_dates {
my $string = shift;
my %target;
#target{qw( m d y )} = split(/\s/,$string);
DATE:
for my $date ( #_ ) {
my #k = keys %$match;
my $count = 0;
for( #k ) {
next DATE unless $date{$_} == $target{$_};
$count++;
}
return 1 if $count == #k; # All keys match
}
return;
}