bash

How to make bash interpret $(command) when reading from a file?


I can run the following from the bash command line:

$ pkg="linux-headers-$(uname -r)"
$ dpkg -l "${pkg}"  |  grep '^ii '
ii  linux-headers-5.15.0-130-generic 5.15.0-130.140~20.04.1 amd64        Linux kernel headers for version 5.15.0 on 64 bit x86 SMP

However, when I create a file with the package name in it and run the same command reading the file input, it fails. Create the file ...

$ echo "build-essential" > pkgs.list
$ echo "linux-headers-\$(uname -r)"  >>  pkgs.list
$ echo "gcc" >> pkgs.list
$ cat pkgs.list
build-essential
linux-headers-$(uname -r)
gcc

... and read it as input:

$ IFS=$'\n' ; while read pkgline ; do dpkg -l "${pkgline}" | grep '^ii ' ; done  <  pkgs.list
ii  build-essential 12.8ubuntu1.1 amd64        Informational list of build-essential packages
dpkg-query: no packages found matching linux-headers-$(uname -r)
ii  gcc            4:9.3.0-1ubuntu2 amd64        GNU C compiler

What needs to change to make the dpkg -l command work inside the loop, reading from a file, the way it did on the command line? I have full control over the file contents and the while loop, but I prefer not to substitute the value of $(uname -r) in the file. The text should remain "$(uname -r)" (or some variation of that) in the file. I have tried several ways to use "eval" without success, including the answer to bash : read a line from a file and interpret this line. Perhaps I'm being dense, but if that's the answer I don't see how to apply it to this case.

Edit: Hmm, yes, I was dense. The ways I tried to use "eval" were:

dpkg -l eval ${pkgline}
dpkg -l ${!pkgline}
p=eval "${pkgline} ; dpkg -l "${p}"

and even

dpkg -l "eval echo -e ${pkgline}"

after reading bash : read a line from a file and interpret this line. But what works is the straightforward:

eval dpkg -l "${pkgline}"

Well, thank you for asking.


Solution

  • Using echo instead of dpkg which I don't have on my machine:

    $ while IFS= read -r pkgline; do eval echo "$pkgline"; done < pkgs.list
    build-essential
    linux-headers-3.5.4-1.x86_64
    gcc
    

    Just replace echo with dpkg -l.

    The above is assuming you're in control of the contents of your input file and so can't be at risk of executing some malicious code via the eval.