简体   繁体   中英

Escaping dollar sign inside nested single quote, backtick, and quote

I would like to use Perl for running a shell command on each line of a file. The file contains package names such as firefox .

Here is the call for a single package name; it works:

dpkg-query -W -f='${binary:Package}_${Version}' firefox

If I pipe from a single-line "file" like this it does not work any more:

echo firefox | perl -ne \
  'print `dpkg-query -W -f="${binary:Package}_${Version}" $_` . "\n"'

The output is _\\n . So what's the right way to escape these dollars inside those nested quotes such that dpkg-query receives the verbatim string ${binary:Package}_${Version} without a shell or bash interpreting them as "their" variables? I've tried various permutations but to no avail so far. The output should be of the form firefox_59.0.2+build1-0ubuntu0.16.04.1\\n .

You have a format string within a shell command within a Perl script within a shell command. This level of nesting makes escaping quite difficult, so the best solution is to get rid of unnecessary levels.

Here, the simplest way will be to get rid of the inner shell command. You do not need to run the dpkg command through a shell, and could execute it directly. This will avoid having to handle any kind of shell metacharacters within Perl. For example:

perl -ne 'chomp; system q(dpkg-query), q(-W), q(-f=${binary:Package}_${Version}), $_; print qq(\n)'

This will print the command output directly to STDOUT, without capturing it first. As with using backticks, this doesn't do any error handling. To get the real exit code, you need to $? >> 8 $? >> 8 . So with proper error handling, the command would look like:

perl -ne '
  chomp;
  @command = (q(dpkg-query), q(-W), q(-f=${binary:Package}_${Version}), $_); 
  system(@command) == 0 or die sprintf qq(Command [%s] exited with status %d\n), qq(@command), $? >> 8;
  print qq(\n)'

There are two main issues:

  1. You need to somehow get ' into the string in the Perl code, which is already quoted with ' for the shell.
  2. You need to escape $ in the backticks because those have a special meaning to Perl.

Let's start with the second issue first.

dpkg-query -W -f='${binary:Package}_${Version}' firefox

is the normal command line. Wrapping that in backticks gives us

print `dpkg-query -W -f='\${binary:Package}_\${Version}' $_` . "\n"

in Perl.

Now we want to pass that code to perl on the command line, which necessitates quoting all of this for the shell:

perl -ne 'print `dpkg-query -W -f='\''\${binary:Package}_\${Version}'\'' $_` . "\n"'

If this looks confusing, every ' in the original string becomes '\\'' in the quoted version. That's because the first ' ends the single-quoted string we're in, then \\' specifies a single escaped quote, then ' starts a quoted section again. (If you look closely, you can kind of see it happen in SO's syntax highlighting.)

There's another simplification we can apply. The incoming line in $_ still contains a trailing newline, which we don't need (or want: it would break the command if there were anything after $_ ). We can remove it with the -l option, which also makes print output a \\n by default:

echo firefox | perl -lne \
  'print `dpkg-query -W -f='\''\${binary:Package}_\${Version}'\'' $_`'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM