mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
NF is re-evaluated at each reference, tracking the current field count of the record as it is modified -- it is not constant per record. This surprised a user whose C-style for-loop testing i <= NF, with a body that added fields, became an infinite loop; the docs sentence "their values ... change from one record to the next" implied per-record constancy. Changes: - reference-dsl-variables.md.in: add an explicit paragraph and live example showing NF changing mid-expression as fields are added and removed, plus a caution about for-loops bounded by NF whose bodies add fields, with the two idiomatic alternatives (snapshot NF first, or a key-value for-loop over $* which iterates over a copy). - reference-dsl-control-structures.md.in: note in the while/do-while section that those examples rely on NF's dynamism, and add a triple-for note warning about non-terminating NF-bounded loops. - glossary.md.in: note dynamism in the NF entry. - pkg/dsl/cst/keyword_usage.go: extend 'mlr help keyword NF' text (flows into the manpage and the usage-keywords docs section). - Also fix a pre-existing typo nearby: "if a field has 5 records" -> "if a record has 5 fields". - Regenerated docs and man pages. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b01d3db47a
commit
ad252c1b2d
11 changed files with 94 additions and 10 deletions
|
|
@ -550,7 +550,11 @@ mathematic constant
|
||||||
Stands for _number of fields_. A read-only [built-in
|
Stands for _number of fields_. A read-only [built-in
|
||||||
variable](reference-dsl-variables.md#built-in-variables) in the [Miller
|
variable](reference-dsl-variables.md#built-in-variables) in the [Miller
|
||||||
programming language](miller-programming-language.md) which shows the number of fields
|
programming language](miller-programming-language.md) which shows the number of fields
|
||||||
in the current record.
|
in the current record. Note that `NF` is dynamic: it is re-evaluated at each
|
||||||
|
reference, so if fields are added to or removed from the current record --
|
||||||
|
even within a single `put` or `filter` expression -- the value of `NF` changes
|
||||||
|
accordingly. See the [built-in-variables
|
||||||
|
section](reference-dsl-variables.md#built-in-variables) for details.
|
||||||
|
|
||||||
## NIDX
|
## NIDX
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -534,7 +534,11 @@ mathematic constant
|
||||||
Stands for _number of fields_. A read-only [built-in
|
Stands for _number of fields_. A read-only [built-in
|
||||||
variable](reference-dsl-variables.md#built-in-variables) in the [Miller
|
variable](reference-dsl-variables.md#built-in-variables) in the [Miller
|
||||||
programming language](miller-programming-language.md) which shows the number of fields
|
programming language](miller-programming-language.md) which shows the number of fields
|
||||||
in the current record.
|
in the current record. Note that `NF` is dynamic: it is re-evaluated at each
|
||||||
|
reference, so if fields are added to or removed from the current record --
|
||||||
|
even within a single `put` or `filter` expression -- the value of `NF` changes
|
||||||
|
accordingly. See the [built-in-variables
|
||||||
|
section](reference-dsl-variables.md#built-in-variables) for details.
|
||||||
|
|
||||||
## NIDX
|
## NIDX
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4128,7 +4128,10 @@ This is simply a copy of what you should see on running `man mlr` at a command p
|
||||||
M_PI: the mathematical constant pi.
|
M_PI: the mathematical constant pi.
|
||||||
|
|
||||||
1mNF0m
|
1mNF0m
|
||||||
NF: evaluates to the number of fields in the current record.
|
NF: evaluates to the number of fields in the current record. Note that NF
|
||||||
|
is dynamic: it is re-evaluated at each reference, so if fields are added to or
|
||||||
|
removed from the current record -- even within a single put/filter expression
|
||||||
|
-- the value of NF changes accordingly.
|
||||||
|
|
||||||
1mNR0m
|
1mNR0m
|
||||||
NR: evaluates to the number of the current record over all files
|
NR: evaluates to the number of the current record over all files
|
||||||
|
|
|
||||||
|
|
@ -4107,7 +4107,10 @@
|
||||||
M_PI: the mathematical constant pi.
|
M_PI: the mathematical constant pi.
|
||||||
|
|
||||||
1mNF0m
|
1mNF0m
|
||||||
NF: evaluates to the number of fields in the current record.
|
NF: evaluates to the number of fields in the current record. Note that NF
|
||||||
|
is dynamic: it is re-evaluated at each reference, so if fields are added to or
|
||||||
|
removed from the current record -- even within a single put/filter expression
|
||||||
|
-- the value of NF changes accordingly.
|
||||||
|
|
||||||
1mNR0m
|
1mNR0m
|
||||||
NR: evaluates to the number of the current record over all files
|
NR: evaluates to the number of the current record over all files
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,11 @@ x=1,y=2,3=,4=,5=,6=,7=,8=,9=,10=,foo=bar
|
||||||
x=1,y=2,3=,4=,5=,foo=bar
|
x=1,y=2,3=,4=,5=,foo=bar
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
Note that these examples rely on the fact that
|
||||||
|
[`NF`](reference-dsl-variables.md#built-in-variables) is **dynamic**: it is
|
||||||
|
re-evaluated at each reference, so each `$[NF+1] = ""` assignment increments
|
||||||
|
`NF`, and the loop makes progress toward its exit condition.
|
||||||
|
|
||||||
A `break` or `continue` within nested conditional blocks or if-statements will,
|
A `break` or `continue` within nested conditional blocks or if-statements will,
|
||||||
of course, propagate to the innermost loop enclosing them, if any. A `break` or
|
of course, propagate to the innermost loop enclosing them, if any. A `break` or
|
||||||
`continue` outside a loop is a syntax error that will be flagged as soon as the
|
`continue` outside a loop is a syntax error that will be flagged as soon as the
|
||||||
|
|
@ -524,6 +529,8 @@ Notes:
|
||||||
|
|
||||||
* As with all `for`/`if`/`while` statements in Miller, the curly braces are required even if the body is a single statement or empty.
|
* As with all `for`/`if`/`while` statements in Miller, the curly braces are required even if the body is a single statement or empty.
|
||||||
|
|
||||||
|
* Since [`NF`](reference-dsl-variables.md#built-in-variables) is **dynamic** -- it is re-evaluated at each reference -- a loop such as `for (i = 1; i <= NF; i += 1)` whose body adds fields to the current record will never terminate, since each added field increments `NF`. To loop over the fields the record had on entry, take a copy first -- e.g. `num n = NF; for (i = 1; i <= n; i += 1) { ... }` -- or use a [key-value for-loop](#key-value-for-loops) such as `for (k, v in $*)`, which iterates over a copy of the record made before the loop began.
|
||||||
|
|
||||||
## Begin/end blocks
|
## Begin/end blocks
|
||||||
|
|
||||||
Miller supports an `awk`-like `begin/end` syntax. The statements in the `begin` block are executed before any input records are read; the statements in the `end` block are executed after the last input record is read. (If you want to execute some statement at the start of each file, not at the start of the first file as with `begin`, you might use a pattern/action block of the form `FNR == 1 { ... }`.) All statements outside of `begin` or `end` are, of course, executed on every input record. Semicolons separate statements inside or outside of begin/end blocks; semicolons are required between begin/end block bodies and any subsequent statement. For example:
|
Miller supports an `awk`-like `begin/end` syntax. The statements in the `begin` block are executed before any input records are read; the statements in the `end` block are executed after the last input record is read. (If you want to execute some statement at the start of each file, not at the start of the first file as with `begin`, you might use a pattern/action block of the form `FNR == 1 { ... }`.) All statements outside of `begin` or `end` are, of course, executed on every input record. Semicolons separate statements inside or outside of begin/end blocks; semicolons are required between begin/end block bodies and any subsequent statement. For example:
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,11 @@ echo x=1,y=2 | mlr put '
|
||||||
'
|
'
|
||||||
GENMD-EOF
|
GENMD-EOF
|
||||||
|
|
||||||
|
Note that these examples rely on the fact that
|
||||||
|
[`NF`](reference-dsl-variables.md#built-in-variables) is **dynamic**: it is
|
||||||
|
re-evaluated at each reference, so each `$[NF+1] = ""` assignment increments
|
||||||
|
`NF`, and the loop makes progress toward its exit condition.
|
||||||
|
|
||||||
A `break` or `continue` within nested conditional blocks or if-statements will,
|
A `break` or `continue` within nested conditional blocks or if-statements will,
|
||||||
of course, propagate to the innermost loop enclosing them, if any. A `break` or
|
of course, propagate to the innermost loop enclosing them, if any. A `break` or
|
||||||
`continue` outside a loop is a syntax error that will be flagged as soon as the
|
`continue` outside a loop is a syntax error that will be flagged as soon as the
|
||||||
|
|
@ -341,6 +346,8 @@ Notes:
|
||||||
|
|
||||||
* As with all `for`/`if`/`while` statements in Miller, the curly braces are required even if the body is a single statement or empty.
|
* As with all `for`/`if`/`while` statements in Miller, the curly braces are required even if the body is a single statement or empty.
|
||||||
|
|
||||||
|
* Since [`NF`](reference-dsl-variables.md#built-in-variables) is **dynamic** -- it is re-evaluated at each reference -- a loop such as `for (i = 1; i <= NF; i += 1)` whose body adds fields to the current record will never terminate, since each added field increments `NF`. To loop over the fields the record had on entry, take a copy first -- e.g. `num n = NF; for (i = 1; i <= n; i += 1) { ... }` -- or use a [key-value for-loop](#key-value-for-loops) such as `for (k, v in $*)`, which iterates over a copy of the record made before the loop began.
|
||||||
|
|
||||||
## Begin/end blocks
|
## Begin/end blocks
|
||||||
|
|
||||||
Miller supports an `awk`-like `begin/end` syntax. The statements in the `begin` block are executed before any input records are read; the statements in the `end` block are executed after the last input record is read. (If you want to execute some statement at the start of each file, not at the start of the first file as with `begin`, you might use a pattern/action block of the form `FNR == 1 { ... }`.) All statements outside of `begin` or `end` are, of course, executed on every input record. Semicolons separate statements inside or outside of begin/end blocks; semicolons are required between begin/end block bodies and any subsequent statement. For example:
|
Miller supports an `awk`-like `begin/end` syntax. The statements in the `begin` block are executed before any input records are read; the statements in the `end` block are executed after the last input record is read. (If you want to execute some statement at the start of each file, not at the start of the first file as with `begin`, you might use a pattern/action block of the form `FNR == 1 { ... }`.) All statements outside of `begin` or `end` are, of course, executed on every input record. Semicolons separate statements inside or outside of begin/end blocks; semicolons are required between begin/end block bodies and any subsequent statement. For example:
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ a=eks,b=wye,i=4,x=NEW,y=0.134188
|
||||||
a=wye,b=pan,i=5,x=0.573288,y=NEW
|
a=wye,b=pan,i=5,x=0.573288,y=NEW
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
Right-hand side accesses to non-existent fields -- i.e., with index less than 1 or greater than `NF` -- return an absent value. Likewise, left-hand side accesses only refer to fields that already exist. For example, if a field has 5 records, then assigning the name or value of the 6th (or 600th) field results in a no-op.
|
Right-hand side accesses to non-existent fields -- i.e., with index less than 1 or greater than `NF` -- return an absent value. Likewise, left-hand side accesses only refer to fields that already exist. For example, if a record has 5 fields, then assigning the name or value of the 6th (or 600th) field results in a no-op.
|
||||||
|
|
||||||
<pre class="pre-highlight-in-pair">
|
<pre class="pre-highlight-in-pair">
|
||||||
<b>mlr put '$[[6]] = "NEW"' data/small</b>
|
<b>mlr put '$[[6]] = "NEW"' data/small</b>
|
||||||
|
|
@ -608,6 +608,29 @@ system environment variables at the time Miller starts. Any changes made to
|
||||||
`ENV` by assigning to it will affect any subprocesses, such as using
|
`ENV` by assigning to it will affect any subprocesses, such as using
|
||||||
[piped tee](reference-dsl-output-statements.md#redirected-output-statements).
|
[piped tee](reference-dsl-output-statements.md#redirected-output-statements).
|
||||||
|
|
||||||
|
Note that, unlike the others, **`NF` is dynamic even within a single record**:
|
||||||
|
it is re-evaluated at each reference, tracking the number of fields in the
|
||||||
|
current record as of that moment. So if you add or remove fields -- even
|
||||||
|
within a single `put` or `filter` expression -- the value of `NF` changes
|
||||||
|
accordingly:
|
||||||
|
|
||||||
|
<pre class="pre-highlight-in-pair">
|
||||||
|
<b>echo 'x=1,y=2,z=3' | mlr put '$nf1 = NF; $u = 4; $nf2 = NF; unset $x, $y; $nf3 = NF'</b>
|
||||||
|
</pre>
|
||||||
|
<pre class="pre-non-highlight-in-pair">
|
||||||
|
z=3,nf1=3,u=4,nf2=5,nf3=4
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
In particular, be careful with loops whose continuation test involves `NF`,
|
||||||
|
when the loop body adds fields to the record. For example,
|
||||||
|
`for (i = 1; i <= NF; i += 1) { $["copy_".i] = $[[[i]]] }` is an **infinite
|
||||||
|
loop**, since each new field increments `NF`, which is re-tested at each
|
||||||
|
iteration. To loop over the fields a record had on entry, either take a copy
|
||||||
|
of `NF` before the loop -- e.g. `num n = NF; for (i = 1; i <= n; i += 1) {
|
||||||
|
... }` -- or use a [key-value for-loop](reference-dsl-control-structures.md#key-value-for-loops)
|
||||||
|
such as `for (k, v in $*) { ... }`, which iterates over a copy of the record
|
||||||
|
made before the loop began.
|
||||||
|
|
||||||
Their **scope is global**: you can refer to them in any `filter` or `put` statement. The input-record reader assigns their values:
|
Their **scope is global**: you can refer to them in any `filter` or `put` statement. The input-record reader assigns their values:
|
||||||
|
|
||||||
<pre class="pre-highlight-in-pair">
|
<pre class="pre-highlight-in-pair">
|
||||||
|
|
@ -1260,7 +1283,10 @@ M_E: the mathematical constant e.
|
||||||
|
|
||||||
M_PI: the mathematical constant pi.
|
M_PI: the mathematical constant pi.
|
||||||
|
|
||||||
NF: evaluates to the number of fields in the current record.
|
NF: evaluates to the number of fields in the current record. Note that NF
|
||||||
|
is dynamic: it is re-evaluated at each reference, so if fields are added to or
|
||||||
|
removed from the current record -- even within a single put/filter expression
|
||||||
|
-- the value of NF changes accordingly.
|
||||||
|
|
||||||
NR: evaluates to the number of the current record over all files
|
NR: evaluates to the number of the current record over all files
|
||||||
being processed, starting with 1. Does not reset at the start of each file.
|
being processed, starting with 1. Does not reset at the start of each file.
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ GENMD-RUN-COMMAND
|
||||||
mlr put '$[[[NR]]] = "NEW"' data/small
|
mlr put '$[[[NR]]] = "NEW"' data/small
|
||||||
GENMD-EOF
|
GENMD-EOF
|
||||||
|
|
||||||
Right-hand side accesses to non-existent fields -- i.e., with index less than 1 or greater than `NF` -- return an absent value. Likewise, left-hand side accesses only refer to fields that already exist. For example, if a field has 5 records, then assigning the name or value of the 6th (or 600th) field results in a no-op.
|
Right-hand side accesses to non-existent fields -- i.e., with index less than 1 or greater than `NF` -- return an absent value. Likewise, left-hand side accesses only refer to fields that already exist. For example, if a record has 5 fields, then assigning the name or value of the 6th (or 600th) field results in a no-op.
|
||||||
|
|
||||||
GENMD-RUN-COMMAND
|
GENMD-RUN-COMMAND
|
||||||
mlr put '$[[6]] = "NEW"' data/small
|
mlr put '$[[6]] = "NEW"' data/small
|
||||||
|
|
@ -320,6 +320,26 @@ system environment variables at the time Miller starts. Any changes made to
|
||||||
`ENV` by assigning to it will affect any subprocesses, such as using
|
`ENV` by assigning to it will affect any subprocesses, such as using
|
||||||
[piped tee](reference-dsl-output-statements.md#redirected-output-statements).
|
[piped tee](reference-dsl-output-statements.md#redirected-output-statements).
|
||||||
|
|
||||||
|
Note that, unlike the others, **`NF` is dynamic even within a single record**:
|
||||||
|
it is re-evaluated at each reference, tracking the number of fields in the
|
||||||
|
current record as of that moment. So if you add or remove fields -- even
|
||||||
|
within a single `put` or `filter` expression -- the value of `NF` changes
|
||||||
|
accordingly:
|
||||||
|
|
||||||
|
GENMD-RUN-COMMAND
|
||||||
|
echo 'x=1,y=2,z=3' | mlr put '$nf1 = NF; $u = 4; $nf2 = NF; unset $x, $y; $nf3 = NF'
|
||||||
|
GENMD-EOF
|
||||||
|
|
||||||
|
In particular, be careful with loops whose continuation test involves `NF`,
|
||||||
|
when the loop body adds fields to the record. For example,
|
||||||
|
`for (i = 1; i <= NF; i += 1) { $["copy_".i] = $[[[i]]] }` is an **infinite
|
||||||
|
loop**, since each new field increments `NF`, which is re-tested at each
|
||||||
|
iteration. To loop over the fields a record had on entry, either take a copy
|
||||||
|
of `NF` before the loop -- e.g. `num n = NF; for (i = 1; i <= n; i += 1) {
|
||||||
|
... }` -- or use a [key-value for-loop](reference-dsl-control-structures.md#key-value-for-loops)
|
||||||
|
such as `for (k, v in $*) { ... }`, which iterates over a copy of the record
|
||||||
|
made before the loop began.
|
||||||
|
|
||||||
Their **scope is global**: you can refer to them in any `filter` or `put` statement. The input-record reader assigns their values:
|
Their **scope is global**: you can refer to them in any `filter` or `put` statement. The input-record reader assigns their values:
|
||||||
|
|
||||||
GENMD-RUN-COMMAND
|
GENMD-RUN-COMMAND
|
||||||
|
|
|
||||||
|
|
@ -4107,7 +4107,10 @@
|
||||||
M_PI: the mathematical constant pi.
|
M_PI: the mathematical constant pi.
|
||||||
|
|
||||||
1mNF0m
|
1mNF0m
|
||||||
NF: evaluates to the number of fields in the current record.
|
NF: evaluates to the number of fields in the current record. Note that NF
|
||||||
|
is dynamic: it is re-evaluated at each reference, so if fields are added to or
|
||||||
|
removed from the current record -- even within a single put/filter expression
|
||||||
|
-- the value of NF changes accordingly.
|
||||||
|
|
||||||
1mNR0m
|
1mNR0m
|
||||||
NR: evaluates to the number of the current record over all files
|
NR: evaluates to the number of the current record over all files
|
||||||
|
|
|
||||||
|
|
@ -6639,7 +6639,10 @@ M_PI: the mathematical constant pi.
|
||||||
.RS 0
|
.RS 0
|
||||||
.\}
|
.\}
|
||||||
.nf
|
.nf
|
||||||
NF: evaluates to the number of fields in the current record.
|
NF: evaluates to the number of fields in the current record. Note that NF
|
||||||
|
is dynamic: it is re-evaluated at each reference, so if fields are added to or
|
||||||
|
removed from the current record -- even within a single put/filter expression
|
||||||
|
-- the value of NF changes accordingly.
|
||||||
.fi
|
.fi
|
||||||
.if n \{\
|
.if n \{\
|
||||||
.RE
|
.RE
|
||||||
|
|
|
||||||
|
|
@ -588,7 +588,11 @@ func M_PIKeywordUsage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NFKeywordUsage() {
|
func NFKeywordUsage() {
|
||||||
fmt.Println(`evaluates to the number of fields in the current record.`)
|
fmt.Println(
|
||||||
|
`evaluates to the number of fields in the current record. Note that NF
|
||||||
|
is dynamic: it is re-evaluated at each reference, so if fields are added to or
|
||||||
|
removed from the current record -- even within a single put/filter expression
|
||||||
|
-- the value of NF changes accordingly.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NRKeywordUsage() {
|
func NRKeywordUsage() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue