3

The following perl script consume-10-lines-and-exit reads ten lines from stdin and prints them. After that it exits.

#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';

for (my $i = 0; $i < 10; $i++) {
    my $line = <STDIN>;
    print $line;
}

I'm trying to combine consume-10-lines-and-exit and cat in such a way that the first ten lines of input and consumed and printed by the first command and then the rest are consumed by cat.

The following code few snippets all print 1 through 10 instead of 1 through 13 like I was expecting.

printf '1 2 3 4 5 6 7 8 9 10 11 12 13' \
    | tr ' ' '\n' | { perl consume-10-lines-and-exit; cat; }

printf '1 2 3 4 5 6 7 8 9 10 11 12 13' \
    | tr ' ' '\n' | ( perl consume-10-lines-and-exit; cat )

printf '1 2 3 4 5 6 7 8 9 10 11 12 13' \
    | tr ' ' '\n' | sh -c 'perl consume-10-lines-and-exit; cat'

Is there a construction for sequencing commands so they will read input from stdin until they exit and then the next command will continue where the previous one left off?

2
  • Is it essential that cat not receive the first 10 lines? You could probably use something like tee if this isn't a hard requirement. Also, does your perl script have to exit, or could it stay in the path and handle passing the output you want to cat.
    – csyria
    Apr 8, 2016 at 16:39
  • @csyria My question isn't so much about the perl script itself or cat, but about the shell's semantics. It seems like cat should be reading from the same stdin as the perl script in all of the above examples. I'm not sure why it isn't doing that and I'm wondering if there's a way to combine two commands so that they do read from the same stdin. Apr 8, 2016 at 16:47

3 Answers 3

3

Your problem is that perl is using buffered input, so reads ahead beyond the lines you want to consume. Try this byte-by-byte version:

perl -e '
use strict;
use warnings FATAL => "all";
for (my $i = 0; $i < 10; $i++) {
  my $line;
  while(sysread(*STDIN,my $char,1)==1){
      $line .= $char;
      last if $char eq "\n";
  }
  print $line;
}'
1
  • That's input file issue, not buffering.
    – cuonglm
    Apr 9, 2016 at 1:28
3

Using the grouping commands is right for doing this job, but the culprit is the input you put to the grouping commands, which is not seekable.

POSIX define in the INPUT FILE section that:

When a standard utility reads a seekable input file and terminates without an error before it reaches end-of-file, the utility shall ensure that the file offset in the open file description is properly positioned just past the last byte processed by the utility. For files that are not seekable, the state of the file offset in the open file description for that file is unspecified

perl is not a standard utility, but there's a big chance that it follow the standard spec. If you make your file seekable, then it will work:

$ seq 13 > /tmp/test
$ {perl -e 'for($i=0;$i<10;$i++){$line = <STDIN>;print $line}'; cat;} </tmp/test
1
2
3
4
5
6
7
8
9
10
11
12
13

If you don't want to use seekable file, you can force perl use lower layer that call system calls function read(), write(), lseek() for IO:

$ seq 13 | {
  PERLIO=:unix perl -e 'for($i=0;$i<10;$i++){$line = <STDIN>;print $line}'
  cat
}
1

Buffering might also be disabled by fiddling around with PerlIO layers (see also perlrun). Also, that awkward and verbose C-style loop can be more easily written as ... for 1..10 or for my $i (1..10) { ... }.

$ perl -E 'say for 1..12' \
| ( PERLIO=:unix perl -e 'print "perl " . scalar <STDIN> for 1..10'; cat )
perl 1
perl 2
perl 3
perl 4
perl 5
perl 6
perl 7
perl 8
perl 9
perl 10
11
12
$ 
1
  • That's input file issue, not Perl IO.
    – cuonglm
    Apr 8, 2016 at 17:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .