Perl trapping Ctrl-C (sigint) in bash - bash

I'm reading How do we capture CTRL ^ C - Perl Monks, but I cannot seem to get the right info to help with my problem.
The thing is - I have an infinite loop, and 'multiline' printout to terminal (I'm aware I'll be told to use ncurses instead - but for short scripts, I'm more comfortable writing a bunch of printfs). I'd like to trap Ctrl-C in such a way, that the script will terminate only after this multiline printout has finished.
The script is (Ubuntu Linux 11.04):
#!/usr/bin/env perl
use strict;
use warnings;
use Time::HiRes;
binmode(STDIN); # just in case
binmode(STDOUT); # just in case
# to properly capture Ctrl-C - so we have all lines printed out
# unfortunately, none of this works:
my $toexit = 0;
$SIG{'INT'} = sub {print "EEEEE"; $toexit=1; };
#~ $SIG{INT} = sub {print "EEEEE"; $toexit=1; };
#~ sub REAPER { # http://www.perlmonks.org/?node_id=436492
#~ my $waitedpid = wait;
#~ # loathe sysV: it makes us not only reinstate
#~ # the handler, but place it after the wait
#~ $SIG{CHLD} = \&REAPER;
#~ print "OOOOO";
#~ }
#~ $SIG{CHLD} = \&REAPER;
#~ $SIG{'INT'} = 'IGNORE';
# main
# http://stackoverflow.com/questions/14118/how-can-i-test-stdin-without-blocking-in-perl
use IO::Select;
my $fsin = IO::Select->new();
$fsin->add(\*STDIN);
my ($cnt, $string);
$cnt=0;
$string = "";
while (1) {
$string = ""; # also, re-initialize
if ($fsin->can_read(0)) { # 0 timeout
$string = <STDIN>;
}
$cnt += length($string);
printf "cnt: %10d\n", $cnt;
printf "cntA: %10d\n", $cnt+1;
printf "cntB: %10d\n", $cnt+2;
print "\033[3A"; # in bash - go three lines up
print "\033[1;35m"; # in bash - add some color
if ($toexit) { die "Exiting\n" ; } ;
}
Now, if I run this, and I press Ctrl-C, I either get something like this (note, the _ indicates position of terminal cursor after script has terminated):
MYPROMPT$ ./test.pl
cnEEEEEcnt: 0
MYPROMPT$ _
cntB: 2
Exiting
or:
MYPROMPT$ ./test.pl
cncnt: 0
MYPROMPT$ _
cntB: 2
Exiting
... however, I'd like to get:
MYPROMPT$ ./test.pl
cncnt: 0
cntA: 1
cntB: 2
Exiting
MYPROMPT$ _
Obviously, handlers are running - but not quite in the timing (or order) I expect them to. Can someone clarify how do I fix this, so I get the output I want?
Many thanks in advance for any answers,
Cheers!

Hmmm... seems solution was easier than I thought :) Basically, the check for "trapped exit" should run after the lines are printed - but before the characters for "go three lines up" are printed; that is, that section should be:
printf "cnt: %10d\n", $cnt;
printf "cntA: %10d\n", $cnt+1;
printf "cntB: %10d\n", $cnt+2;
if ($toexit) { die "Exiting\n" ; } ;
print "\033[3A"; # in bash - go three lines up
print "\033[1;35m"; # in bash - add some color
... and then the output upon Ctrl-C seems to be like:
MYPROMPT$ ./test.pl
cnt: 0
^CcntA: 1
cntB: 2
Exiting
MYPROMPT$ _
Well, hope this may help someone,
Cheers!

Related

How I combine a bash hookscript with a Perl hookscript for use with Proxmox?

I am trying to use Promox VE has the hypervisor for running VMs.
In one of my VMs, I have a hookscript that is written for the bash shell:
#!/bin/bash
if [ $2 == "pre-start" ]
then
echo "gpu-hookscript: Resetting GPU for Virtual Machine $1"
echo 1 > /sys/bus/pci/devices/0000\:01\:00.0/remove
echo 1 > /sys/bus/pci/rescan
fi
which is to help with enabling GPU passthrough.
And then I have another hookscript that is written in Perl, which enables virtio-fs:
#!/usr/bin/perl
# Exmple hook script for PVE guests (hookscript config option)
# You can set this via pct/qm with
# pct set <vmid> -hookscript <volume-id>
# qm set <vmid> -hookscript <volume-id>
# where <volume-id> has to be an executable file in the snippets folder
# of any storage with directories e.g.:
# qm set 100 -hookscript local:snippets/hookscript.pl
use strict;
use warnings;
print "GUEST HOOK: " . join(' ', #ARGV). "\n";
# First argument is the vmid
my $vmid = shift;
# Second argument is the phase
my $phase = shift;
if ($phase eq 'pre-start') {
# First phase 'pre-start' will be executed before the guest
# ist started. Exiting with a code != 0 will abort the start
print "$vmid is starting, doing preparations.\n";
system('/var/lib/vz/snippets/launch-virtio-daemon.sh');
# print "preparations failed, aborting."
# exit(1);
} elsif ($phase eq 'post-start') {
# Second phase 'post-start' will be executed after the guest
# successfully started.
print "$vmid started successfully.\n";
} elsif ($phase eq 'pre-stop') {
# Third phase 'pre-stop' will be executed before stopping the guest
# via the API. Will not be executed if the guest is stopped from
# within e.g., with a 'poweroff'
print "$vmid will be stopped.\n";
} elsif ($phase eq 'post-stop') {
# Last phase 'post-stop' will be executed after the guest stopped.
# This should even be executed in case the guest crashes or stopped
# unexpectedly.
print "$vmid stopped. Doing cleanup.\n";
} else {
die "got unknown phase '$phase'\n";
}
exit(0);
What would be the best way for me to combine these two files into a single format, so that I can use it as a hookscript in Proxmox?
I tried reading the thread here about how to convert a bash shell script to Perl, and not being a programmer, admittedly, I didn't understand what I was reading.
I appreciate the teams help in educating a non-programmer.
Thank you.
before
system('/var/lib/vz/snippets/launch-virtio-daemon.sh');
insert pls.
system('echo 1 > /sys/bus/pci/devices/0000\:01\:00.0/remove');
system('echo 1 > /sys/bus/pci/rescan');
Had your original code above evaluated return code of these perl calls (it is not the case):
echo 1 > /sys/bus/pci/devices/0000\:01\:00.0/remove
echo 1 > /sys/bus/pci/rescan
you could apply solutions from:
Getting Perl to return the correct exit code

Perl Windows alternatives for "system" ( opening multiple processes )

So long story short, I'm trying to run a linux perl script in Windows ( with few modifications ).
On Unix it works just fine, but on Windows I come to the conclusion that calling for system doesn't work the same as on Unix and so it doesn't create multiple processes.
Below is the code :
use strict;
use warnings;
open (FIN, 'words.txt'); while (<FIN>) {
chomp;
my $line = $_;
system( "perl script.pl $line &" );
}
close (FIN);
So basically, I have 5 different words in "words.txt" which I want each and every one to be used one by one when calling for script.pl , which means :
word1 script.pl
word2 script.pl
word3 script.pl
etc
As of now it opens just the first word in words.txt and it loops with that one only. As I said, on Unix it works perfectly, but not on Windows.
I've tried to use "start" system( "start perl script.pl $line &" ); and it works...except it opens 5 additional CMDs to do the work. I want it to do the work on the same window.
If anyone has any idea how this can work on window, i'll really appreciate it.
Thanks!
According to perlport :
system
(Win32) [...] system(1, #args) spawns an external process and
immediately returns its process designator, without waiting for it to
terminate. Return value may be used subsequently in wait or waitpid.
Failure to spawn() a subprocess is indicated by setting $? to 255 <<
8. $? is set in a way compatible with Unix (i.e. the exit status of the subprocess is obtained by $? >> 8, as described in the
documentation).
I tried this:
use strict;
use warnings;
use feature qw(say);
say "Starting..";
my #pids;
for my $word (qw(word1 word2 word3 word3 word5)) {
my $pid = system(1, "perl script.pl $word" );
if ($? == -1) {
say "failed to execute: $!";
}
push #pids, $pid;
}
#wait for all children to finish
for my $pid (#pids) {
say "Waiting for child $pid ..";
my $ret = waitpid $pid, 0;
if ($ret == -1) {
say " No such child $pid";
}
if ($? & 127) {
printf " child $pid died with signal %d\n", $? & 127;
}
else {
printf " child $pid exited with value %d\n", $? >> 8;
}
}
say "Done.";
With the following child script script.pl :
use strict;
use warnings;
use feature qw(say);
say "Starting: $$";
sleep 2+int(rand 5);
say "Done: $$";
sleep 1;
exit int(rand 10);
I get the following output:
Starting..
Waiting for child 7480 ..
Starting: 9720
Starting: 10720
Starting: 9272
Starting: 13608
Starting: 13024
Done: 13608
Done: 10720
Done: 9272
Done: 9720
Done: 13024
child 7480 exited with value 9
Waiting for child 13344 ..
child 13344 exited with value 5
Waiting for child 17396 ..
child 17396 exited with value 3
Waiting for child 17036 ..
child 17036 exited with value 6
Waiting for child 17532 ..
child 17532 exited with value 8
Done.
Seems to work fine..
You can use Win32::Process to get finer control over creating a new process than system gives you on Windows. In particular, the following doesn't create a new console for each process like using system("start ...") does:
#!/usr/bin/env perl
use warnings;
use strict;
use feature qw/say/;
# Older versions don't work with an undef appname argument.
# Use the full path to perl.exe on them if you can't upgrade
use Win32::Process 0.17;
my #lines = qw/foo bar baz quux/; # For example instead of using a file
my #procs;
for my $line (#lines) {
my $proc;
if (!Win32::Process::Create($proc, undef, "perl script.pl $line", 1,
NORMAL_PRIORITY_CLASS, ".")) {
$_->Kill(1) for #procs;
die "Unable to create process: $!\n";
}
push #procs, $proc;
}
$_->Wait(INFINITE) for #procs;
# Or
# use Win32::IPC qw/wait_all/;
# wait_all(#procs);
As Yet Another Way To Do It, the start command takes a /b option to not open a new command prompt.
system("start /b perl script.pl $line");

Bash prompt with background color extending to end of line

It's bash-prompt-fiddling-time for me (had to happen one day...).
I'm trying to get a 2-line prompt:
1st line with location info, and background color to the end of line
2nd line with time and exit code of previous command
I'm almost there, but I can't crack the "background color to the end of line" part. Not quite.
Putting together bits of info from several sources, and most importantly from here and here, I get this result (terminal screenshot).
As you can see, something's wrong with the COLUMNS calculations:
it does not reach the end of line
it depends on the text length on the 1st line
it gets worse when reaching the bottom of the terminal; it does reach the end of line then; the wrong line...
another weird thing: the tiny [ ] which are bracketing the 2nd prompt line; and those ONLY APPEAR AFTER A COMMAND IS ENTERED
This is my bashrc code:
PROMPT_COMMAND=__prompt_command
__prompt_command()
{
local EXIT="$?"
local Red='\[\033[1;38;5;9m\]'
local Green='\[\033[1;38;5;10m\]'
local Gray='\[\033[0;38;5;248m\]'
local BgBlue='\[\033[48;5;253;1;38;5;12m\]'
local BgPurple='\[\033[48;5;253;1;38;5;93m\]'
local None='\[\e[0m\]'
PS1="${BgPurple}\u#\h:${BgBlue}\$PWD"
printf -v TC_SPC "%${COLUMNS}s" ''
COLUMNS=$(tput cols)
PS1=`echo $PS1 | sed "s/$/$TC_SPC/; s/^\\(.\\{${COLUMNS}\\}\\) */\\1/"`
PS1+="\n${Gray}\D{%F %T}"
if [ $EXIT != 0 ]; then
PS1+=" ${Red} O_o ${None}" # Add red if exit code non 0
else
PS1+="${Green} ^_^ ${None}"
fi
}
I tried more hacking but no success.
Oh, there another more sophisticated version of the sed bit, which I also tried:
PS1=`echo $PS1 | sed "s/$/$TC_SPC/; s/^\\(\\(\\o33\\[[0-9;]*[a-zA-Z]\\)*\\)\\([^\o033]\\{${COLUMNS}\\}\\) */\\1\\3/"`
Different result (terminal screenshot) but still not OK.
At this point I'm taking any help !
Here is the working solution, thanks to Eric's "erase to end of line" hint.
PROMPT_COMMAND=__prompt_command # Func to gen PS1 after CMDs
__prompt_command()
{
local EXIT="$?" # This needs to be first (retrieves last commmand exit code)
local Red='\[\033[1;38;5;9m\]'
local Green='\[\033[1;38;5;10m\]'
local Gray='\[\033[0;38;5;248m\]'
local BgBlue='\[\033[48;5;253;1;38;5;12m\]'
local BgPurple='\[\033[48;5;253;1;38;5;93m\]'
local None='\[\e[0m\]'
PS1="${BgPurple}\u#\h:${BgBlue}\$PWD"
PS1+="\033[K" # erase to end of 1st line (background color stays)
PS1+="\n${Gray}\D{%F %T}\a"
if [ $EXIT != 0 ]; then
PS1+="${Red} O_o ${None}" # Add red if exit code non 0
else
PS1+="${Green} ^_^ ${None}"
fi
PS1+="\033[K" # erase to end of 2nd line (no more background color)
}
And here is the result (terminal screenshot). One more happy prompt owner...
Instead of:
printf -v TC_SPC "%${COLUMNS}s" ''
COLUMNS=$(tput cols)
PS1=`echo $PS1 | sed "s/$/$TC_SPC/; s/^\\(.\\{${COLUMNS}\\}\\) */\\1/"`
Use:
PS1+=$'\033[K' #erase to end of line

How to modify a global variable in bash function while catching stdout output and also return a valid return code?

In bash programming I am currently facing a problem where I do not only want to modify a global variable in a bash function but also return a proper return code via return and $? as well as being able to assign all stdout output which appears during the function call to a variable outside of the function.
While each of these individual tasks (modify global var, return status code, assign stdout to variable) seems to perfectly possible in bash (and even a combination of two of these wishes), a combination of all the three requirements seems to be hardly (i.e. only inconveniently) possible.
Here is an example script I have prepared to demonstrate the problem:
#!/bin/bash
#
# This is the required output:
#
# -- cut here --
# RETURN: '2'
# OUTPUT: 'Hello World!'
# GLOBAL_VAR: '3'
# -- cut here --
#
GLOBAL_VAR=0
hello() {
echo "Hello World!"
GLOBAL_VAR=3
return 2
}
# (1) normal bash command substition (subshell)
# PROBLEM: GLOBAL_VAR is 0 but should be 3
# (hello is executed in subshell)
#
output=$(hello) ; result=$?
# (2) use 'read' and a reverse pipe
# PROBLEM: RETURN and GLOBAL_VAR is 0
# (hello in subshell and read return
# code returned)
#
#read output < <(hello) ; result=$?
# (3) normal function execution
# PROBLEM: no catched output!
#
#hello ; result=$?
# (4) using lastpipe + read
# PROBLEM: GLOBAL_VAR is 0 but should be 3
# (a pipe generateѕ a subshell?!?!)
#
#shopt -s lastpipe
#hello | read output ; result=${PIPESTATUS[0]}
# (5) ksh-like command substiution
# PROBLEM: Works, but ksh-syntax
# -> doesn't work in bash!
#
#output=${ hello; } ; result=$?
# (6) using a temp file to catch output of hello()
# WORKS, but ugly due to tmpfile and 2xsubshell use!
#
#tmp=$(mktemp)
#hello >${tmp} ; result=$?
#output=$(cat ${tmp})
#rm -f ${tmp}
###################################
# OUTPUT stuff
# this should output "2"
echo "RESULT: '${result}'"
# this should output "Hello World!"
echo "OUTPUT: '$output'"
# this should output "3"
echo "GLOBAL_VAR: '$GLOBAL_VAR'"
In this script I have added a function hello() which should return a status code of 2, sets a global variable GLOBAL_VAR to 3 and outputs "Hello World!" to stdout.
In addition to this function I have added 6 potential solutions to call this hello() function to achieve exactly the output I require (which is shown at the top of the bash script code).
By commenting out/in these 6 different ways of calling the function you will see that only solution (6) is possible to fulfill all my requirements for calling the function.
Especially interesting is that solution number (5) shows the ksh-syntax which works exactly like I require this function to work. So calling this script using ksh outputs all variables with their required values. Of course this solution (command substitution using ${ cmd; }) isn't supported in bash. However, I definitely require a bash solution as the main script where I require my solution is a bash-only script I cannot port to ksh.
While of course, solution (6) also fulfills my requirements it requires to put the output of hello() in a temporary file and read it afterwards again. In terms of performance (several subshells required, temp-file management) this isn't a real solution for me.
So now the question comes up if there is any other potential solution in bash that fulfills my requirements so that the script above outputs exactly what I want, thus combines all my three requirements?!?
It's not clear from the question why you can't simply rewrite the function to behave differently. If you really can't change the function, you could change bash:
#!/bin/bash
GLOBAL_VAR=0
hello() {
echo "Hello World!"
GLOBAL_VAR=3
return 2
}
echo () { output=$*; }
hello
result=$?
unset -f echo
# this should output "2"
echo "RESULT: '$result'"
# this should output "Hello World!"
echo "OUTPUT: '$output'"
# this should output "3"
echo "GLOBAL_VAR: '$GLOBAL_VAR'"
The outputs match the expectations.

bash time output processing

I know that time will send timing statistics output to stderr. But somehow I couldn't capture it either in a bash script or into a file via redirection:
time $cmd 1>/dev/null 2>file
$output=`cat file`
Or
$output=`time $cmd 1>/dev/null`
I'm only interested in timing, not the direct output of the command. I've read some posts overhere but still no luck finding a viable solution. Any suggestions?
Thanks!
Try:
(time $cmd) 1>/dev/null 2>file
so that (time $cmd) is executed in a subshell environment and you can then redirect its output.
(Using GNU time /usr/bin/time rather than bash builtin) (Thanks #Michael Krelin)
(Or invoke as \time) (Thanks #Sorpigal, if I ever knew this I'd entirely forgotten)
How about using the -o and maybe -a command line options:
-o FILE, --output=FILE
Do not send the results to stderr, but overwrite the specified file.
-a, --append
(Used together with -o.) Do not overwrite but append.
I had a similar issue where I wanted to bench optimizations. The idea was to run the program several times then output statistics on run durations.
I used the following command lines:
1st run: (time ./myprog)2>times.log
Next runs: (time ./myprog)2>>times.log
Note that my (bash?) built-in time outputs statistics in the form:
real 0m2.548s
user 0m7.341s
sys 0m0.007s
Then I ran the following Perl script to retrieve statistics:
#!/usr/bin/perl -w
open FH, './times.log' or die "ERROR: ", $!;
my $useracc1 = 0;
my $useracc2 = 0;
my $usermean = 0;
my $uservar = 0;
my $temp = 0;
while(<FH>)
{
if("$_" =~ /user/)
{
if("$_" =~ /(\d+)m(\d{1,2})\.(\d{3})s/)
{
$usercpt++;
$temp = $1*60 + $2 + $3*0.001;
$useracc1 += $temp;
$useracc2 += $temp**2;
}
}
}
if($usercpt ne 0)
{
$usermean = $useracc1 / $usercpt;
$userdev = sqrt($useracc2 / $usercpt - $usermean**2);
$usermean = int($usermean*1000)/1000;
$userdev = int($userdev*1000)/1000;
}
else
{
$usermean = "---";
$userdev = "---";
}
print "User: ", $usercpt, " runs, avg. ", $usermean, "s, std.dev. ", $userdev,"s\n";
Of course, regular expressions may require adjustements depending on your time output format. It can also be easily extended to include real and system statistics.

Resources