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.