File Coverage

blib/lib/Shell/Run.pm
Criterion Covered Total %
statement 120 130 92.3
branch 37 60 61.6
condition 8 20 40.0
subroutine 15 15 100.0
pod 2 2 100.0
total 182 227 80.1


line stmt bran cond sub pod time code
1             package Shell::Run;
2              
3 4     4   562367 use strict;
  4         21  
  4         110  
4 4     4   21 use warnings;
  4         7  
  4         91  
5              
6 4     4   1907 use Exporter::Tiny;
  4         12817  
  4         25  
7 4     4   2316 use IPC::Open2;
  4         17965  
  4         225  
8 4     4   1844 use IO::Select;
  4         6553  
  4         204  
9 4     4   1943 use File::Which;
  4         3788  
  4         214  
10 4     4   29 use Carp;
  4         9  
  4         209  
11              
12 4     4   22 use constant BLKSIZE => 1024;
  4         8  
  4         4616  
13              
14             our
15             $VERSION = '0.09';
16              
17             our @ISA = qw(Exporter::Tiny);
18              
19             sub new {
20 4     4 1 81 my $class = shift;
21 4         6 my @cmd;
22            
23 4         11 my $shell = _get_shell(@_);
24 4         11 return bless $shell, $class;
25             }
26              
27             sub _exporter_expand_sub {
28 3     3   279 my $class = shift;
29 3         8 my ($name, $args, $globals) = @_;
30 3   66     17 my $as = $args->{as} || $name;
31 3 50       24 croak "$as: not a valid subroutine name" unless $as =~ /^[a-z][\w]*$/;
32 3         11 my $shell = $class->new(name => $name, %$args);
33 3     11   22 return ($as => sub {return $shell->run(@_);});
  11         29987  
34             }
35              
36             sub _get_shell {
37 4     4   12 my %args = @_;
38 4         8 my @cmd;
39            
40 4 100       12 if ($args{exe}) {
41 1 50       23 croak "$args{exe}: not an excutable file" unless -x $args{exe};
42 1         4 $cmd[0] = $args{exe};
43             } else {
44 3   50     11 my $name = $args{name} || 'sh';
45 3         12 $cmd[0] = which $name;
46 3 50       707 croak "$name: not found in PATH" unless $cmd[0];
47             }
48              
49 4 100       24 if (defined $args{args}) {
50 1         2 push @cmd, @{$args{args}};
  1         2  
51             } else {
52 3         12 push @cmd, '-c';
53             }
54              
55 4         8 my $shell;
56 4         18 $shell->{shell} = \@cmd;
57 4         10 $shell->{debug} = $args{debug};
58 4         11 return $shell;
59             }
60              
61             sub run {
62 14     14 1 11900 my $self = shift;
63             # command to execute
64 14         70 my $cmd = shift;
65 14 50       114 print STDERR "using shell: @{$self->{shell}}\n" if $self->{debug};
  0         0  
66 14 50       46 print STDERR "executing cmd:\n$cmd\n" if $self->{debug};
67              
68             # cmd output, make $output an alias to the second argument
69 14         24 our $output;
70 14         78 local *output = \$_[0];
71 14         38 $output = '';
72 14         26 shift;
73              
74             # cmd input
75 14         25 my $input = shift;
76 14 50   3   314 open my $inh, '<', \$input or croak "cannot open input data";
  3         19  
  3         6  
  3         19  
77 14 50 33     2395 print STDERR "have input data\n" if $self->{debug} && $input;
78              
79             # additional environment entries for use as shell variables
80 14         53 my %env = @_;
81 14         934 local %ENV = %ENV;
82 14         83 $ENV{$_} = $env{$_} foreach keys %env;
83 14 50 33     49 if ($self->{debug} && %env) {
84 0         0 print STDERR "setting env variables:\n";
85 0         0 print STDERR "$_=$env{$_}\n" foreach keys %env;
86             }
87              
88             # start cmd
89 14         24 my ($c_in, $c_out);
90 14 100       131 $c_in = '' unless $input;
91 14         33 my $pid = open2($c_out, $c_in, @{$self->{shell}}, $cmd);
  14         71  
92              
93             # ensure filehandles are blocking
94 14         50508 $c_in->blocking(1);
95 14         24331 $c_out->blocking(1);
96              
97             # create selectors for read and write filehandles
98 14         394 my $sin = IO::Select->new;
99 14         329 $sin->add($c_out);
100 14         1310 my $sout = IO::Select->new;
101 14 100       191 $sout->add($c_in) if $input;
102              
103             # catch SIGPIPE on input pipe to cmd
104 14         158 my $pipe_closed;
105             local $SIG{PIPE} = sub {
106 1     1   13 $pipe_closed = 1;
107 1 50       24 print STDERR "got SIGPIPE\n" if $self->{debug};
108 14         794 };
109              
110 14 50       84 print STDERR "\n" if $self->{debug};
111             loop:
112 14         29 while (1) {
113             # get filehandles ready to read or write
114 34         239 my ($read, $write) = IO::Select->select($sin, $sout, undef);
115            
116             # read from cmd
117 34         17303 foreach my $rh (@$read) {
118 22         52 my $data;
119 22         240 my $bytes = sysread $rh, $data, BLKSIZE;
120 22 50       88 unless (defined $bytes) {
121 0 0       0 print STDERR "read from cmd failed\n" if $self->{debug};
122 0         0 carp "read from cmd failed";
123 0         0 return 1;
124             }
125             print STDERR "read $bytes bytes from cmd\n"
126 22 50 33     146 if $self->{debug} && $bytes;
127 22         164 $output .= $data;
128              
129             # finish on eof from cmd
130 22 100       67 if (! $bytes) {
131 14 50       41 print STDERR "closing output from cmd\n" if $self->{debug};
132 14         215 close($rh);
133 14         98 $sin->remove($rh);
134 14         990 last loop;
135             }
136             }
137              
138             # write to cmd
139 20         61 foreach my $wh (@$write) {
140             # stop writing to input on write error / SIGPIPE
141 13 50       51 if ($pipe_closed) {
142             print STDERR "closing input to cmd as pipe is closed\n"
143 0 0       0 if $self->{debug};
144 0         0 close $wh;
145 0         0 $sout->remove($wh);
146 0         0 next loop;
147             }
148              
149             # save position in case of partial writes
150 13         39 my $pos = tell $inh;
151              
152             # try to write chunk of data
153 13         536 my $data = $inh->getline;
154 13 100       1892 my $to_be_written = length($data) < BLKSIZE ?
155             length($data) : BLKSIZE;
156             print STDERR "writing $to_be_written bytes to cmd\n"
157 13 50 33     72 if $self->{debug} && $data;
158 13         309 my $bytes = syswrite $wh, $data, BLKSIZE;
159              
160             # write failure mostly because of broken pipe
161 13 100       72 unless (defined $bytes) {
162 1 50       17 print STDERR "write to cmd failed\n" if $self->{debug};
163 1         766 carp "write to cmd failed";
164 1         19 $pipe_closed = 1;
165 1         5 next loop;
166             }
167              
168             # log partial write
169             print STDERR "wrote $bytes bytes to cmd\n"
170 12 50 33     31 if $self->{debug} && $bytes < $to_be_written;
171            
172             # adjust input data position
173 12 100       36 if ($bytes < length($data)) {
174 9         35 seek $inh, $pos + $bytes, 0;
175             }
176              
177             # close cmd input when data is exhausted
178 12 100       50 if (eof($inh)) {
179             print STDERR "closing input to cmd on end of data\n"
180 1 50       9 if $self->{debug};
181 1         11 close $wh;
182 1         15 $sout->remove($wh);
183             }
184             }
185              
186             }
187              
188             # avoid zombies and get return status
189 14         289 waitpid $pid, 0;
190 14         152 my $status = $? >> 8;
191 14 50       53 print STDERR "cmd exited with rc=$status\n\n" if $self->{debug};
192              
193 14         3070 return ($status, !$status);
194             }
195              
196             1;
197              
198             # vi:ts=4:
199             __END__