File Coverage

bin/cut
Criterion Covered Total %
statement 78 104 75.0
branch 29 50 58.0
condition 7 9 77.7
subroutine 9 10 90.0
pod n/a
total 123 173 71.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             =begin metadata
4              
5             Name: cut
6             Description: select portions of each line of a file
7             Author: Rich Lafferty, rich@alcor.concordia.ca
8             License: perl
9              
10             =end metadata
11              
12             =cut
13              
14 6     6   31417 use strict;
  6         10  
  6         300  
15              
16 6     6   35 use File::Basename qw(basename);
  6         9  
  6         666  
17 6     6   3262 use Getopt::Std qw(getopts);
  6         14402  
  6         554  
18              
19 6     6   43 use constant EX_SUCCESS => 0;
  6         17  
  6         681  
20 6     6   33 use constant EX_FAILURE => 1;
  6         16  
  6         15325  
21              
22 6         963610 my $me = basename($0);
23              
24 6         20 my %opt;
25 6 50       64 getopts('b:c:d:f:ns', \%opt) or usage();
26              
27             # There's no difference between -b and -c on any unix I
28             # use regularly -- it's for i18n. Thus, -n is a noop, too.
29 6 100       473 if (defined $opt{'b'}) {
    50          
    50          
30 3 50       20 usage() if $opt{'f'};
31 3         15 handle_b($opt{'b'});
32             }
33             elsif (defined $opt{'c'}) {
34 0 0       0 usage() if $opt{'f'};
35 0         0 handle_b($opt{'c'});
36             }
37             elsif (defined $opt{'f'}) {
38 3         21 handle_f($opt{'f'}, $opt{'d'}, $opt{'s'});
39             }
40             else {
41 0         0 warn "$me: byte, character or field list required\n";
42 0         0 usage();
43             }
44 6         0 exit EX_SUCCESS;
45              
46             sub checknum {
47 8     8   24 my $n = shift;
48 8 50       36 if ($n !~ m/\A\-?[0-9]+\Z/) {
49 0         0 warn "$me: unexpected byte or field number: '$n'\n";
50 0         0 exit EX_FAILURE;
51             }
52 8 50       28 if ($n == 0) {
53 0         0 warn "$me: bytes and fields are numbered from 1\n";
54 0         0 exit EX_FAILURE;
55             }
56             }
57              
58             sub parse_fields {
59 6     6   26 my $spec = shift;
60 6         25 my @list = split /,/, $spec;
61              
62 6         13 my $to_end;
63             my @cols;
64 6         20 foreach my $item (@list) {
65 6         10 my ($from, $is_range, $to);
66 6 50       54 if ($item =~ m/\A([0-9]*)(\-?)([0-9]*)\z/) {
67 6         15 $from = $1;
68 6         13 $is_range = $2;
69 6         23 $to = $3;
70             }
71             else {
72 0         0 warn "$me: invalid byte list: '$item'\n";
73 0         0 exit EX_FAILURE;
74             }
75 6 100       26 checknum($from) if length($from);
76 6 100       26 checknum($to) if length($to);
77 6 50 66     43 if (!length($from) && !length($to)) { # reject lone '-'
78 0         0 warn "$me: invalid byte list\n";
79 0         0 exit EX_FAILURE;
80             }
81 6 100 100     37 if (length($from) && length($to)) {
82 2 50       7 if ($from > $to) {
83 0         0 warn "$me: invalid range $from-$to\n";
84 0         0 exit EX_FAILURE;
85             }
86 2 50       7 $is_range = 0 if $from == $to;
87             }
88 6 100       21 if ($is_range) {
89 4 100 66     21 if (length($from) && length($to)) { # a-b
    50          
90 2         15 push @cols, $from .. $to;
91             }
92             elsif (length $from) { # a-
93 0         0 push @cols, $from;
94 0         0 $to_end = 1;
95             }
96             else { # -a
97 2         21 push @cols, 1 .. $to;
98             }
99             }
100             else {
101 2         7 push @cols, $from;
102             }
103             }
104 6         29 my @sorted = sort { $a <=> $b } @cols;
  6         39  
105 6         13 unshift @sorted, $to_end;
106 6         46 return @sorted;
107             }
108              
109             sub handle_b {
110 3     3   16 my $spec = shift;
111 3         16 my ($to_end, @cols) = parse_fields($spec);
112              
113 3         147 while (<>) {
114 3         7 chomp;
115 3         7 my $col = 0;
116 3         8 my @chars = split //;
117 3         26 foreach my $c (@cols) {
118 6 50       24 next if $c <= $col;
119 6         56 print $chars[$c - 1];
120 6         13 $col = $c;
121             }
122 3 50       8 if ($to_end) {
123 0         0 $col++;
124 0         0 foreach my $c ($col .. scalar(@chars)) {
125 0         0 print $chars[$c - 1];
126             }
127             }
128 3         73 print "\n";
129             }
130             }
131              
132             sub handle_f {
133 3     3   14 my ($spec, $delim, $sflag) = @_;
134              
135 3         17 my ($to_end, @cols) = parse_fields($spec);
136 3 50       12 if (defined $delim) {
137 3         20 $delim = substr $delim, 0, 1;
138             }
139             else {
140 0         0 $delim = "\t";
141             }
142 3         144 while (<>) {
143 3         9 chomp;
144              
145             # Only waste time on lines with delimiters
146 3 50       86 if (/$delim/) {
147 3         29 my @hunk = split /$delim/;
148 3         6 my $col = 0;
149 3         12 my @out;
150 3         25 foreach my $c (@cols) {
151 5 50       15 next if $c <= $col;
152 5         12 push @out, $hunk[$c - 1];
153 5         9 $col = $c;
154             }
155 3 50       11 if ($to_end) {
156 0         0 $col++;
157 0         0 foreach my $c ($col .. scalar(@hunk)) {
158 0         0 push @out, $hunk[$c - 1];
159             }
160             }
161 3         130 print join($delim, @out), "\n";
162             }
163             else { # no delimiter in line
164 0 0         print "$_\n" unless $sflag;
165             }
166             }
167             }
168              
169             sub usage {
170 0     0     print <
171             usage: $me -b list [-n] [file ...]
172             $me -c list [file ...]
173             $me -f list [-d delim] [-s] [file ...]
174              
175             Each LIST is made up of one range, or many ranges separated by commas.
176             Each range is one of:
177              
178             N Nth byte, character or field, counted from 1
179             N- from Nth byte, character or field, to end of line
180             N-M from Nth to Mth (included) byte, character or field
181             -M from first to Mth (included) byte, character or field
182              
183             EOT
184 0           exit EX_FAILURE;
185             }
186              
187             __END__