97

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 
Amelio Vazquez-Reina
  • 40,169
  • 77
  • 197
  • 294
  • 7
    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. – Tim Kennedy Oct 25 '11 at 04: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 '14 at 03:34
  • 1
    Careful, there is more to this story than initially appears. [I invite you to check my answer.](http://stackoverflow.com/questions/13667284/how-do-i-keep-functions-variables-local-to-my-zshrc/42081254#42081254) – jasonleonhard Feb 07 '17 at 03: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 08 '18 at 04:45

2 Answers2

105

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).

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • 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? – Amelio Vazquez-Reina Oct 24 '11 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. – Gilles 'SO- stop being evil' Oct 24 '11 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. – Tim Kennedy Oct 25 '11 at 04:25
  • For more brevity in a common case, zsh also lets you omit do ... done for a single command: `for i in 1 2 3; echo $i`. – Ulrich Schwarz Oct 25 '11 at 09:37
  • you can add `for i in {1..3}; do echo $i ; done` to the list of loops. (should work at least in `bash` and `zsh`) – pseyfert Jan 15 '16 at 21:22
  • 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. – Gilles 'SO- stop being evil' Jan 15 '16 at 23:35
  • @Gilles I was trying to `grep` through the output of a plugin using the command: `for ((i = 1; i<100; i++)); ghi show $i | grep "log"; done`. But it keeps giving me the error `zsh: parse error near `done'`. When I change the command to `for ((i = 1; i<100; i++)); ghi show $i | grep "log"`. It works fine. Any idea why? I typically try to stay in `python` but am not very familiar with `zsh` syntax... – alpha_989 Jun 09 '18 at 22:15
  • 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](http://zsh.sourceforge.net/Doc/Release/Shell-Grammar.html#Alternate-Forms-For-Complex-Commands). – Gilles 'SO- stop being evil' Jun 09 '18 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.

Niing
  • 723
  • 1
  • 6
  • 15