Search code examples
bashdpkgperl

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.


Solution

  • 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. 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)'