3

I ran into this problem while trying to print single quotes in a Perl one-liner. I eventually figured out you have to escape them with '\''. Here's some code to illustrate my question.

Let's start with printing a text file.

perl -ne 'chomp; print "$_\n"' shortlist.txt

red
orange
yellow
green
blue

Now let's print the name of the file instead for each line.

perl -ne 'chomp; print "$ARGV\n"' shortlist.txt

shortlist.txt
shortlist.txt
shortlist.txt
shortlist.txt
shortlist.txt

Then we can add single quotes around each line.

perl -ne 'chomp; print "'$_'\n"' shortlist.txt

shortlist.txt
shortlist.txt
shortlist.txt
shortlist.txt
shortlist.txt

Wait that didn't work. Let's try again.

perl -ne 'chomp; print "'\''$_'\''\n"' shortlist.txt

'red'
'orange'
'yellow'
'green'
'blue'

So I got it working now. But I'm still confused on why '$_' evaluates to the program name. Maybe this is something easy but can someone explain or link to some documentation?

edit: I'm running Perl 5.8.8 on Red Hat 5

amon
  • 57,091
  • 2
  • 89
  • 149
hmatt1
  • 4,939
  • 3
  • 30
  • 51
  • 3
    in your 3rd example, $_ is outside the quotes, meaning it is evaluated before the string is formed and passed to `perl -ne` – njzk2 Jan 06 '14 at 17:28
  • 4
    This is a shell feature and has nothing to do with Perl. Try `echo foo; echo $_` (repeats `foo` two times on my bash or sh: `$_` is the last argument). So the important question is: what shell are you using? – amon Jan 06 '14 at 17:36

3 Answers3

12

To your shell, 'chomp; print "'$_'\n"' results in a string that's the concatenation of

  1. chomp; print " (the first sequence inside single quotes),
  2. the value of its variable $_, and
  3. \n" (the second sequence inside single quotes).

In bash, $_ "... expands to the last argument to the previous command, after expansion. ...". Since this happens to be shortlist.txt, the following is passed to perl:

chomp; print "shortlist.txt\n"

For example,

$ echo foo
foo

$ echo 'chomp; print "'$_'\n"'
chomp; print "foo\n"

Note that the above mechanism shouldn't be used to pass values to a Perl one-liner. You shouldn't be generating Perl code from the shell. See How can I process options using Perl in -n or -p mode? for how to provide arguments to a one-liner.

ikegami
  • 367,544
  • 15
  • 269
  • 518
12

You use single quotes in one-liners to protect your Perl code from being evaluated by the shell. In this command:

perl -ne 'chomp; print "'$_'\n"' shortlist.txt

you close the single quotes before $_, so the shell expands $_ to the last argument to the previous command. In your case, this happened to be the name of your input file, but the output would be different if you ran a different command first:

$ echo foo
$ perl -ne 'chomp; print "'$_'\n"' shortlist.txt
foo
foo
foo
foo
foo
ThisSuitIsBlackNot
  • 23,492
  • 9
  • 63
  • 110
5

I try to avoid quotes in one liners for just this reason. I use generalized quoting when I can:

% perl -ne 'chomp; print qq($_\n)'

Although I can avoid even that with the -l switch to get the newline for free:

% perl -nle 'chomp; print $_'

If I don't understand a one-liner, I use -MO=Deparse to see what Perl thinks it is. The first two are what you expect:

% perl -MO=Deparse -ne 'chomp; print "$_\n"' shortlist.txt
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    print "$_\n";
}
-e syntax OK

% perl -MO=Deparse -ne 'chomp; print "$ARGV\n"' shortlist.txt
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    print "$ARGV\n";
}
-e syntax OK

You see something funny in the one where you saw the problem. The variable has disappeared before perl ever saw it and there's a constant string in its place:

% perl -MO=Deparse -ne 'chomp; print "'$_'\n"' shortlist.txt

LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    print "shortlist.txt\n";
}
-e syntax OK

Your fix is curious too because Deparse puts the variable name in braces to separate it from the old package specifier ':

% perl -MO=Deparse -ne 'chomp; print "'\''$_'\''\n"' shortlist.txt
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    print "'${_}'\n";
}
-e syntax OK
brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • Nit: `perl -nle 'chomp; print $_'` is rather inconsistent in its use of `$_`, and `-nl` implies `chomp`, so `perl -nle 'chomp; print $_'` becomes `perl -nle 'chomp; print'` then `perl -nle 'print'`, then `perl -ple 1`, then `perl -ple1`. But why `-l`? `perl -pe1` or `cat`. I don't know why you suggested one of those simplifications but not the others. (I don't know why you suggested any simplification at all.) – ikegami Jan 07 '14 at 15:52
  • Even though the actual code is silly, it's what the OP used. I showed a couple of things using the original code for the fewest changes for the particular thing the OP was doing. Perhaps he simplified his script for a bigger task. *shrug* – brian d foy Jan 07 '14 at 17:05
  • You're missing `-ne` on the line `perl -MO=Deparse 'chomp; print "'$_'\n"' shortlist.txt` and yes this code was a little silly but it was a simplification of a bigger task just to illustrate the problem. Thanks! – hmatt1 Jan 07 '14 at 17:40
  • Re " I showed a couple of things using the original code for the fewest changes for the particular thing the OP was doing." What are you talking about? The only change wanted was to add quotes to the output, and `-l` doesn't do that, so it's definitely not a minimal way of achieve what was desired. – ikegami Jan 07 '14 at 18:01
  • @ikegami I actually thought his simplification was useful. I could have still demonstrated the single quote issue while writing less code if I had used the `-l` flag. It makes sense to stop there because if he had simplified further I would not have been able to compare the behavior between `$_` and `'$_'`. I simply wanted some short code to illustrate my problem, the end goal is not to just print the contents of a file. – hmatt1 Jan 08 '14 at 01:34
  • @Matt, If that's what you want, he still failed cause he could have showed you how to simplify to `perl -ple'$_="$_"'` – ikegami Jan 08 '14 at 02:44
  • 1
    @ikegami - Matt is happy and found it useful, so maybe you can move on from whatever personal problem you have with me and stop arguing against all evidence that it's not useful. – brian d foy Jan 08 '14 at 05:21
  • @brian d foy, I didn't say it wasn't useful. It's merely incomplete, so I added the missing bits. – ikegami Jan 08 '14 at 08:18