Search code examples
syntax-errorfilehandleperl

Syntax error using a filehandle stored in a hash value


I am trying, with a one-liner, to write lines read from an input source to different files according to the contents of a field of the line itself.

My method was to use a hash to store the filehandles in order to avoid opening the same file multiple times, adding keys to that hash dynamically once a value for the field which hadn't appeared before is found on a new line read.

However using the hash value which stores the filehandle with a common print/printf call, such as

print $hash{ $field_value } "String"

results in a compilation error. Replacing $hash{ $field_value } with STDERR, for instance, results in a clean compilation.

Below an example with a sample one-liner.

$ cat /tmp/input_file 
mail1.sql:INSERT INTO
mail2.sql:INSERT INTO
mail3.sql:INSERT INTO
mail4.sql:INSERT INTO
mail6.sql:INSERT INTO
mail7.sql:INSERT INTO
mail8.sql:INSERT INTO
mail9.sql:INSERT INTO
maildev.sql:INSERT INTO

$ perl -C -nE '
m{^(?<server>mail[^.]+)\.sql:(?<string>INSERT INTO)};
exists $fd->{$+{server}} or open($fd->{$+{server}}, ">>", "/tmp/". $+{server}.".tmp.sql");
print sprintf(qq{server %s.domain.tld command %s;\n}, $+{server}, $+{string});
' /tmp/input_file 
server mail1.domain.tld command INSERT INTO;
server mail2.domain.tld command INSERT INTO;
server mail3.domain.tld command INSERT INTO;
server mail4.domain.tld command INSERT INTO;
server mail6.domain.tld command INSERT INTO;
server mail7.domain.tld command INSERT INTO;
server mail8.domain.tld command INSERT INTO;
server mail9.domain.tld command INSERT INTO;
server maildev.domain.tld command INSERT INTO;

# The files were opened, creating them. print-ing to STDOUT works fine.

$ ls -1  /tmp/mail*sql
/tmp/mail1.tmp.sql
/tmp/mail2.tmp.sql
/tmp/mail3.tmp.sql
/tmp/mail4.tmp.sql
/tmp/mail6.tmp.sql
/tmp/mail7.tmp.sql
/tmp/mail8.tmp.sql
/tmp/mail9.tmp.sql
/tmp/maildev.tmp.sql

# Now using $fd->{$+{server}} as filehandle for print

$ perl -C -nE '
m{^(?<server>mail[^.]+)\.sql:(?<string>INSERT INTO)};
exists $fd->{$+{server}} or open($fd->{$+{server}}, ">>", "/tmp/". $+{server}.".tmp.sql");
print $fd->{$+{server}} sprintf(qq{server %s.domain.tld command %s;\n}, $+{server}, $+{string});
' /tmp/input_file
syntax error at -e line 4, near "} sprintf"
syntax error at -e line 5, near ";}"
Execution of -e aborted due to compilation errors.

# Simplifying further

$ perl -C -nE '
my $a = "test";                                      
exists $fd->{$a} or open($fd->{$a}, ">>", "/tmp/". $a.".tmp.sql");
print $fd->{$a} sprintf(qq{AAA %s AAA\n}, $a);
' /tmp/input_file
syntax error at -e line 4, near "} sprintf"
BEGIN not safe after errors--compilation aborted at -e line 4.

I've attempted this using perl 5.22 and 5.30, just to check whether it was some kind of bug fixed recently.

Maybe I am missing something obvious, but I can't see what.

Does anyone have an idea?

Thanks in advance.


Solution

  • You need to wrap the handle in a bare block.

    print { $hash{ $field_value } } "String";
    

    This is documented in the print perldoc.

    If you're storing handles in an array or hash, or in general whenever you're using any expression more complex than a bareword handle or a plain, unsubscripted scalar variable to retrieve it, you will have to use a block returning the filehandle value instead, in which case the LIST may not be omitted:

    print { $files[$i] } "stuff\n";
    print { $OK ? *STDOUT : *STDERR } "stuff\n";