11

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

14

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.