Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: [Review Request] Arrays and their usage



2021-05-31 01:24:36 +0200, René Neumann:
[...]
> I always feel a little unsure around arrays in zsh. I've currently used the
> following:
> 
>    local pkgs=( `makepkg --printsrcinfo | \
>                  sed -n -e 's/pkgname = \(.*\)$/\1/p'` )

Here's my take on answering this (repeating some of what has
already been said).

First, I'd say: `...` form of command substitution should really
be banned these days. That's really a broken heritage from the
Bourne shell. There's not good reason to keep using it these
days. The main problem with it is the handling of backslash
inside it (and the awkward nesting and the fact that it's less
legible, etc...).

The sed command could be written:

  sed -n 's/pkgname = //p'

Strictly speaking, it's not exactly equivalent with some sed
implementations when the input contains sequences of bytes that
don't form valid text in the current locale. For instance, in a
locale that uses UTF-8 as its charmap, on the output of:

printf 'foopkgname = bar\200baz\n'

yours would output foobar<LF> while mine would output
foobar<0x80>baz<LF> with GNU sed as the . regexp operator only
matches on *characters* so would not match on that 0x80 byte
which doesn't form a valid character in UTF-8.

Then, leaving `...` (or the better $(...)) unquoted performs
IFS-splitting, so you're left with the same kind of conundrum as
you get in POSIX shells when you leave any form of expansion
($param, $((arith)) as well there!) unquoted though at least
zsh doesn't perform globbing there:

either you're happy that the default value of IFS (space, tab,
newline, nul) is good enough for all splitting, or you need to
set it every time you use it (in zsh, that's for unquoted $(...)
or the $=param operator).

Here, you can do

pkgs=(
  $(
    makepkg --printsrcinfo |
     sed -n 's/pkgname = //p'
  )
)

With the default value of $IFS if you know the values don't
contain any of the $IFS characters. If that can't be guaranteed,
you'd need:

IFS=$'\n'
pkgs=(
  $(
    makepkg --printsrcinfo |
     sed -n 's/pkgname = //p'
  )
)

But in zsh, rather than using IFS-splitting which is cumbersome
to use as it relised on a global parameter, you can use explicit
splitting operators, using the "f" (short for "ps:\n:")
parameter expansion flag. Then you don't have to worry about
what $IFS may contain at the time:

pkgs=(
  ${(f)"$(
    makepkg --printsrcinfo |
     sed -n 's/pkgname = //p'
  )"}
)

Here, we're quoteing the $(...) to disable IFS-splitting and use
the "f" flag to do splitting on line feeds. Note that empty
elements are discarded.

>    pkgs=(${pkgs/#/"$DATABASE/"})

The more idiomatic zsh variant to that ksh syntax would be:

pkgs=( $DATABASE/$^pkgs )

(same as rc's pkgs = ( $DATABASE/$pkgs )).

-- 
Stephane




Messages sorted by: Reverse Date, Date, Thread, Author