Home > Mobile >  Save STDIN to file in Perl?
Save STDIN to file in Perl?

Time:09-17

I'm trying to use Perl to:

  1. wait until at least 1 byte arrives on STDIN
  2. read all of STDIN and save it to a file as binary (to be UTF-8 compatible)

My attempts:

# read STDIN into a string/buffer and save it (maybe this only reads one line):
echo hello | perl -e 'open (fh, ">", "my_filename.txt"); print fh <STDIN>';

# or:
echo hello | perl -e 'open STDIN, ">", "my_filename.txt"'

# ideally be able to specify the filename:
echo hello | perl -e 'open STDIN, ">", $ARGV[0]' my_filename.txt

I can't use the shell to do this with echo hello > my_filename.txt because of an ancient bug/feature of shells where all file handles are opened simultaneously instead of sequentially based on some dependency logic. So my plan is to wait until there are bytes waiting on STDIN before opening the destination file.

Unfortunately I'm in a time crunch and don't have time to relearn the syntax of Perl. I think where I'm going wrong is that I'm trying to refer to STDIN as a new variable here instead of the actual input stream. I've looked at various other buffering solutions but unfortunately they're all inadequate in some way (don't actually work, aren't cross-platform, require additional executables to be installed, etc).

Bonus points if you perform the redirection either directly or by looping over chunks to reduce memory usage. Knowing how to read from a file and write to STDOUT would also be helpful. This may also be possible in something like awk, but note that I'm avoiding sed due to another ancient poor choice of syntax between GNU sed and BSD sed that often prevents using a single command on both platforms without tweaks. So Perl seems to be the way to go.

CodePudding user response:

I can't be sure, but I think you want to do something like

cat foo | some_operation > foo

If so, the simple solution is

cat foo | some_operation | sponge foo

But what you directly asked for can be achieved as follows:

perl -e'
   @ARGV or die("usage\n");
   my $qfn = shift;
   my $line = <>;
   open(STDOUT, ">", $qfn)
      or die("open $qfn: $!\n");
   print $line;
   print while <STDIN>;
' file.out

We can take advantage of -p.

perl -pe'
   BEGIN {
      @ARGV or die("usage\n");
      $qfn = shift(@ARGV);
   }
   if ($. == 1) {
      open(STDOUT, ">", $qfn)
         or die("open $qfn: $!\n");
   }
' file.out

We can throw in -s.

perl -spe'
   BEGIN { defined($o) or die("usage\n"); }
   if ($. == 1) {
      open(STDOUT, ">", $o)
         or die("open $o: $!\n");
   }
' -- -o=file.out

Finally, we can get rid of error checking.

perl -spe'open(STDOUT, ">", $o) if $. == 1' -- -o=file.out

Bonus: If you'd like to read from a file instead of STDIN, all of the above are capable of doing so. Just provide the name of the file from which to read as a subsequent argument. You can even provide more than one name.

  • Related