In a new addition to a script I am working on, I want to loop through parameters associated with a flag until zsh reaches the next flag in the command. For instance, when the user enters the following:

datt -p Package1 Package2 Package3 --build

The script should take Package1, Package2, and Package3 and assign these each to a place in an array. My thought process is to do something similar to this:

for i in "$@"; do

My question becomes, then, how do I write the script in such a way that this loop keeps going until it hits the next flag on the command line? With the command shown at the beginning of my question (datt -p Package1 Package2 Package3 --build), how do I prevent the for loop from capturing --build as a package name and make it stop adding strings to the array after PackageN?

  • So "datt" is the name of the script? Would putting options before the subjects to be an option? I.e. `datt -p --build Package1 Package2 Package3'? Commented Sep 15, 2021 at 19:13
  • would all the non-option arguments always go the same array? Or does -p have something to do with it? The usual way would be to take options first, and non-options after, like Peregrino69 suggested above. That would be directly supported by e.g. getopts, though it only does short options.
    – ilkkachu
    Commented Sep 15, 2021 at 20:39
  • 1
    @Karie I have removed the bash tag as your question explicitly mentions zsh. If the bash tag is warranted, please mention this in the question (and put the tag back in ;) ).
    – AdminBee
    Commented Sep 16, 2021 at 13:28

3 Answers 3


Argument parsing can be annoying to get right. I would suggest you use getopt(1). From here you can specify the same flag multiple times:

datt -p Package1 -p Package2 -p Package3 --build

If for some reason you can't reuse the flag (not sure what that would be but...) you could also pass the arg as a single item and break on space:

datt -p "Package1 Package2 Package3" --build
  • The second option might work nicely, assuming I can put the full string into an area, breaking each value on the space and storing them as individual variables in the array. Is that possible?
    – Karie
    Commented Sep 15, 2021 at 20:54
  • Certainly possible, though you also have a couple good answers above, I think. Commented Sep 15, 2021 at 23:25

To do this kind of parsing, it's easier to use a while loop and shift until the array is empty.

while (($#)); do
  case $1 in
      while shift; [[ $1 != -* ]]; do

You can use the i subscript flag to perform the inner loop looking for the next option for you. $array[(i)PATTERN] is the index of the first element matching PATTERN, with indices starting at 1, so subtract 1 to get the number of elements left of that. If there is no match for PATTERN, the value is one plus the index of the last element, precisely so that code like this works in that case as well.

while (($#)); do
  case $1 in
    -p) ((n = $@[(i)-*] - 1)); packages+=($@[1,$n]); shift $n;;

The syntax you describe is non-standard. For this reason, I recommend against it: it's likely to confuse your users. Normally datt -p Package1 Package2 Package3 --build would do the same thing as datt -p Package1 Package2 --build Package3.


Usually, it'd be sanest to loop over all options in one loop, and drop the stuff in the right places there. Usually, I wouldn't do processing from the argument-parsing loop, but it might make sense if the order of options matters.

E.g. a loop like this (for Bash or zsh):

while [ "$#" -gt 0 ]; do
    case $1 in
    -p)      echo "p option seen...";;
    --build) echo "--build option seen after ${#packages[@]} packages...";;
    *)       echo "adding package '$1'";

typeset -p packages;

would give:

$ zsh dattest.sh -p foo bar doo --build
p option seen...
adding package 'foo'
adding package 'bar'
adding package 'doo'
--build option seen after 3 packages...
typeset -a packages=( foo bar doo )

You must log in to answer this question.

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