13

I am trying to learn loops and array manipulation in zsh.

Say I have two arrays in zsh with the same unknown # of elements and each of them containing a list of paths, e.g.

LOCAL_ARRAY=($LOCAL_PATH_1 $LOCAL_PATH_2 $LOCAL_PATH_3)
REMOTE_ARRAY=($REMOTE_PATH_1 $REMOTE_PATH_2 $REMOTE_PATH_3)

I would like to iterate through both arrays simultaneously with a common index, so that I can do something like:

cd $LOCAL_PATH_i
hg pull $REMOTE_PATH_i

in each iteration. How can I do this in zsh?

Also, I have searched for loop control and arrays in the zsh documentation but had little success. Is there a good tutorial that introduces the basics of loops, arrays, etc. in zsh? Something for people with little bash or shell-scripting experience but with a background in programming?

3 Answers 3

15

Section 15.2.1 “Array Subscripts” says that arrays can be index with [exp] (where exp is a numeric expression) and that elements start at index 1 (0 if KSH_ARRAYS is set).

Section 14.3 “Parameter Expansion” says that the syntax ${#array_name} will expand to the number of elements of an array.

Section 6.3 “Complex Commands” gives the syntax for a numeric for loop (as in C):
for (( initExpr ; testExpr ; stepExpr )) do … done.

Putting them all together:

for (( i = 1; i <= $#LOCAL_PATH; i++ )) do
    ( # subshell to contain the effect of the chdir
        cd $LOCAL_PATH[i]
        hg pull $REMOTE_PATH[i]
    )
done

Or, if you are using KSH_ARRAYS, then this:

for (( i = 0; i < ${#LOCAL_PATH[@]}; i++ )) do
    ( # subshell to contain the effect of the chdir
        cd ${LOCAL_PATH[i]}
        hg pull ${REMOTE_PATH[i]}
    )
done

Using KSH_ARRAYS makes arrays start with index 0, requires the use of braces for array expressions, and interprets $array as $array[0] (requiring a change to the array length expression). The syntax changes required by KSH_ARRAY will also work without KSH_ARRAY, but you still have to adjust the logic for the different index range (1 through N versus 0 through N-1).

3

You could zip the two arrays and loop over them with two variables like:

LOCAL_ARRAY=($LOCAL_PATH_1 $LOCAL_PATH_2 $LOCAL_PATH_3)
REMOTE_ARRAY=($REMOTE_PATH_1 $REMOTE_PATH_2 $REMOTE_PATH_3)

for local remote (${LOCAL_ARRAY:^REMOTE_ARRAY}) (
  cd $local &&
   hg pull $remote
)
2

If you have control over the two arrays, you can also combine them into an associated array (think of “hash” in Ruby/Perl, “map” in C++, “HashMap” in Java, or “dict” in Python):

typeset -A remotes
remotes=(
  local1 remote1
  local2 remote2
)

for local remote in "${(@kv)remotes}"  # (kv) means key and value
                                       # and (@) within quotes is to
                                       # preserve empty ones (in your
                                       # case ${(kv)remotes} would be
                                       # enough as file paths are not
                                       # meant to be empty).
do
    (
        cd $local &&
          hg pull $remote
    )
done

Note: the syntax highlight might be misleading.

2
  • Or for local (${(k)remotes}) (cd $local && hg pull $remotes[$local]) otherwise there's little benefit in using an associative array. Apr 17, 2021 at 5:49
  • @StéphaneChazelas Yeah, that’s true. I guess I wrote it that way due to a habit from other languages (such as C++) where performance matters. (Doing lookup for N times can be expensive.) Apr 17, 2021 at 20:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .