Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use "here-doc" to print lines to a file?

Tags:

perl

Basically, this is the result of my programming and using Google for the last half an hour trying to achieve a simple thing: pick up user inputs from STDIN and write them into a structured XML file as the output. Below is my ugly code:

#!/bin/perl
print "img URL = ?    ";
$img = <>;
chomp($img);
print "filename = ?    ";
$filename = <>;
chomp($filename);

# now write xml with inserted contents to a file:
open (CARDFILE, ">>$filename");
print CARDFILE "<card>\r\n";
print CARDFILE "    <img>$img</img>\r\n";
print CARDFILE "    <type>$type</type>\r\n";
print CARDFILE "    <expansion>$expansion</expansion>\r\n";
print CARDFILE "    <keyword>$keyword</keyword>\r\n";
print CARDFILE "    <color>$color</color>\r\n";
print CARDFILE "    <linkcolor>$linkcolor</linkcolor>\r\n";
print CARDFILE "    <kickercolor>$kickercolor</kickercolor>\r\n";
print CARDFILE "    <cost>$cost</cost>\r\n";
print CARDFILE "    <strength>$strength</strength>\r\n";
print CARDFILE "    <health>$health</health>\r\n";
print CARDFILE "</card>";
close (CARDFILE);  

I am new to Perl, but I could tell some of the significant problems in my code:

  1. Use two lines to first receive STDIN then chomp() it to get rid of the trailing carriage return. Should there be a one-liner?

  2. I cannot work out how to use the "here-doc" thing to print multiple-lines to file in one go. Without the CARDFILE file handle, I can use here-doc syntax to print all strings to STDOUT in a single print statement. Now I have to call the print function multiple times.

  3. For these "\r\n"s, is there a way to use a different function so I don't have to remember to manually insert them after each line?

like image 826
Michael Mao Avatar asked Jul 04 '13 23:07

Michael Mao


3 Answers

Reading a line

An idiom for reading a single line would be

chomp(my $var = <>);

But I usually end up writing a small prompt subroutine:

sub prompt {
  print @_;
  chomp(my $answer = <>);
  return $answer;
}
...;
my $img = prompt("img URL = ? ");

Here-docs

A here-doc is just another kind of string literal, e.g.

my $str = <<"END";
foo bar
baz
END
print $str;

If you enclose the delimiter with single quotes: <<'END', then you can't interpolate variables into the here-doc. Note that the end marker must always be at the start of the line, but may include space characters:

    my $string = <<'END THIS';
    foo
    bar
END THIS
    # marker must be at start of line!

You can directly print a here-doc like

print CARDFILE <<"END";
<card>
    <img>$img</img>
    <type>$type</type>
    <expansion>$expansion</expansion>
    <keyword>$keyword</keyword>
    <color>$color</color>
    <linkcolor>$linkcolor</linkcolor>
    <kickercolor>$kickercolor</kickercolor>
    <cost>$cost</cost>
    <strength>$strength</strength>
    <health>$health</health>
</card>
END

The difference is here that there will be no \r\n at the end of line, except when you save the source file with Windows line endings. However, the here-doc does include the physical newlines, and will always terminate with a newline.

Printing Things

The print function prints the arguments seperated by the $, special variable, and then outputs the $\ variable. Both of these are empty by default. But we could provide a temporary value:

local $\ = "123";
print "foo";

Output:

foo123

Of course, we would usually set it to \n. But there is a shortcut: Just put a use feature 'say' or use 5.010 (or higher) at the start of the source code, then you can

say "foo";

which automatically appends a newline.

Opening file handles

Use lexical variables. Use three-arg open. Do proper error handling. This means either:

use autodie;
open my $fh, "<", "filename";

or

my $filename = "filename";
open my $fh, "<", $filename or die "Can't open $filename: $!";

The second argument is the mode, which would be >> for appending. It is safer to specify it explicitly, and not as part of the filename.

General note

Always use strict; use warnings; at the top of your script, this helps finding problems.

like image 94
amon Avatar answered Dec 03 '22 01:12

amon


The big issue people have with here files in Perl is they forget the semicolon on the end of the line.

Here documents come in two flavors. If you use double quotes (or none) around the here document name, it will interpolate variables and such.

If you used single quotes around the Here Document name, it won't interpolate. Carefully observe the placement of semicolons and quotation marks.

#! /usr/bin/env perl
#
use warnings;
use strict;
use feature qw(say);

my $foo = "The value of foo";
my $bar = "The value of bar";

my $heredoc =<<"END_HERE_1";
This is my first here document
and this will interpolate things
like $foo and $bar.

Heck, it even shows up correctly
in Vim.
END_HERE_1

say "$heredoc"; # All set

print  <<'END_HERE_2';
This is my first here document
and this will NOT interpolate things
like $foo and $bar.

Heck, it even shows up correctly
in Vim.
END_HERE_2

say "And that's all folks!";
like image 28
David W. Avatar answered Dec 03 '22 01:12

David W.


If you want a here-document, you can use:

#!/usr/bin/env perl
use strict;
use warnings;

print "img URL = ?    ";
chomp(my $img = <>);
print "filename = ?   ";
chomp(my $filename = <>);

# now write xml with inserted contents to a file:
open my $CARDFILE, ">>", $filename or die "Failed to open file $filename for writing";
my $type = "unregimented";
my $expansion = "contracted";
my $keyword = "lock";
my $color = "green";
my $linkcolor = "blue";
my $kickercolor = "red";
my $cost = '$0.00';
my $strength = "humungous";
my $health = "excellent";

print $CARDFILE <<EOF;
<card>\r
    <img>$img</img>\r
    <type>$type</type>\r
    <expansion>$expansion</expansion>\r
    <keyword>$keyword</keyword>\r
    <color>$color</color>\r
    <linkcolor>$linkcolor</linkcolor>\r
    <kickercolor>$kickercolor</kickercolor>\r
    <cost>$cost</cost>\r
    <strength>$strength</strength>\r
    <health>$health</health>\r
</card>\r
EOF

close $CARDFILE;

Note the compressed input, with the data now lining up vertically — though, having run the Perl script a few times, I'd far rather it took its data from command line arguments than prompted for it.

Ironically, if you use a here document, you cannot use the output record separator to specify the line ending. Without doing anything special, you can set:

$\ = "\r\n";

and each print output will be ended with CRLF. (The default value of $\ is undef, so nothing is printed as a line ending by default.) Alternatively, you can make your code more comprehensible with:

use English '-no_match_vars';

$ORS = "\r\n";

For example:

#!/usr/bin/env perl
use strict;
use warnings;
use English '-no_match_vars';

print "img URL = ?    ";
my $img = <>;
chomp($img);
print "filename = ?    ";
my $filename = <>;
chomp($filename);

# now write xml with inserted contents to a file:
open my $CARDFILE, ">>", $filename or die "Failed to open file $filename for writing";
my $type = "unregimented";
my $expansion = "contracted";
my $keyword = "lock";
my $color = "green";
my $linkcolor = "blue";
my $kickercolor = "red";
my $cost = '$0.00';
my $strength = "humungous";
my $health = "excellent";

$ORS = "\r\n";

print $CARDFILE "<card>";
print $CARDFILE "    <img>$img</img>";
print $CARDFILE "    <type>$type</type>";
print $CARDFILE "    <expansion>$expansion</expansion>";
print $CARDFILE "    <keyword>$keyword</keyword>";
print $CARDFILE "    <color>$color</color>";
print $CARDFILE "    <linkcolor>$linkcolor</linkcolor>";
print $CARDFILE "    <kickercolor>$kickercolor</kickercolor>";
print $CARDFILE "    <cost>$cost</cost>";
print $CARDFILE "    <strength>$strength</strength>";
print $CARDFILE "    <health>$health</health>";
print $CARDFILE "</card>";

close $CARDFILE;
like image 28
Jonathan Leffler Avatar answered Dec 03 '22 01:12

Jonathan Leffler