So the script is something like this:
use strict;
use warnings;
use Term::ReadLine;
$SIG{'INT'} = 'INT_handler';
sub INT_handler {
print "[+] Abording\n";
home();
}
my $term = Term::ReadLine->new('Simple Perl calc');
sub home {
my $prompt = "\$> ";
my $OUT = $term->OUT || \*STDOUT;
while ( defined ($_ = $term->readline($prompt)) ) {
my $com = $_;
print $com."\n";
if ($com eq 'exit') {
exit;
}
option(); # another subroutine when the input is transferred to
}
}
home();
what I get:
$>
[+] Abording
$> # I pushed CRTL C but nothing shows
$> # same here
What I want to achieve is to be able to go to home() without exiting, and keep the $SIG{'INT'} working.
I have tried some other methods ( labels, using if statement ), but it will take too long cause the input is used in long processes
You should not call home() in your signal handler.
Just set a flag that you check in your input loop. When $term->readline() returns, because it was interrupted by CTRL-C, check that the flag was set, reset it and continue to loop.
Here is your updated code:
#!/usr/bin/perl
use strict;
use warnings;
use Term::ReadLine;
$SIG{'INT'} = 'INT_handler';
my $interrupted;
sub INT_handler {
$interrupted++;
}
my $term = Term::ReadLine->new('Simple Perl calc');
sub home {
my $prompt = "\$> ";
my $OUT = $term->OUT || \*STDOUT;
while ( defined ($_ = $term->readline($prompt)) || $interrupted ) {
if ($interrupted) {
$interrupted = 0;
print "\n[+] Aborting\n";
next;
}
my $com = $_;
print $com."\n";
if ($com eq 'exit') {
exit;
}
}
}
home();
exit 0;
Test output:
$ perl dummy.pl
$> test
test
$> ^C
[+] Aborting
$> ^C
[+] Aborting
$> sdasd^C
[+] Aborting
$> exit
exit
NOTE: there seems to be one issue still: you need to press return to get the prompt back. Probably something to do with how Term::Readline works.
Related
I've got a perl wrapper script for taskwarrior's task command that runs perfectly fine on macos.
I've ported it to a docker container running alpine. When I run the script, I get this weird error:
> # bin/task_wrapper.pl task list
Wrapper command: command task list
Can't exec "command": No such file or directory at bin/task_wrapper.pl line 61.
On my mac, it works just fine, no error.
which command reports: command: shell built-in command on both mac and on docker alpine.
I can run command task list directly from the command line in docker container and works fine.
Here's the whole script:
#! /usr/bin/env perl
use strict;
use warnings;
my $context;
my $runsub = shift #ARGV;
my #show_cmds = qw( done delete add modify );
{ no strict 'refs';
&${runsub}(#ARGV) if $runsub;
}
# my #tw_cmds = qw ( add annotate append calc config context count delete denotate done duplicate edit execute export help import log logo modify prepend purge start stop synchronize undo version );
my #descriptors = qw ( due: dep: depends: attribute: status: priority: pri: due: after: start: before: end: );
sub _parse {
my #bits = #_;
# find the first element that contains a command
my $count = 0;
my #ids;
my #rest = #bits;
foreach my $b (#bits) {
if ( $b =~ /([[a-f][0-9]]{8}),*/ ) {
push #ids, $1;
shift #rest;
next;
}
if ( $b =~ /(\d[\d\-]*),*/ ) {
push #ids, $1;
shift #rest;
next;
}
last;
}
return \#ids, \#rest;
}
sub task {
my $args = $_[0] || '';
my $filter = '';
my $subcmd = '';
if (ref $args) {
$filter = %$args{'filter'} || '';
$subcmd = %$args{'subcmd'} || '';
shift #_;
}
my #args = #_;
my #a = qw ( command task );
$context = $ENV{FLEXIBLE_CONTEXT} if !$context;
if ($context && $args ne 'sync') {
push #a, 'rc.context=' . $context;
}
if ($args =~ /sync/) {
exec 'command task sync';
} else {
print "Wrapper command: #a $filter $subcmd #args \n";
################ ERROR ON LINE BELOW
system("#a $filter $subcmd #args");
################
}
# show updated list of tasks
my $show;
$show = grep { $subcmd eq $_ } #show_cmds if $subcmd;
if ($show) {
my #sub_args;
push #sub_args, 'limit:3' if !$context;
push (#sub_args, '+st') if $context && $context !~ /\+sn|\+st/;
task ({}, #sub_args);
}
#print #a;
#print $ENV{FLEXIBLE_CONTEXT};
return;
}
sub ta {
task ({subcmd => 'add' }, #_ );
}
sub tm {
my ($ids, $rest) = _parse(#_);
task ({subcmd => 'modify', filter => "#$ids"}, #$rest);
}
# delete task
sub tdel {
my ($ids, $rest) = _parse(#_);
task ({subcmd => 'delete', filter => "#$ids"}, #$rest);
}
# done task
sub td {
task ('done', #_);
}
sub tl {
task ('next', "\\($ENV{'PU'} or +qst\\)", "-BLOCKED", #_);
}
sub tai {
task ('add', $ENV{'PU'}, 'due:1h', #_);
}
You say you're using zsh, and zsh does have a builtin named command, but you're not using zsh. You're using /bin/sh since
system( SHELL_CMD )
is effectively short for
system( '/bin/sh', '-c', SHELL_CMD )
(It's technically the value returned by perl -V:sh, not /bin/sh. But that value is /bin/sh.)
If you want to issue a zsh command, you will need to run zsh instead of /bin/sh.
system( 'zsh', '-c', SHELL_CMD )
Note that "#a $filter $subcmd #args" is not the proper way to build a shell command. It suffers from code injection bugs. You should use String::ShellQuote's shell_quote.
I have a perl script. It's not killing my tcl process. Here is what my process looks like:
UID 14439 10897 0 13:55:44 pts/26 0:00 /prod/bin/wish /prod/bin/q
Here is my perl script:
#!/usr/bin/env perl
#
# Simple kill script that allows users to kill specific processes
# using sudo
use BPub::Platform qw(solaris);
# Assume a taint mode type environment
$ENV{PATH} = '/opt/csw/bin:/usr/bin:/bin';
use strict;
use Getopt::Long;
if (!#ARGV) {
print "Usage: bkill [-9] pid <pid...>\n";
exit 1;
}
my $dashnine;
GetOptions('9' => \$dashnine);
# If the process name isn't in this hash, then you don't get to kill
# it
my %allowed_process_names = (
'dtsession' => 1,
'wish' => 1,
'compose' => 1,
);
# True if the given process name is one that we can kill
sub allowed_to_kill {
return $allowed_process_names{$_[0]};
}
# Takes a pid and returns the process name
sub process_name {
my ($pid) = #_;
my $out = `/usr/ucb/ps -c $pid | /bin/grep $pid | /bin/cut -b 25-1000`;
if ($?) {
print "Error running ps command: $!\n";
exit 2;
}
chomp($out);
return $out;
}
foreach my $pid (#ARGV) {
# tainted, remember?
if ($pid =~ /^(\d+)$/) {
my $safe_pid = $1;
my $name = process_name($safe_pid);
if (allowed_to_kill($name)) {
my #cmd = "/usr/bin/kill";
push (#cmd, '-9') if $dashnine;
push #cmd, $pid;
print "#cmd\n";
print "Killing $name ($pid)\n";
system(#cmd);
} else {
print "Not allowed to kill $safe_pid: $name.\n";
}
} else {
print "Invalid pid: must be a number: $pid\n";
}
}
When I run the script using sudo bkill PID. I get an error message saying:
bpub-xserver7-prod^UID118-> sudo bkill 14439
Password:
Not allowed to kill 14439: wish8.0.
Is there somethings that i can better in this script? How can i fix this problem that i am having getting rid of tcl process.
The program name your error message is emitting is wish8.0. So the simplest fix (and most strict) is to add 'wish8.0' => 1 to %allowed_process_names.
Or you could make your search less strict by doing something like:
my #allowed_process_names = qw(dtsession wish compose)
sub allowed_to_kill {
return scalar grep { index($_[0], $_) == 0 } #allowed_process_names
}
Which will find any string that starts with any of the allowed process names. (change to >= 0 or != -1 to allow it to match anywhere)
You could also use a regular expression instead. The following ones will match any program that starts with the provided program names. If you remove the leading caret, it would instead match them anywhere in the string.
my $allowed_process_names = qr/^(?:dtsession|wish|compose)/;
sub allowed_to_kill {
return $_[0] =~ /$allowed_process_names/;
}
or if you want to build up the regular expression programmatically:
my #allowed_process_names = qw(dtsession wish compose)
my $allowed_process_names = join('|', map quotemeta, #allowed_process_names);
$allowed_process_names = qr/^(?:$allowed_process_names)/;
# If the process name isn't in this hash, then you don't get to kill
# it
my %allowed_process_names = (
'dtsession' => 1,
'wish' => 1,
'compose' => 1,
);
...
my $out = `/usr/ucb/ps -c $pid | /bin/grep $pid | /bin/cut -b 25-1000`;
...
Not allowed to kill 14439: wish8.0.
Your ps pipeline is identifying the process as wish8.0 instead of wish or /prod/bin/wish. You will need to add an entry for "wish8.0" to this %allowed_process_names hash in order to use this perl script to kill this process.
If you were to look at /prod/bin/wish, you might find that it's a symbolic link to a file named wish8.0.
#!/usr/bin/perl -w
use Term::ReadKey;
ReadMode('cbreak');
while (1) {
$char = ReadKey(-1);
next unless defined $char;
printf("Char: $char Decimal: %d\tHex: %x\n", ord($char), ord($char));
}
ReadMode('normal');
The above works great. But i want to be able to get user input while some executable is running. so i ve tried the below but its not working. maybe running an executable while trying to get a user input is messing up? if so, how do i go about doing it?
I am getting output from $myexe and depending on the user input, i would like to filter differnt things from $myexe
#!/usr/bin/perl -w
use Term::ReadKey;
my $myexe = 'bin/myexecutable';
open my $EXE,
"$myexe distribute 2>&1 |"
or die 'Cannot open EXE';
ReadMode('cbreak');
while (<$EXE>) {
$char = ReadKey(-1);
if (defined $char) {
print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> $char\n"; #i would press a key but nothin prints out
}
print "$_\n";
}
ReadMode('normal');
I'm wary of running a 'busy-wait' loop like you'd get with Term::ReadKey. But what I'd suggest - if you're trying to do two things at once - is that it may be worth considering doing a spot of parallel code.
Something like:
#!/usr/bin/perl
use strict;
use warnings;
use threads;
use threads::shared;
use Term::ReadKey;
my $myexe = 'bin/myexecutable';
my $filter : shared;
sub worker {
open my $EXE, "$myexe distribute 2>&1 |"
or die 'Cannot open EXE';
while ( my $line = <$EXE> ) {
#do something with filter here;
print "$filter : $line";
}
}
$filter = 0;
threads->create( \&worker );
my $keypress;
ReadMode 4;
while ( threads->list(threads::running) ) {
while ( not defined( $keypress = ReadKey(-1) )
and threads->list(threads::running) )
{
print "Waiting\nRunning:" . threads->list(threads::running) . "\n";
sleep 1;
}
print "Got $keypress\n";
$filter = $keypress;
}
ReadMode 0;
foreach my $thr ( threads->list ) {
$thr->join();
}
This is some fairly simple example code - you can extend it in a variety of ways, but the principle is this:
you start a thread to 'do the work'.
you handle the 'keypress watching' in the 'main' thread.
Because there's a sleep in there, you're not busy-waiting on a keypress (e.g. polling as fast as a processor will spin).
When I pipe stuff into my program, it does not seem to get any character like 0x4 to indicate EOF.
$ echo "abc" | map 'cat'
saw a: \x61
saw b: \x62
saw c: \x63
saw
: \x0A
zzzbc
^C
I have to press Ctrl+C to quit, but I'm not really sure what the Ctrl+C is acting on. It's probably having the shell send a SIGINT to the pipeline? I don't know how pipelines work on that level.
Here is my program map:
#!/usr/bin/env perl
use strict;
use warnings;
use IO::Pty::Easy;
use Term::ReadKey;
use Encode;
$#ARGV % 2 and die "Odd number of args required.\n";
if ($#ARGV == -1) {
warn ("No args provided. A command must be specified.\n");
exit 1;
}
# be sure to enter the command as a string
my %mapping = #ARGV[-#ARGV..-2];
my $interactive = -t STDIN;
# my %mapping = #ARGV;
# my #mapkeys = keys %mapping;
# warn #mapkeys;
if ($interactive) {
print "Spawning command in pty: #ARGV\n"
# print "\nContinue? (y/n)";
# my $y_n;
# while (($y_n = <STDIN>) !~ /^(y|n)$/) {
# print '(y/n)';
# }
# exit if $y_n eq "n\n";
}
my $pty = IO::Pty::Easy->new();
my $spawnret = $pty->spawn("#ARGV")."\n";
print STDERR "Spawning has failed: #ARGV\n" if !$spawnret;
ReadMode 4;
END {
ReadMode 0; # Reset tty mode before exiting
}
my $i = undef;
my $j = 0;
{
local $| = 1;
while (1) {
myread();
# responsive to key input, and pty output may be behind by 50ms
my $key = ReadKey(0.05);
# last if !defined($key) || !$key;
if (defined($key)) {
my $code = ord($key); # this byte is...
if ($interactive and $code == 4) {
# User types Ctrl+D
print STDERR "Saw ^D from term, embarking on filicide with TERM signal\n";
$pty->kill("TERM", 0); # blocks till death of child
myread();
$pty->close();
last;
}
printf("saw %s: \\x%02X\n", $key, $code);
# echo translated input to pty
if ($key eq "a") {
$pty->write("zzz"); # print 'Saw "a", wrote "zzz" to pty';
} else {
$pty->write($key); # print "Wrote to pty: $key";
}
}
}
}
sub myread {
# read out pty's activity to echo to stdout
my $from_pty = $pty->read(0);
if (defined($from_pty)) {
if ($from_pty) {
# print "read from pty -->$from_pty<--\n";
print $from_pty;
} else {
if ($from_pty eq '') {
# empty means EOF means pty has exited, so I exit because my fate is sealed
print STDERR "Got back from pty EOF, quitting\n" if $interactive;
$pty->close();
last;
}
}
}
}
That would explain why it produced "zzzbc".
Now my question is how can I get map to be able to know about echo "abc" having reached the end of input? cf. echo "abc" | cat completes on its own. ReadKey does not seem to provide API for determining this situation.
Similarly I am not sure how to do the same to pass along the EOF to the child in the pty. I am thinking this might cause issues when a command is to write to a file or something, because EOF vs sending a kill signal is the difference between writing the file correctly and not exiting cleanly.
Try reading from STDIN and not that $pty object. The pipe you create via the shell passes the data to your STDIN file descriptor 0, which in perl is your handle .
The $pty, I assume that's your terminal. That's why the script just hangs (I guess).
I'm coding a shell like bash for a student project.
I need to make a perl auto tester of line commands.
my $cmd = "(echo \"foo\" | ./shell >& /dev/null)";
system($cmd);
if ($? == 35584) {
print "SIGSEGV";
}
elsif ($? == 34304) {
print "GLIB C";
}
else {
print "GOOD";
}
I want to be able to hide the output on segfault or glibc.
#Marc B
Okay, but my shell read on output 0, so i must have "echo" :s
#drquicksilver
* glibc detected ./shell: free(): invalid pointer: 0x0000000000608291 **
OR
Segmentation fault
First of all, system executes sh, but you're using csh syntax.
my $cmd = "(echo \"foo\" | ./shell >& /dev/null)";
should be
my $cmd = "(echo \"foo\" | ./shell >/dev/null 2>&1)";
But executing shell can only interfere with your test. So let's avoid the shell entirely.
use Config qw( %Config );
use IPC::Run qw( run );
sub sig_name {
my ($sig_num) = #_;
my %sig_names;
#sig_names{ reverse split ' ', $Config{sig_num} } =
reverse split ' ', $Config{sig_name};
return $sig_names{$sig_num}
? "SIG$sig_names{$sig_num} (".($sig_num).")"
: $sig_num;
}
run [ './shell' ], \"foo\n", '>','/dev/null', '2>&1';
if (my $sig_num = $? & 0x7F) {
print "Killed by signal ".sig_name($sig_num)."\n";
} elsif (my $exit_code = $? >> 8) {
print "Exited with error code $exit_code\n";
} else {
print "GOOD\n";
}
What you call "GLIB C" is actually SIGABRT.