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

240 lines
7.3 KiB
Markdown
Raw Normal View History

2023-07-05 11:43:35 +02:00
# 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
2023-07-05 11:43:35 +02:00
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.
2023-07-05 11:43:35 +02:00
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)
2023-07-05 11:43:35 +02:00
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
2023-07-05 11:43:35 +02:00
the `continue N` analog to `break N`.
The equivalent construct using a [while loop](../../syntax/ccmd/while_loop.md)
2023-07-05 11:43:35 +02:00
and the [arithmetic expression compound
command](../../syntax/ccmd/arithmetic_eval.md) would be structured as:
2023-07-05 11:43:35 +02:00
(( <EXPR1> ))
while (( <EXPR2> )); do
<LIST>
(( <EXPR3> ))
done
2024-03-30 20:09:26 +01:00
The equivalent `while` construct isn't exactly the same, because both,
2023-07-05 11:43:35 +02:00
the `for` and the `while` loop behave differently in case you use the
[continue](../../commands/builtin/continuebreak.md) command.
2023-07-05 11:43:35 +02:00
### 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
}
2024-03-30 20:09:26 +01:00
This syntax is **not documented** and shouldn't be used. I found the
2023-07-05 11:43:35 +02:00
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
2023-07-05 11:43:35 +02:00
aforementioned shells, Bash does not support the analogous syntax for
[case..esac](../../syntax/ccmd/case.md#portability_considerations).
2023-07-05 11:43:35 +02:00
### 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>
2023-07-05 11:43:35 +02:00
## 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
2023-07-05 11:43:35 +02:00
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`
2023-07-05 11:43:35 +02:00
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>
2023-07-05 11:43:35 +02:00
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>
2023-07-05 11:43:35 +02:00
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
2023-07-05 11:43:35 +02:00
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
2023-07-05 11:43:35 +02:00
2
3
4
5
4
3
2
1 0 1
2
3
4
5
4
3
2
1 </code>
2023-07-05 11:43:35 +02:00
## Portability considerations
2024-03-30 20:09:26 +01:00
- C-style for loops aren't POSIX. They are available in Bash, ksh93,
2023-07-05 11:43:35 +02:00
and zsh. All 3 have essentially the same syntax and behavior.
2024-03-30 20:09:26 +01:00
- C-style for loops aren't available in mksh.
2023-07-05 11:43:35 +02:00
## Bugs
- *Fixed in 4.3*. ~~There appears to be a bug as of Bash 4.2p10 in
2024-03-30 20:09:26 +01:00
which command lists can't be distinguished from the for loop's
2023-07-05 11:43:35 +02:00
arithmetic argument delimiter (both semicolons), so command
2024-03-30 20:09:26 +01:00
substitutions within the C-style for loop expression can't contain
2023-07-05 11:43:35 +02:00
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)