4

For no justifiable reason at all, I have a pretty substantial Perl script embedded within a Bash function that is being invoked within an autoenv .env file.

It looks something like this:

perl='
    $inverse = "\e[7m";
    $invoff  = "\e[27m";
    $bold    = "\e[1m";
    ⋮
'

perl -e "$perl" "$inputfile"

I understand that standalone Perl scripts and the PATH variable are a thing, and I understand that Term::ANSIColor is a thing. This is not about that.

My question is, if there's a syntax error in the embedded Perl code, how can I get Perl to report the actual line number within the parent shell script?

For example, say the perl= assignment occurs on line 120 within that file, but there's a syntax error on the 65th line of actual Perl code. I get this:

syntax error at -e line 65, near "s/(#.*)$/$comment\1$endcomment/"
Execution of -e aborted due to compilation errors.

…but I want to see this (the actual line number in the parent script) instead:

syntax error at -e line 185, near "s/(#.*)$/$comment\1$endcomment/"

Things I've tried (that didn't work):

  • assigning to __LINE__
    • don't even know why I thought that would work; it's not a variable, it's a constant, and you get an error stating the same
  • assigning to $. ($INPUT_LINE_NUMBER with use English)
    • I was pretty sure this wasn't going to work anyway, because this is like NR in Awk, and this clearly isn't what this is for
TheDudeAbides
  • 1,821
  • 1
  • 21
  • 29

2 Answers2

9

As described in perlsyn, you can use the following directive to set the line number and (optionally) the file name of the subsequent line:

#line 42 "file.pl"

This means that you could use

#!/bin/sh

perl="#line 4 \"$0\""'
warn("test");
'

perl -e "$perl"

Output:

$ ./a.sh
test at ./a.sh line 4.

There's no clean way to avoid hardcoding the line number when using sh, but it is possible.

#!/bin/sh

script_start=$( perl -ne'if (/^perl=/) { print $.+1; last }' -- "$0" )
perl="#line $script_start \"$0\""'
warn("test");
'

perl -e "$perl"

On the other hand, bash provides the current line number.

#!/bin/bash

script_start=$(( LINENO + 2 ))
perl="#line $script_start \"$0\""'
warn("test");
'

perl -e "$perl"
ikegami
  • 367,544
  • 15
  • 269
  • 518
4

There is this useful tidbit in the perlrun man page, under the section for -x, which "tells Perl that the program is embedded in a larger chunk of unrelated text, such as in a mail message."

All references to line numbers by the program (warnings, errors, ...) will treat the #! line as the first line. Thus a warning on the 2nd line of the program, which is on the 100th line in the file will be reported as line 2, not as line 100. This can be overridden by using the #line directive. (See Plain Old Comments (Not!) in perlsyn)

Based on the bolded statement, adding #line NNN (where NNN is the actual line number of the parent script where that directive appears) achieves the desired effect:

perl='#line 120
    $inverse = "\e[7m";
    $invoff  = "\e[27m";
    $bold    = "\e[1m";
    ⋮
'
⋮
TheDudeAbides
  • 1,821
  • 1
  • 21
  • 29
  • 1
    This outputs `-e` as the file name. You can pass a second argument to `#line` providing the file name. Place it in double-quotes. – ikegami Sep 11 '20 at 13:24
  • @ikegami This was such a kludgy script to begin with, I was totally fine with the `-e`, but this is a great tip! – TheDudeAbides Sep 11 '20 at 14:02