File Coverage

blib/lib/Shell/Run.pm
Criterion Covered Total %
statement 121 131 92.3
branch 36 58 62.0
condition 8 20 40.0
subroutine 15 15 100.0
pod 2 2 100.0
total 182 226 80.5


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