http://qs321.pair.com?node_id=1197277

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Is there a passably elegant way to persuade a Perl here document to repeat lines, e.g. under control of an external foreach?

I'm trying to generate a sequence of routing commands like

ip rule add fwmark 0x0201/0xffff table 201 ip rule add fwmark 0x0200/0xffff table 200 ip route add default via 172.27.201.1 dev eth1.201 table 201 ip route add default via 172.27.200.1 dev eth1.200 table 200

and while putting them in a here document is by far the most readable approach it would be a great improvement if the repeated lines- where the count might vary- could be generated on the fly.

MarkMLl

Replies are listed 'Best First'.
Re: hiccoughing here documents
by kcott (Archbishop) on Aug 12, 2017 at 03:26 UTC

    You can use backticks to quote the terminating string; you can also stack multiple heredocs. Here's an example to get the output you show:

    #!/usr/bin/env perl use strict; use warnings; my $rule_base = 'ip rule add fwmark 0x0201/0xffff table'; my $route_base = 'ip route add default via 172.27.201.1 dev eth1.201 t +able'; my @table_nums = (200, 201); my $routing_commands = <<`RULE` . <<`ROUTE`; for i in @table_nums; do echo "$rule_base \$i"; done RULE for i in @table_nums; do echo "$route_base \$i"; done ROUTE print $routing_commands;

    Output:

    ip rule add fwmark 0x0201/0xffff table 200 ip rule add fwmark 0x0201/0xffff table 201 ip route add default via 172.27.201.1 dev eth1.201 table 200 ip route add default via 172.27.201.1 dev eth1.201 table 201

    See "perlop: here-document syntax" for details.

    — Ken

Re: hiccoughing here documents
by AnomalousMonk (Archbishop) on Aug 11, 2017 at 22:20 UTC

    Not very pretty IMHO, but maybe something like:

    use warnings; use strict; my $n = 5; my $s = <<EOS; line 1 line two ${ \ join "\n", ("repeated line tres") x $n } line penultimate line the last EOS print "[[$s]]";
    Output:
    c:\@Work\Perl\monks\Anonymous Monk\1197277>perl hiccup_here_1.pl [[line 1 line two repeated line tres repeated line tres repeated line tres repeated line tres repeated line tres line penultimate line the last ]]
    (The explicit join statement | expression could become a call to a function returning any string.)


    Give a man a fish:  <%-{-{-{-<

Re: hiccoughing here documents
by NetWallah (Canon) on Aug 11, 2017 at 22:22 UTC
Re: hiccoughing here documents
by NetWallah (Canon) on Aug 12, 2017 at 18:02 UTC
    This is beginning to look like a use-case for Text::Template or Template::Simple.

    I think that is a much better alternative compared to the ugly solutions (including my earlier one) proposed so far.

                    All power corrupts, but we need electricity.

Re: hiccoughing here documents
by Paladin (Vicar) on Aug 11, 2017 at 22:00 UTC
    Perhaps I'm not understanding the question, but what about something like:
    my @tables = (200, 201); for my $tab (@tables) { print <<BLOCK; ip rule add fwmark 0x0${tab}/0xffff table ${tab} ip route add default via 172.27.${tab}.1 dev eth1.${tab} table ${tab} BLOCK }
Re: hiccoughing here documents
by Your Mother (Archbishop) on Aug 11, 2017 at 21:50 UTC

    Interesting idea… there might be some good trick for it. I would reach for inlined templates but I’m curious if there is a sane HEREDOC solution.

Re: hiccoughing here documents
by Anonymous Monk on Aug 12, 2017 at 09:29 UTC

    I'm settling down to work through the suggestions so far, but I think I could usefully go into a bit more detail now that I've got a better idea of formatting etc. My apologies, I've been an occasional lurker for years but haven't barked before.

    What I'm trying to do is take a pattern of some sort, and then to "hiccough" some (but not all) of the lines an indeterminate number of times. So on reflection- and my apologies for not being more explicit first time round- this might be a better example of the required output:

    iptables -t nat -N outbound-DMZ iptables -t nat -A outbound-DMZ -o eth1.201 -j SNAT --to-source 90.145 +.84.155 iptables -t nat -A outbound-DMZ -o eth1.200 -j SNAT --to-source 90.145 +.84.155 iptables -t nat -A outbound-DMZ -j RETURN iptables -t nat -A POSTROUTING -j outbound-DMZ

    where this shows that the repeated lines are typically in the middle of the block of text rather than at the beginning or end. One idea that occured to me was something like

    $commands = &reinterpolate <<"EOF"; iptables -t nat -N outbound-DMZ foreach \$iface (keys \%ipAddress) { iptables -t nat -A outbound-DMZ -o \$iface -j SNAT --to-source \$ipA +ddress{\$iface} } iptables -t nat -A outbound-DMZ -j RETURN iptables -t nat -A POSTROUTING -j outbound-DMZ EOF

    where &reinterpolate picks up the lines with and between braces and (eventually) applies eval(), but I can see that some of the suggestions are approaching that.

    The system this would be running on is a minimal router, so if possible I'd like to avoid anything that isn't in a typical Linux distro.

    MarkMLl

      Something like this works:

      sub IFACE { my $pattern = shift @_; my $result; foreach $iface (@_) { $result .= (eval "\"$pattern\"") . "\n"; } chomp $result; return $result; } &utter( <<"EOF" ); iptables -t nat -N outbound-DMZ @{[ &IFACE( "iptables -t nat -A outbound-DMZ -s $lanCidr -o \$iface -j SNAT --to +-source $dmzIp", (keys %ipAddress)) ]} iptables -t nat -A outbound-DMZ -j RETURN EOF

      I know it's unconventional but I've capitalised the function name to emphasise its relationship to the name of the variable being interpolated, I felt that trying to pass that name as a parameter would be pushing my luck. It's (obviously) very sensitive to the double-quotes being correct, both in the function and in the heredoc itself. The one thing I wasn't able to get working was ~ to enable indents (5.20.2 on Debian Jessie armhf), but that's a fairly detail since the nearer the heredoc is to the actual stuff going into the tables the better.

      Many thanks to everybody, especially NetWallah.

      Pax vobiscum, MarkMLl

        Here's a version that's not quite so funky (but still kinda ugly). This is a situation in which a prototype is actually useful. If the  hiccup() function is defined in the source file in which it's used | invoked, either the full definition of the function or a prototype declaration must appear before first invocation of the function. If the function is moved to a module, a use statement must appear before first invocation.

        File hiccup_here_3.pl:

        use warnings; use strict; use Hic; # sub hiccup (&@); my $lanCidr = 'WHATEVER'; my $dmzIp = 'SOMEWHERE'; my %ipAddress = qw(HERE we_are THERE it_goes EVERYWHERE at_once); my @array = qw(WIBBLE BOFF); my $s = << "EOF"; iptables -t nat -N outbound-DMZ @{[ hiccup { "iptables -t nat -s $lanCidr -o $_ -j SNAT --to-source $dmzIp" } keys %ipAddress ]} whatever else here @{[ hiccup { "flocculate -f $_ -j SNAT --to-source $dmzIp" } @array ]} iptables -t nat -A outbound-DMZ -j RETURN EOF # sub hiccup (&@) { return join "\n", map $_[0]->(), @_[ 1 .. $#_ ]; } print "[[$s]]";

        Module Hic.pm:

        package Hic; use warnings; use strict; use parent 'Exporter'; our $VERSION = '0.1.0'; our @EXPORT = qw(hiccup); # default exported symbol(s) our @EXPORT_OK = qw(); # optional exported symbol(s) (none) sub hiccup (&@) { return join "\n", map $_[0]->(), @_[ 1 .. $#_ ]; } 1;

        However it's invoked, the function produces the following output:

        c:\@Work\Perl\monks\Anonymous Monk\1197277>perl hiccup_here_3.pl [[iptables -t nat -N outbound-DMZ iptables -t nat -s WHATEVER -o EVERYWHERE -j SNAT --to-source SOMEWHER +E iptables -t nat -s WHATEVER -o THERE -j SNAT --to-source SOMEWHERE iptables -t nat -s WHATEVER -o HERE -j SNAT --to-source SOMEWHERE whatever else here flocculate -f WIBBLE -j SNAT --to-source SOMEWHERE flocculate -f BOFF -j SNAT --to-source SOMEWHERE iptables -t nat -A outbound-DMZ -j RETURN ]]

        Update: Screwed up initial post of hiccup_here_3.pl file. Fixed.


        Give a man a fish:  <%-{-{-{-<