File Coverage

blib/lib/PerlX/bash.pm
Criterion Covered Total %
statement 103 103 100.0
branch 67 70 95.7
condition 13 15 86.6
subroutine 18 18 100.0
pod 4 4 100.0
total 205 210 97.6


line stmt bran cond sub pod time code
1             package PerlX::bash;
2              
3 15     15   1703512 use strict;
  15         150  
  15         436  
4 15     15   79 use warnings;
  15         34  
  15         422  
5              
6 15     15   73 use Exporter 'import';
  15         24  
  15         1408  
7             our @EXPORT = ('bash');
8             our @EXPORT_OK = (@EXPORT, qw< shq pwd head tail >);
9             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
10              
11             our $VERSION = '0.05'; # VERSION
12              
13              
14 15     15   94 use Carp;
  15         38  
  15         860  
15 15     15   13943 use Contextual::Return;
  15         245269  
  15         87  
16 15     15   25985 use List::Util 1.33 qw< min max any >;
  15         372  
  15         1622  
17 15     15   107 use Scalar::Util qw< blessed >;
  15         25  
  15         677  
18 15     15   8366 use IPC::System::Simple qw< run capture EXIT_ANY $EXITVAL >;
  15         192813  
  15         17609  
19              
20             # see e.g. https://mywiki.wooledge.org/BashGuide/SpecialCharacters
21             my $BASH_SPECIAL_CHARS = qr/[\s\$'"\\#\[\]!<>|;{}()~&]/;
22             my $BASH_REDIRECTION = qr/^\d[<>].+/;
23              
24              
25              
26             my @AUTOQUOTE =
27             (
28             sub { ref shift eq 'Regexp' },
29             sub { blessed $_[0] and $_[0]->can('basename') },
30             );
31              
32             sub _should_quote ()
33             {
34 168     168   248 my $arg = $_;
35 168         228 local $_;
36 168 100   335   1368 return 1 if any { $_->($arg) } @AUTOQUOTE;
  335         912  
37 161 100       4146 return 0 if $arg =~ /^$BASH_SPECIAL_CHARS/;
38 142 100       827 return 0 if $arg =~ $BASH_REDIRECTION;
39 136 100       1111 return 1 if $arg =~ $BASH_SPECIAL_CHARS;
40 100         314 return 0;
41             }
42              
43             sub _process_bash_arg ()
44             {
45             # incoming arg is in $_
46 169     169   298 my $arg = $_; # make a copy
47 169 100       367 croak("Use of uninitialized argument to bash") unless defined $arg;
48 168 100       299 $arg = shq($arg) if _should_quote;
49 168         546 return $arg;
50             }
51              
52              
53              
54             sub bash (@)
55             {
56 66     66 1 95044 my (@opts, $capture);
57 66         803 my $exit_codes = [0..125];
58              
59 66         137 my $dash_c_cmd;
60 66   100     1116 while ( $_[0] and ($_[0] =~ /^-/ or ref $_[0]) )
      100        
61             {
62 50         147 my $arg = shift;
63 50 100       286 if (ref $arg)
    100          
    100          
64             {
65 36 100       166 croak("bash: multiple capture specifications") if $capture;
66 35         234 $capture = $$arg;
67             }
68             elsif ($arg eq '-c')
69             {
70 5         8 $dash_c_cmd = shift;
71 5 100       46 croak("Missing argument for bash -c") unless length($dash_c_cmd);
72             }
73             elsif ($arg eq '-e')
74             {
75 8         101 $exit_codes = [0];
76             }
77             else
78             {
79 1         10 push @opts, $arg;
80             }
81             }
82 63 100       185 if (defined $dash_c_cmd)
83             {
84 3 100       50 croak("Too many arguments for bash -c") if @_;
85             }
86             else
87             {
88 60 100       238 croak("Not enough arguments for bash") unless @_;
89 56 100 66     290 $dash_c_cmd = shift if @_ == 1 and $_[0] and $_[0] =~ /\s/;
      66        
90             }
91              
92 58         96 my $filter;
93 58 100       175 $filter = pop if ref $_[-1] eq 'CODE';
94 58 100 100     366 croak("bash: multiple output redirects") if $capture and $filter;
95              
96 57         194 my @cmd = 'bash';
97 57         103 push @cmd, @opts;
98 57         159 push @cmd, '-c';
99              
100 57 100       250 my $bash_cmd = $dash_c_cmd ? $dash_c_cmd : join(' ', map { _process_bash_arg } @_);
  169         367  
101 56         138 push @cmd, $bash_cmd;
102              
103 56 100       165 if ($capture)
    100          
104             {
105 31         70 my $IFS = $ENV{IFS};
106 31 100       94 $IFS = " \t\n" unless defined $IFS;
107              
108 31         164 my $output = capture $exit_codes, qw< bash -c >, $bash_cmd;
109 30 100       168359 if ($capture eq 'string')
    100          
    100          
110             {
111 24         88 chomp $output;
112 24         1260 return $output;
113             }
114             elsif ($capture eq 'lines')
115             {
116 2         19 my @lines = split("\n", $output);
117 2 100       86 return wantarray ? @lines : $lines[0];
118             }
119             elsif ($capture eq 'words')
120             {
121 3         168 my @words = split(/[$IFS]+/, $output);
122 3 100       156 return wantarray ? @words : $words[0];
123             }
124             else
125             {
126 1         85 die("bash: unrecognized capture specification [$capture]");
127             }
128             }
129             elsif ($filter)
130             {
131 11 100       135 $cmd[-1] =~ s/\s*(?
132 9 100       69 $cmd[-1] .= ' 2>&1' if $1;
133              
134             # This is pretty much straight out of `man perlipc`.
135 9         38 local *CHILD;
136 9         8699 my $pid = open(CHILD, "-|");
137 9 50       449 defined($pid) or die("bash: can't fork [$!]");
138              
139 9 100       281 if ($pid) # parent
140             {
141 6         106 local $_;
142 6         1476107 while ()
143             {
144 60         425 chomp;
145 60         268 $filter->($_);
146             }
147 6 100       1562 unless (close(CHILD))
148             {
149             # You know how IPC::System::Simple says that `_process_child_error` is not intended
150             # to be called directly? Yeah, well, the alternatives are worse ...
151 1         60 IPC::System::Simple::_process_child_error($?, 'bash', $exit_codes);
152             }
153             }
154             else # child
155             {
156 3 0       0 exec(@cmd) or die("bash: can't exec program [$!]");
157             }
158             }
159             else
160             {
161 14         60 run $exit_codes, @cmd;
162             return
163 2     2   1182 BOOL { $EXITVAL == 0 }
164 3     3   3902 SCALAR { $EXITVAL }
165 12         77988 ;
166             }
167             }
168              
169              
170             sub shq
171             {
172 46     46 1 1394 local $_ = shift;
173             #$_ = "$_"; # stringify
174 46         256 s/'/'\\''/g; # handle internal single quotes
175 46         213 "'$_'"; # quote with single quotes
176             }
177              
178              
179              
180 15     15   136 use Cwd ();
  15         35  
  15         2705  
181             *pwd = \&Cwd::cwd;
182              
183              
184              
185             sub head
186             {
187 7     7 1 1998 my $num = shift;
188 7 100       56 $num = $num < 0 ? @_ + $num : min($num, scalar @_);
189 7         47 @_[0..$num-1];
190             }
191              
192             sub tail
193             {
194 7     7 1 996 my $num = shift;
195 7 100       23 return () unless $num;
196 6 100       23 $num = $num < 0 ? max(@_ + $num, 0) : $num - 1 ;
197 6         35 @_[$num..$#_];
198             }
199              
200              
201             1;
202              
203             # ABSTRACT: tighter integration between Perl and bash
204             # COPYRIGHT
205             #
206             # This module is similar to the solution presented here:
207             # http://stackoverflow.com/questions/571368/how-can-i-use-bash-syntax-in-perls-system
208              
209             __END__