87

I have noticed there are two alternative ways of building loops in zsh:

  1. for x (1 2 3); do echo $x; done
  2. for x in 1 2 3; do echo $x; done

They both print:

1
2
3

My question is, why the two syntaxes? Is $x iterating through a different type of object in each of them?

Does bash make a similar distinction?

Addendum:

Why does the following work?:

#!/bin/zsh
a=1
b=2
c=5    

d=(a b c)    
for x in $d; do print $x;done

but this one doesn't?:

#!/bin/zsh
a=1
b=2
c=5

d=(a b c)    
# It complains with "parse error near `$d'"
for x $d; do print $x;done 
4
  • 6
    for the example that "doesn't work", which is a csh style for loop, you're missing the parentheses. for x ($d); do print $x; done will work, and it will match the first syntax that you have enumerated at the beginning of your question. Oct 25, 2011 at 4:22
  • 1
    It is SO crazy - that those two statements - indeed do not both "work the same". I literally can't get my head around it! I need to smoke some of what those shell designers were smoking', back in the day, lol.
    – alex gray
    Apr 14, 2014 at 3:34
  • Careful, there is more to this story than initially appears. I invite you to check my answer. Feb 7, 2017 at 3:48
  • 1
    I didn't see anyone else mention it, but both forms allow omission of do/done: for i ({0..4..2}) for j ({a..c}) echo "($i,$j)" = {0,2,4}x{a,b,c}. Semicolons apply to the outermost loop and redirections apply to the innermost, and if you need to change that, you only need braces: for i ({0..4..2}) { for j ({a..c}) echo "($i,$j)" } | cat -n = {1,...,9}*({0,2,4}x{a,b,c}). Of course you can combine loops with zsh expansion: for i ("("{0..4..2}","{a..c}")") echo $i
    – John P
    Jun 8, 2018 at 4:45

2 Answers 2

96

Several forms of complex commands such as loops have alternate forms in zsh. These forms are mostly inspired by the C shell, which was fairly common when zsh was young but has now disappeared. These alternate forms act exactly like the normal forms, they're just a different syntax. They're slightly shorter, but less clear.

The standard form for the for command is for x in 1 2 3; do echo $x; done, and the standard form for the while command is while test …; do somecommand; done. Ksh, bash and zsh have an alternate form of for: for ((i = 0; i < 42; i++)); do somecommand; done, which mimics the for loops of languages like Pascal or C, to enumerate integers. Other exotic forms that exist in zsh are specific to zsh (but often inspired by csh).

8
  • Thanks a lot @Guilles. I am still a bit confused though. Which form is the specific one to zsh? Also, I have added one example at the end of my OP where I can't figure out how to translate between syntaxes. If the difference is just syntax, how should I fix the second script in the addendum to make it work? Oct 24, 2011 at 23:20
  • 3
    @intrpc See my updated answer. As for your last example, it doesn't work because you wrote for x $d and zsh expects either in or ( where you wrote $d. Punctuation marks and reserved words can't come from a variable expansion, they have to be parsed before the shell can start on the variable expansions. Oct 24, 2011 at 23:33
  • @intrpc both are zsh specific. zsh was specifically designed to support both bourne, korn, and c-shell syntax, as it's a hybrid of all three. Oct 25, 2011 at 4:25
  • 1
    @pseyfert It works, but it doesn't have any advantage over the forms I show: it's less portable than the for (( … )) form and uses a lot of memory if 3 is large. Jan 15, 2016 at 23:35
  • 2
    @alpha_989 In sh-like syntax, you need do after ));. Zsh also accepts another syntax for ((…)); COMMAND where COMMAND is a single command. That's why it accepts for ((i = 1; i<100; i++)); ghi show $i | grep "log". When it sees done after this, it complains because there was no do to end. See “Alternate Forms For Complex Commands” in the manual. Jun 9, 2018 at 22:43
3

In my usecase I have many font files to patch (i.e. many .ttf files), and I want to directly type and run the script command in Terminal (not storing it in a script file):

for x in JetBrainsMonoNL* ; do \
    fontforge -script fontpatcher $x --mono -l -q --fontawesome \
        --octicons --fontlogos --mdi --powerline --powerlineextra; \
done;

The point is: JetBrainsMonoNL* will be the array to loop through, and if written in the first form:

for x in (JetBrainsMonoNL*)

the parentheses look redundant (at least to me, a newbie), and I think this is why the standard is defined without them.


I was leanring how to create this script and found this question (based on the title), and I think this answer will be helpful for newbie people like me. You can consider this as an real life example for the accepted answer.

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.