bash-hackers-wiki/docs/syntax/ccmd/c_for.md

240 lines
7.3 KiB
Markdown

# The C-style for-loop
## Synopsis
for (( <EXPR1> ; <EXPR2> ; <EXPR3> )); do
<LIST>
done
# as a special case: without semicolon after ((...))
for (( <EXPR1> ; <EXPR2> ; <EXPR3> )) do
<LIST>
done
# alternative, historical and undocumented syntax
for (( <EXPR1> ; <EXPR2> ; <EXPR3> )) {
<LIST>
}
## Description
The C-style for-loop is a [compound
command](../../syntax/basicgrammar.md#compound_commands) derived from the
equivalent ksh88 feature, which is in turn derived from the C \"for\"
keyword. Its purpose is to provide a convenient way to evaluate
arithmetic expressions in a loop, plus initialize any required
arithmetic variables. It is one of the main \"loop with a counter\"
mechanisms available in the language.
The `((;;))` syntax at the top of the loop is not an ordinary
[arithmetic compound command](../../syntax/ccmd/arithmetic_eval.md), but is part
of the C-style for-loop's own syntax. The three sections separated by
semicolons are [arithmetic expression](../../syntax/arith_expr.md) contexts.
Each time one of the sections is to be evaluated, the section is first
processed for: brace, parameter, command, arithmetic, and process
substitution/expansion as usual for arithmetic contexts. When the loop
is entered for the first time, `<EXPR1>` is evaluated, then `<EXPR2>` is
evaluated and checked. If `<EXPR2>` is true, then the loop body is
executed. After the first and all subsequent iterations, `<EXPR1>` is
skipped, `<EXPR3>` is evaluated, then `<EXPR2>` is evaluated and checked
again. This process continues until `<EXPR2>` is false.
- `<EXPR1>` is to **initialize variables** before the first run.
- `<EXPR2>` is to **check** for a termination condition. This is
always the last section to evaluate prior to leaving the loop.
- `<EXPR3>` is to **change** conditions after every iteration. For
example, incrementing a counter.
:!: If one of these arithmetic expressions in the for-loop is empty, it
behaves as if it would be 1 (**TRUE** in arithmetic context).
:!: Like all loops (Both types of `for`-loop, `while` and `until`), this
loop can be:
- Terminated (broken) by the [break](../../commands/builtin/continuebreak.md)
builtin, optionally as `break N` to break out of `N` levels of
nested loops.
- Forced immediately to the next iteration using the
[continue](../../commands/builtin/continuebreak.md) builtin, optionally as
the `continue N` analog to `break N`.
The equivalent construct using a [while loop](../../syntax/ccmd/while_loop.md)
and the [arithmetic expression compound
command](../../syntax/ccmd/arithmetic_eval.md) would be structured as:
(( <EXPR1> ))
while (( <EXPR2> )); do
<LIST>
(( <EXPR3> ))
done
The equivalent `while` construct isn't exactly the same, because both,
the `for` and the `while` loop behave differently in case you use the
[continue](../../commands/builtin/continuebreak.md) command.
### Alternate syntax
Bash, Ksh93, Mksh, and Zsh also provide an alternate syntax for the
`for` loop - enclosing the loop body in `{...}` instead of
`do ... done`:
for ((x=1; x<=3; x++))
{
echo $x
}
This syntax is **not documented** and shouldn't be used. I found the
parser definitions for it in 1.x code, and in modern 4.x code. My guess
is that it's there for compatibility reasons. Unlike the other
aforementioned shells, Bash does not support the analogous syntax for
[case..esac](../../syntax/ccmd/case.md#portability_considerations).
### Return status
The return status is that of the last command executed from `<LIST>`, or
`FALSE` if any of the arithmetic expressions failed.
## Alternatives and best practice
<div center round todo 60%>TODO: Show some alternate usages involving
functions and local variables for initialization.</div>
## Examples
### Simple counter
A simple counter, the loop iterates 101 times (\"0\" to \"100\" are 101
numbers -> 101 runs!), and everytime the variable `x` is set to the
current value.
- It **initializes** `x = 0`
- Before every iteration it **checks** if `x ≤ 100`
- After every iteration it **changes** `x++`
```{=html}
<!-- -->
```
for ((x = 0 ; x <= 100 ; x++)); do
echo "Counter: $x"
done
### Stepping counter
This is the very same counter (compare it to the simple counter example
above), but the **change** that is made is a `x += 10`. That means, it
will count from 0 to 100, but with a **step of 10**.
for ((x = 0 ; x <= 100 ; x += 10)); do
echo "Counter: $x"
done
### Bits analyzer
This example loops through the bit-values of a Byte, beginning from 128,
ending at 1. If that bit is set in the `testbyte`, it prints \"`1`\",
else \"`0`\" => it prints the binary representation of the `testbyte`
value (8 bits).
#!/usr/bin/env bash
# Example written for http://wiki.bash-hackers.org/syntax/ccmd/c_for#bits_analyzer
# Based on TheBonsai's original.
function toBin {
typeset m=$1 n=2 x='x[(n*=2)>m]'
for ((x = x; n /= 2;)); do
printf %d $(( m & n && 1))
done
}
function main {
[[ $1 == +([0-9]) ]] || return
typeset result
if (( $(ksh -c 'printf %..2d $1' _ "$1") == ( result = $(toBin "$1") ) )); then
printf '%s is %s in base 2!\n' "$1" "$result"
else
echo 'Oops, something went wrong with our calculation.' >&2
exit 1
fi
}
main "${1:-123}"
# vim: set fenc=utf-8 ff=unix ft=sh :
<div hide>
testbyte=123
for (( n = 128 ; n >= 1 ; n /= 2 )); do
if (( testbyte & n )); then
printf %d 1
else
printf %s 0
fi
done
echo
</div>
Why that one begins at 128 (highest value, on the left) and not 1
(lowest value, on the right)? It's easier to print from left to
right\...
We arrive at 128 for `n` through the recursive arithmetic expression
stored in `x`, which calculates the next-greatest power of 2 after `m`.
To show that it works, we use ksh93 to double-check the answer, because
it has a built-in feature for `printf` to print a representation of any
number in an arbitrary base (up to 64). Very few languages have that
ability built-in, even things like Python.
### Up, down, up, down\...
This counts up and down from `0` to `${1:-5}`, `${2:-4}` times,
demonstrating more complicated arithmetic expressions with multiple
variables.
for (( incr = 1, n=0, times = ${2:-4}, step = ${1:-5}; (n += incr) % step || (incr *= -1, --times);)); do
printf '%*s\n' "$((n+1))" "$n"
done
<code> \~ \$ bash <(xclip -o) 1
2
3
4
5
4
3
2
1 0 1
2
3
4
5
4
3
2
1 </code>
## Portability considerations
- C-style for loops aren't POSIX. They are available in Bash, ksh93,
and zsh. All 3 have essentially the same syntax and behavior.
- C-style for loops aren't available in mksh.
## Bugs
- *Fixed in 4.3*. ~~There appears to be a bug as of Bash 4.2p10 in
which command lists can't be distinguished from the for loop's
arithmetic argument delimiter (both semicolons), so command
substitutions within the C-style for loop expression can't contain
more than one command.~~
## See also
- Internal: [Arithmetic expressions](../../syntax/arith_expr.md)
- Internal: [The classic for-loop](../../syntax/ccmd/classic_for.md)
- Internal: [The while-loop](../../syntax/ccmd/while_loop.md)