[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: URL to source file



On Thu, Jan 28, 2021 at 02:26:47AM +0100, Vincent Lefevre wrote:
> Using the "units" example, we start with:
> 
>   print -r "$(aptitude show units)"
> 
> The double quotes are necessary, as the zshexpn(1) man page says:
> for command substitution:
> 
> "If the substitution is not enclosed in double quotes, the output
> is broken into words using the IFS parameter."
> 
> But here, we want to keep the end of lines.
> 
> Then one wants to get the "Version:" line, and one can do this via
> an array. The "(f)" will turn the aptitude output to an array with
> splitting at the end of lines (\n). This can be seen with
> 
>   for i in ${(f)"$(aptitude show units)"}; print -r "[$i]"
> 
> Without the "(f)", one just gets one element.
> 
> The ${name:#pattern} syntax on an array does filtering of elements
> matching the pattern. By default, matching array elements are removed.
> Here, we want the opposite: to keep the matching array elements. This
> is done with the "(M)" flag. Here, the pattern to use is "Version:*",
> which matches lines that start with "Version:", the * character
> matching any sequence of characters, like in filename generation
> (a.k.a. globbing). So,
> 
>   print -r ${(M)${(f)"$(aptitude show units)"}:#Version:*}
> 
> gives on my machine:
> 
> Version: 2.21-1
> 
> Then, we want to remove the "Version: " prefix. This is done with
> the ${name#pattern} syntax. One could use the "Version: " pattern,
> but "* " is shorter and safe since there should be a single space.
> Hence
> 
>   print -r ${${(M)${(f)"$(aptitude show units)"}:#Version:*}#* }

I have one major objection to this: how do you handle a package that
has more than one version available?

unicorn:~$ apt-cache show firefox-esr | grep ^Version:
Version: 78.7.0esr-1~deb10u1
Version: 78.5.0esr-1~deb10u1

Setting that aside for now....

Here's how the same set of operations might look in bash:

myfunc() {
  local IFS=": " key value
  while read -r key value; do
    if [[ $key = Version ]]; then
      printf %s\\n "$value"
      return
    fi
  done < <(aptitude show units)
}

Other variations are possible, of course.  This one reads the output
of the aptitude command a line at a time, splitting each line into
two variables, before and after the first colon plus any spaces that
are adjacent to said colon.  If the first field is "Version" then it
prints the second field (plus a newline) to standard output, and stops.

The aptitude command is run via a process substitution, which means
aptitude is run in a background process that's connected via a sort
of pipe (on Linux, it'll use /dev/fd/something).  The while loop runs
in the foreground shell process and reads from that.  The read command
does the field splitting on each line, using IFS, which is local to
the function and therefore doesn't need to be set temporarily in the
read command, or restored after we're done with the function.

The [[ command is a keyword, meaning it has magic juju (special
syntax exceptions), so the $key expansion inside it doesn't need to
be double-quoted.  We could double-quote it if we wanted to.

"$value" must be double-quoted because it's being passed as a single
argument to printf, which is "only" a builtin and therefore doesn't
have any magic juju.

One of the advantages of this version over the zsh version is that it
doesn't need to read the whole output of aptitude into memory all at
once.  For this particular task, it won't matter much.  The output of
aptitude show isn't very large, and shouldn't take very long to produce,
or much memory to store.  But for commands that take a really long time
to run, or which produce copious output, reading only a line at a time
and then stopping when you've got the line you want could be a huge win.


Reply to: