mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-19 01:15:21 +00:00
1454 lines
67 KiB
HTML
1454 lines
67 KiB
HTML
POKI_PUT_TOC_HERE
|
|
|
|
<p/>
|
|
<button style="font-weight:bold;color:maroon;border:0" onclick="vis_expand_all_body_sections();" href="javascript:;">Expand all sections</button>
|
|
<button style="font-weight:bold;color:maroon;border:0" onclick="vis_collapse_all_body_sections();" href="javascript:;">Collapse all sections</button>
|
|
|
|
<!-- ================================================================ -->
|
|
<h1>Overview</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_overview');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_overview" style="display: block">
|
|
|
|
<p/> Here’s comparison of verbs and <code>put</code>/<code>filter</code> DSL expressions:
|
|
|
|
<table border=1>
|
|
<tr> <td>
|
|
Example:
|
|
POKI_RUN_COMMAND{{mlr stats1 -a sum -f x -g a data/small}}HERE
|
|
<p/>
|
|
<ul>
|
|
<li/> Verbs are coded in C
|
|
<li/> They run a bit faster
|
|
<li/> They take fewer keystrokes
|
|
<li/> There is less to learn
|
|
<li/> Their customization is limited to each verb’s options
|
|
</ul>
|
|
</td>
|
|
<td>
|
|
Example:
|
|
POKI_RUN_COMMAND{{mlr put -q '@x_sum[$a] += $x; end{emit @x_sum, "a"}' data/small}}HERE
|
|
<ul>
|
|
<li/> You get to write your own DSL expressions
|
|
<li/> They run a bit slower
|
|
<li/> They take more keystrokes
|
|
<li/> There is more to learn
|
|
<li/> They are highly customizable
|
|
</ul>
|
|
</td> </tr>
|
|
</table>
|
|
|
|
<p/>Please see <a href="reference-verbs.html">here</a> for information on
|
|
verbs other than <code>put</code> and <code>filter</code>.
|
|
|
|
<p/>
|
|
The essential usages of <code>mlr filter</code> and <code>mlr put</code> are for
|
|
record-selection and record-updating expressions, respectively. For example, given the following input data:
|
|
|
|
POKI_RUN_COMMAND{{cat data/small}}HERE
|
|
|
|
<p/> you might retain only the records whose <code>a</code> field has value <code>eks</code>:
|
|
|
|
POKI_RUN_COMMAND{{mlr filter '$a == "eks"' data/small}}HERE
|
|
|
|
<p/> or you might add a new field which is a function of existing fields:
|
|
|
|
POKI_RUN_COMMAND{{mlr put '$ab = $a . "_" . $b ' data/small}}HERE
|
|
|
|
<p/>The two verbs <code>mlr filter</code> and <code>mlr put</code> are essentially the
|
|
same. The only differences are:
|
|
|
|
<ul>
|
|
|
|
<li/> Expressions sent to <code>mlr filter</code> must end with a boolean expression,
|
|
which is the filtering criterion;
|
|
|
|
<li/> <code>mlr filter</code> expressions may not
|
|
reference the <code>filter</code> keyword within them; and
|
|
|
|
<li/> <code>mlr filter</code> expressions may not use <code>tee</code>, <code>emit</code>,
|
|
<code>emitp</code>, or <code>emitf</code>.
|
|
|
|
</ul>
|
|
|
|
<p/> All the rest is the same: in particular, you can define and invoke
|
|
functions and subroutines to help produce the final boolean statement, and
|
|
record fields may be assigned to in the statements preceding the final boolean
|
|
statement.
|
|
|
|
<p/>There are more details and more choices, of course, as detailed in the following sections.
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Syntax</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_syntax');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_syntax" style="display: block">
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Expression formatting</h2>
|
|
|
|
<p/>Multiple expressions may be given, separated by semicolons, and each may refer to the ones before:
|
|
|
|
POKI_RUN_COMMAND{{ruby -e '10.times{|i|puts "i=#{i}"}' | mlr --opprint put '$j = $i + 1; $k = $i +$j'}}HERE
|
|
|
|
Newlines within the expression are ignored, which can help increase legibility of complex expressions:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/put-multiline-example.txt)HERE
|
|
|
|
POKI_RUN_COMMAND{{mlr --opprint filter '($x > 0.5 && $y < 0.5) || ($x < 0.5 && $y > 0.5)' then stats2 -a corr -f x,y data/medium}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Expressions from files</h2>
|
|
|
|
<p/>The simplest way to enter expressions for <code>put</code> and <code>filter</code> is between single quotes on the command line, e.g.
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/fe-example-1.sh)HERE
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/fe-example-2.sh)HERE
|
|
|
|
<p/>You may, though, find it convenient to put expressions into files for reuse, and read them
|
|
<b>using the -f option</b>. For example:
|
|
|
|
POKI_RUN_COMMAND{{cat data/fe-example-3.mlr}}HERE
|
|
POKI_RUN_COMMAND{{mlr --from data/small put -f data/fe-example-3.mlr}}HERE
|
|
|
|
<p/>If you have some of the logic in a file and you want to write the rest on the command line, you
|
|
can <b>use the -f and -e options together</b>:
|
|
|
|
POKI_RUN_COMMAND{{cat data/fe-example-4.mlr}}HERE
|
|
POKI_RUN_COMMAND{{mlr --from data/small put -f data/fe-example-4.mlr -e '$xy = f($x, $y)'}}HERE
|
|
|
|
<p/>A suggested use-case here is defining functions in files, and calling them from command-line expressions.
|
|
|
|
<p/>Another suggested use-case is putting default parameter values in files, e.g. using
|
|
<code>begin{@count=is_present(@count)?@count:10}</code> in the file, where you can precede that using
|
|
<code>begin{@count=40}</code> using <code>-e</code>.
|
|
|
|
<p/>Moreover, you can have one or more <code>-f</code> expressions (maybe one
|
|
function per file, for example) and one or more <code>-e</code> expressions on the
|
|
command line. If you mix <code>-f</code> and <code>-e</code> then the expressions are
|
|
evaluated in the order encountered. (Since the expressions are all simply
|
|
concatenated together in order, don’t forget intervening semicolons: e.g.
|
|
not <code>mlr put -e '$x=1' -e '$y=2 ...'</code> but rather <code>mlr put -e '$x=1;' -e
|
|
'$y=2' ...</code>.)
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Semicolons, commas, newlines, and curly braces</h2>
|
|
|
|
<p/>Miller uses <b>semicolons as statement separators</b>, not statement terminators. This means you can write:
|
|
|
|
POKI_INCLUDE_ESCAPED(data/semicolon-example.txt)HERE
|
|
|
|
<p/>Semicolons are optional after closing curly braces (which close conditionals and loops as discussed below).
|
|
|
|
POKI_RUN_COMMAND{{echo x=1,y=2 | mlr put 'while (NF < 10) { $[NF+1] = ""} $foo = "bar"'}}HERE
|
|
POKI_RUN_COMMAND{{echo x=1,y=2 | mlr put 'while (NF < 10) { $[NF+1] = ""}; $foo = "bar"'}}HERE
|
|
|
|
<p/>Semicolons are required between statements even if those statements are on
|
|
separate lines. <b>Newlines</b> are for your convenience but have no syntactic
|
|
meaning: line endings do not terminate statements. For example, adjacent
|
|
assignment statements must be separated by semicolons even if those statements
|
|
are on separate lines:
|
|
|
|
POKI_INCLUDE_ESCAPED(data/newline-example.txt)HERE
|
|
|
|
<p/><b>Trailing commas</b> are allowed in function/subroutine definitions,
|
|
function/subroutine callsites, and map literals. This is intended for (although
|
|
not restricted to) the multi-line case:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/trailing-commas.sh)HERE
|
|
|
|
<p/>Bodies for all compound statements must be enclosed in <b>curly braces</b>, even if the body is a single statement:
|
|
POKI_CARDIFY{{mlr put 'if ($x == 1) $y = 2' # Syntax error}}HERE
|
|
POKI_CARDIFY{{mlr put 'if ($x == 1) { $y = 2 }' # This is OK}}HERE
|
|
|
|
<p/>Bodies for compound statements may be empty:
|
|
|
|
POKI_CARDIFY{{mlr put 'if ($x == 1) { }' # This no-op is syntactically acceptable}}HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Variables</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_variables');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_variables" style="display: block">
|
|
|
|
<p/>Miller has the following kinds of variables:
|
|
|
|
<p/> <b>Built-in variables</b> such as <code>NF</code>, <code>NF</code>,
|
|
<code>FILENAME</code>, <code>M_PI</code>, and <code>M_E</code>. These are all capital letters
|
|
and are read-only (although some of them change value from one record to
|
|
another).
|
|
|
|
<p/> <b>Fields of stream records</b>, accessed using the <code>$</code> prefix.
|
|
These refer to fields of the current data-stream record. For example, in
|
|
<code>echo x=1,y=2 | mlr put '$z = $x + $y'</code>, <code>$x</code> and <code>$y</code>
|
|
refer to input fields, and <code>$z</code> refers to a new, computed output field.
|
|
In a few contexts, presented below, you can refer to the entire record as
|
|
<code>$*</code>.
|
|
|
|
<p/> <b>Out-of-stream variables</b> accessed using the <code>@</code> prefix. These
|
|
refer to data which persist from one record to the next, including in
|
|
<code>begin</code> and <code>end</code> blocks (which execute before/after the record
|
|
stream is consumed, respectively). You use them to remember values across
|
|
records, such as sums, differences, counters, and so on. In a few contexts,
|
|
presented below, you can refer to the entire out-of-stream-variables collection
|
|
as <code>@*</code>.
|
|
|
|
<p/> <b>Local variables</b> are limited in scope and extent to the current
|
|
statements being executed: these include function arguments, bound variables in
|
|
for loops, and explicitly declared local variables.
|
|
|
|
<p/> <b>Keywords</b> are not variables, but since their names are reserved, you
|
|
cannot use these names for local variables.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Built-in variables</h2>
|
|
|
|
<p/> These are written all in capital letters, such as <code>NR</code>,
|
|
<code>NF</code>, <code>FILENAME</code>, and only a small, specific set of them is
|
|
defined by Miller.
|
|
|
|
<p/>Namely, Miller supports the following five built-in variables for <a
|
|
href="reference-verbs.html#filter"><code>filter</code></a> and <code>put</code>, all <code>awk</code>-inspired:
|
|
<code>NF</code>, <code>NR</code>, <code>FNR</code>, <code>FILENUM</code>, and
|
|
<code>FILENAME</code>, as well as the mathematical constants <code>M_PI</code> and
|
|
<code>M_E</code>. Lastly, the <code>ENV</code> hashmap allows read access to environment
|
|
variables, e.g. <code>ENV["HOME"]</code> or <code>ENV["foo_".$hostname]</code>.
|
|
|
|
POKI_RUN_COMMAND{{mlr filter 'FNR == 2' data/small*}}HERE
|
|
|
|
POKI_RUN_COMMAND{{mlr put '$fnr = FNR' data/small*}}HERE
|
|
|
|
<p/> Their values of <code>NF</code>, <code>NR</code>, <code>FNR</code>, <code>FILENUM</code>,
|
|
and <code>FILENAME</code> change from one record to the next as Miller scans
|
|
through your input data stream. The mathematical constants, of course, do not
|
|
change; <code>ENV</code> is populated from the system environment variables at the
|
|
time Miller starts and is read-only for the remainder of program execution.
|
|
|
|
<p/> Their <b>scope is global</b>: you can refer to them in any <code>filter</code>
|
|
or <code>put</code> statement. Their values are assigned by the input-record
|
|
reader:
|
|
|
|
POKI_RUN_COMMAND{{mlr --csv put '$nr = NR' data/a.csv}}HERE
|
|
POKI_RUN_COMMAND{{mlr --csv repeat -n 3 then put '$nr = NR' data/a.csv}}HERE
|
|
|
|
<p/> The <b>extent</b> is for the duration of the put/filter: in a
|
|
<code>begin</code> statement (which executes before the first input record is
|
|
consumed) you will find <code>NR=1</code> and in an <code>end</code> statement (which
|
|
is executed after the last input record is consumed) you will find <code>NR</code>
|
|
to be the total number of records ingested.
|
|
|
|
<p/> These are all <b>read-only</b> for the <code>mlr put</code> and <code>mlr
|
|
filter</code> DSLs: they may be assigned from, e.g. <code>$nr=NR</code>, but they may
|
|
not be assigned to: <code>NR=100</code> is a syntax error.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Field names</h2>
|
|
|
|
<p/>Names of fields within stream records must be specified using a <code>$</code>
|
|
in <code>filter</code> and <a href="reference-verbs.html#put"><code>put</code></a>
|
|
expressions, even though the dollar signs don’t appear in the data stream
|
|
itself. For integer-indexed data, this looks like <code>awk</code>’s
|
|
<code>$1,$2,$3</code>, except that Miller allows non-numeric names such as
|
|
<code>$quantity</code> or <code>$hostname</code>. Likewise, enclose string literals
|
|
in double quotes in <code>filter</code> expressions even though they don’t
|
|
appear in file data. In particular, <code>mlr filter '$x=="abc"'</code> passes
|
|
through the record <code>x=abc</code>.
|
|
|
|
<p/>If field names have <b>special characters</b> such as <code>.</code> then you
|
|
can use braces, e.g. <code>'${field.name}'</code>.
|
|
|
|
<p/>You may also use a <b>computed field name</b> in square brackets, e.g.
|
|
|
|
POKI_RUN_COMMAND{{echo a=3,b=4 | mlr filter '$["x"] < 0.5'}}HERE
|
|
|
|
POKI_RUN_COMMAND{{echo s=green,t=blue,a=3,b=4 | mlr put '$[$s."_".$t] = $a * $b'}}HERE
|
|
|
|
Notes:
|
|
|
|
<p/> The names of record fields depend on the contents of your input data stream, and their
|
|
values change from one record to the next as Miller scans through your input
|
|
data stream.
|
|
|
|
<p/> Their <b>extent</b> is limited to the current record; their <b>scope</b>
|
|
is the <code>filter</code> or <code>put</code> command in which they appear.
|
|
|
|
<p/> These are <b>read-write</b>: you can do <code>$y=2*$x</code>,
|
|
<code>$x=$x+1</code>, etc.
|
|
|
|
<p/> Records are Miller’s output: field names present in the input
|
|
stream are passed through to output (written to standard output) unless fields
|
|
are removed with <code>cut</code>, or records are excluded with <code>filter</code> or
|
|
<code>put -q</code>, etc. Simply assign a value to a field and it will be output.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Positional field names</h2>
|
|
|
|
<p/> Even though Miller’s main selling point is
|
|
name-indexing, sometimes you really want to refer to a field name by its
|
|
positional index (starting from 1).
|
|
|
|
<p/> Use <code>$[[3]]</code> to access the name of field 3. More generally, any
|
|
expression evaluating to an integer can go between <code>$[[</code> and
|
|
<code>]]</code>.
|
|
|
|
Then using a computed field name, <code>$[ $[[3]] ]</code> is the value in the third field.
|
|
This has the shorter equivalent notation <code>$[[[3]]]</code>.
|
|
|
|
POKI_RUN_COMMAND{{mlr cat data/small}}HERE
|
|
|
|
POKI_RUN_COMMAND{{mlr put '$[[3]] = "NEW"' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$[[[3]]] = "NEW"' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$NEW = $[[NR]]' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$NEW = $[[[NR]]]' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$[[[NR]]] = "NEW"' data/small}}HERE
|
|
|
|
Right-hand side accesses to non-existent fields — i.e. with index less
|
|
than 1 or greater than <code>NF</code> -- return an absent value. Likewise,
|
|
left-hand side accesses only refer to fields which 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.
|
|
|
|
POKI_RUN_COMMAND{{mlr put '$[[6]] = "NEW"' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$[[[6]]] = "NEW"' data/small}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Out-of-stream variables</h2>
|
|
|
|
<p/> These are prefixed with an at-sign, e.g. <code>@sum</code>. Furthermore,
|
|
unlike built-in variables and stream-record fields, they are maintained in an
|
|
arbitrarily nested hashmap: you can do <code>@sum += $quanity</code>, or
|
|
<code>@sum[$color] += $quanity</code>, or <code>@sum[$color][$shape] +=
|
|
$quanity</code>. The keys for the multi-level hashmap can be any expression which
|
|
evaluates to string or integer: e.g. <code>@sum[NR] = $a + $b</code>,
|
|
<code>@sum[$a."-".$b] = $x</code>, etc.
|
|
|
|
<p/> Their names and their values are entirely under your control; they change
|
|
only when you assign to them.
|
|
|
|
<p/> Just as for field names in stream records, if you want to define out-of-stream variables
|
|
with <b>special characters</b> such as <code>.</code> then you can use braces, e.g. <code>'@{variable.name}["index"]'</code>.
|
|
|
|
<p/>You may use a <b>computed key </b> in square brackets, e.g.
|
|
|
|
POKI_RUN_COMMAND{{echo s=green,t=blue,a=3,b=4 | mlr put -q '@[$s."_".$t] = $a * $b; emit all'}}HERE
|
|
|
|
<p/> Out-of-stream variables are <b>scoped</b> to the <code>put</code> command in
|
|
which they appear. In particular, if you have two or more <code>put</code>
|
|
commands separated by <code>then</code>, each put will have its own set of
|
|
out-of-stream variables:
|
|
|
|
POKI_RUN_COMMAND{{cat data/a.dkvp}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '@sum += $a; end {emit @sum}' then put 'is_present($a) {$a=10*$a; @sum += $a}; end {emit @sum}' data/a.dkvp}}HERE
|
|
|
|
<p/> Out-of-stream variables’ <b>extent</b> is from the start to the end of the record stream,
|
|
i.e. every time the <code>put</code> or <code>filter</code> statement referring to them is executed.
|
|
|
|
<p/> Out-of-stream variables are <b>read-write</b>: you can do <code>$sum=@sum</code>, <code>@sum=$sum</code>,
|
|
etc.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Indexed out-of-stream variables</h2>
|
|
|
|
<p/>Using an index on the <code>@count</code> and <code>@sum</code> variables, we get the benefit of the
|
|
<code>-g</code> (group-by) option which <code>mlr stats1</code> and various other Miller commands have:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-6.sh)HERE
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-7.sh)HERE
|
|
|
|
<p/>Indices can be arbitrarily deep — here there are two or more of them:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-6a.sh)HERE
|
|
|
|
The idea is that <code>stats1</code>, and other Miller verbs, encapsulate
|
|
frequently-used patterns with a minimum of keystroking (and run a little
|
|
faster), whereas using out-of-stream variables you have more flexibility and
|
|
control in what you do.
|
|
|
|
<p/>Begin/end blocks can be mixed with pattern/action blocks. For example:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-8.sh)HERE
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Local variables</h2>
|
|
|
|
<p/>Local variables are similar to out-of-stream variables, except that
|
|
their extent is limited to the expressions in which they appear (and their
|
|
basenames can’t be computed using square brackets).
|
|
There are three kinds of local variables: <b>arguments</b> to
|
|
functions/subroutines, <b>variables bound within for-loops</b>, and
|
|
<b>locals</b> defined within control blocks. They may be untyped using
|
|
<code>var</code>, or typed using <code>num</code>, <code>int</code>, <code>float</code>,
|
|
<code>str</code>, <code>bool</code>, and <code>map</code>.
|
|
|
|
<p/>For example:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/local-example-1.sh)HERE
|
|
|
|
<p/>Things which are completely unsurprising, resembling many other languages:
|
|
|
|
<ul>
|
|
|
|
<li/> Parameter names are bound to their arguments but can be reassigned, e.g.
|
|
if there is a parameter named <code>a</code> then you can reassign the value of
|
|
<code>a</code> to be something else within the function if you like.
|
|
|
|
<li/> However, you cannot redeclare the <i>type</i> of an argument or a local:
|
|
<code>var a=1; var a=2</code> is an error but
|
|
<code>var a=1; a=2</code> is OK.
|
|
|
|
<li/> All argument-passing is positional rather than by name; arguments are
|
|
passed by value, not by reference. (This is also true for map-valued variables:
|
|
they are not, and cannot be, passed by reference)
|
|
|
|
<li/> You can define locals (using <code>var</code>, <code>num</code>, etc.) at any
|
|
scope (if-statements, else-statements, while-loops, for-loops, or the top-level
|
|
scope), and nested scopes will have access (more details on scope in the next
|
|
section). If you define a local variable with the same name inside an inner
|
|
scope, then a new variable is created with the narrower scope.
|
|
|
|
<li/> If you assign to a local variable for the first time in a scope without
|
|
declaring it as <code>var</code>, <code>num</code>, etc. then: if it exists in an outer
|
|
scope, that outer-scope variable will be updated; if not, it will be defined in
|
|
the current scope as if <code>var</code> had been used. (See also <a
|
|
href="#Type-checking">here</a> for an example.) I recommend always declaring
|
|
variables explicitly to make the intended scoping clear.
|
|
|
|
<li/> Functions and subroutines never have access to locals from their callee
|
|
(unless passed by value as arguments).
|
|
|
|
</ul>
|
|
|
|
<p/>Things which are perhaps surprising compared to other languages:
|
|
|
|
<ul>
|
|
|
|
<li/> Type declarations using <code>var</code>, or typed using <code>num</code>,
|
|
<code>int</code>, <code>float</code>, <code>str</code>, and <code>bool</code> are necessary to
|
|
declare local variables. Function arguments and variables bound in for-loops
|
|
over stream records and out-of-stream variables are <i>implicitly</i> declared
|
|
using <code>var</code>. (Some examples are shown below.)
|
|
|
|
<li/> Type-checking is done at assignment time. For example, <code>float f =
|
|
0</code> is an error (since <code>0</code> is an integer), as is <code>float f = 0.0; f
|
|
= 1</code>. For this reason I prefer to use <code>num</code> over <code>float</code> in
|
|
most contexts since <code>num</code> encompasses integer and floating-point values.
|
|
More information about type-checking is <a href="#Type-checking">here</a>.
|
|
|
|
<li/> Bound variables in for-loops over stream records and out-of-stream
|
|
variables are implicitly local to that block. E.g. in
|
|
<code>for (k, v in $*) { ... }</code>
|
|
<code>for ((k1, k2), v in @*) { ... }</code>
|
|
if there are <code>k</code>, <code>v</code>, etc. in the enclosing scope then those
|
|
will be masked by the loop-local bound variables in the loop, and moreover
|
|
the values of the loop-local bound variables are not available after the
|
|
end of the loop.
|
|
|
|
<li/> For C-style triple-for loops, if a for-loop variable is defined using
|
|
<code>var</code>, <code>int</code>, etc. then it is scoped to that for-loop. E.g.
|
|
<code>for (i = 0; i < 10; i += 1) { ... }</code> and <code>for (int i = 0; i < 10; i
|
|
+= 1) { ... }</code>. (This is unsurprising.). If there is no typedecl and an
|
|
outer-scope variable of that name exists, then it is used. (This is also
|
|
unsurprising.) But of there is no outer-scope variable of that name then the
|
|
variable is scoped to the for-loop only.
|
|
|
|
</ul>
|
|
|
|
<p/> The following example demonstrates the scope rules:
|
|
|
|
POKI_RUN_COMMAND{{cat data/scope-example.mlr}}HERE
|
|
POKI_RUN_COMMAND{{cat data/scope-example.dat}}HERE
|
|
POKI_RUN_COMMAND{{mlr --oxtab --from data/scope-example.dat put -f data/scope-example.mlr}}HERE
|
|
|
|
<p/> And this example demonstrates the type-declaration rules:
|
|
|
|
POKI_RUN_COMMAND{{cat data/type-decl-example.mlr}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Map literals</h2>
|
|
|
|
<p/>Miller’s <code>put</code>/<code>filter</code> DSL has four kinds of hashmaps.
|
|
<b>Stream records</b> are (single-level) maps from name to value.
|
|
<b>Out-of-stream variables</b> and <b>local variables</b> can also be maps,
|
|
although they can be multi-level hashmaps (e.g. <code>@sum[$x][$y]</code>). The
|
|
fourth kind is <b>map literals</b>. These cannot be on the left-hand side of
|
|
assignment expressions. Syntactically they look like JSON, although Miller
|
|
allows string and integer keys in its map literals while JSON allows only
|
|
string keys (e.g. <code>"3"</code> rather than <code>3</code>).
|
|
|
|
<p/> For example, the following swaps the input stream’s <code>a</code> and
|
|
<code>i</code> fields, modifies <code>y</code>, and drops the rest:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/map-literal-example-1.sh)HERE
|
|
|
|
<p/>Likewise, you can assign map literals to out-of-stream variables or local variables;
|
|
pass them as arguments to user-defined functions, return them from functions, and so on:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/map-literal-example-2.sh)HERE
|
|
|
|
<p/> Like out-of-stream and local variables, map literals can be multi-level:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/map-literal-example-3.sh)HERE
|
|
|
|
<p/>By default, map-valued expressions are dumped using JSON formatting. If you
|
|
use <code>dump</code> to print a hashmap with integer keys and you don’t want
|
|
them double-quoted (JSON-style) then you can use <code>mlr put
|
|
--jknquoteint</code>. See also <code>mlr put --help</code>.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Type-checking</h2>
|
|
|
|
<p/> Miller’s <code>put</code>/<code>filter</code> DSLs support two optional
|
|
kinds of type-checking. One is inline <b>type-tests</b> and
|
|
<b>type-assertions</b> within expressions. The other is <b>type
|
|
declarations</b> for assignments to local variables, binding of arguments to
|
|
user-defined functions, and return values from user-defined functions, These
|
|
are discussed in the following subsections.
|
|
|
|
<p/> Use of type-checking is entirely up to you: omit it if you want
|
|
flexibility with heterogeneous data; use it if you want to help catch
|
|
misspellings in your DSL code or unexpected irregularities in your input data.
|
|
|
|
<!-- ================================================================ -->
|
|
<h3>Type-test and type-assertion expressions</h3>
|
|
|
|
<p/> The following <code>is...</code> functions take a value and return a boolean
|
|
indicating whether the argument is of the indicated type. The
|
|
<code>assert_...</code> functions return their argument if it is of the specified
|
|
type, and cause a fatal error otherwise:
|
|
|
|
<table>
|
|
<tr>
|
|
<td>
|
|
POKI_RUN_COMMAND{{mlr -F | grep ^is}}HERE
|
|
</td>
|
|
<td>
|
|
POKI_RUN_COMMAND{{mlr -F | grep ^assert}}HERE
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p/> Please see the POKI_PUT_LINK_FOR_PAGE(cookbook.html#Data-cleaning_examples)HERE for examples
|
|
of how to use these.
|
|
|
|
<!-- ================================================================ -->
|
|
<h3>Type-declarations for local variables, function parameter, and function return values</h3>
|
|
|
|
<p/> Local variables can be defined either untyped as in <code>x = 1</code>, or
|
|
typed as in <code>int x = 1</code>. Types include <b>var</b> (explicitly untyped),
|
|
<b>int</b>, <b>float</b>, <b>num</b> (int or float), <b>str</b>, <b>bool</b>,
|
|
and <b>map</b>. These optional type declarations are enforced at the time
|
|
values are assigned to variables: whether at the initial value assignment as in
|
|
<code>int x = 1</code> or in any subsequent assignments to the same variable
|
|
farther down in the scope.
|
|
|
|
<p/> The reason for <code>num</code> is that <code>int</code> and <code>float</code> typedecls are very precise:
|
|
|
|
<div class="pokipanel">
|
|
<pre>
|
|
float a = 0; # Runtime error since 0 is int not float
|
|
int b = 1.0; # Runtime error since 1.0 is float not int
|
|
num c = 0; # OK
|
|
num d = 1.0; # OK
|
|
</pre>
|
|
</div>
|
|
|
|
<p/> A suggestion is to use <code>num</code> for general use when you want numeric
|
|
content, and use <code>int</code> when you genuinely want integer-only values, e.g.
|
|
in loop indices or map keys (since Miller map keys can only be strings or
|
|
ints).
|
|
|
|
<p/> The <code>var</code> type declaration indicates no type restrictions, e.g.
|
|
<code>var x = 1</code> has the same type restrictions on <code>x</code> as <code>x =
|
|
1</code>. The difference is in intentional shadowing: if you have <code>x = 1</code>
|
|
in outer scope and <code>x = 2</code> in inner scope (e.g. within a for-loop or an
|
|
if-statement) then outer-scope <code>x</code> has value 2 after the second
|
|
assignment. But if you have <code>var x = 2</code> in the inner scope, then you
|
|
are declaring a variable scoped to the inner block.) For example:
|
|
|
|
<div class="pokipanel">
|
|
<pre>
|
|
x = 1;
|
|
if (NR == 4) {
|
|
x = 2; # Refers to outer-scope x: value changes from 1 to 2.
|
|
}
|
|
print x; # Value of x is now two
|
|
</pre>
|
|
</div>
|
|
<div class="pokipanel">
|
|
<pre>
|
|
x = 1;
|
|
if (NR == 4) {
|
|
var x = 2; # Defines a new inner-scope x with value 2
|
|
}
|
|
print x; # Value of this x is still 1
|
|
</pre>
|
|
</div>
|
|
|
|
<p/> Likewise function arguments can optionally be typed, with type enforced
|
|
when the function is called:
|
|
|
|
<div class="pokipanel">
|
|
<pre>
|
|
func f(map m, int i) {
|
|
...
|
|
}
|
|
$a = f({1:2, 3:4}, 5); # OK
|
|
$b = f({1:2, 3:4}, "abc"); # Runtime error
|
|
$c = f({1:2, 3:4}, $x); # Runtime error for records with non-integer field named x
|
|
if (NR == 4) {
|
|
var x = 2; # Defines a new inner-scope x with value 2
|
|
}
|
|
print x; # Value of this x is still 1
|
|
</pre>
|
|
</div>
|
|
|
|
<p/> Thirdly, function return values can be type-checked at the point of
|
|
<code>return</code> using <code>:</code> and a typedecl after the parameter list:
|
|
|
|
<div class="pokipanel">
|
|
<pre>
|
|
func f(map m, int i): bool {
|
|
...
|
|
...
|
|
if (...) {
|
|
return "false"; # Runtime error if this branch is taken
|
|
}
|
|
...
|
|
...
|
|
if (...) {
|
|
return retval; # Runtime error if this function doesn't have an in-scope
|
|
# boolean-valued variable named retval
|
|
}
|
|
...
|
|
...
|
|
# In Miller if your functions don't explicitly return a value, they return absent-null.
|
|
# So it would also be a runtime error on reaching the end of this function without
|
|
# an explicit return statement.
|
|
}
|
|
</pre>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Null data: empty and absent</h2>
|
|
|
|
<p/> Please see
|
|
<a href="reference.html#Null_data:_empty_and_absent">here</a>.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Aggregate variable assignments</h2>
|
|
|
|
<p/>There are three remaining kinds of variable assignment using out-of-stream
|
|
variables, the last two of which use the <code>$*</code> syntax:
|
|
<ul>
|
|
<li/> Recursive copy of out-of-stream variables
|
|
<li/> Out-of-stream variable assigned to full stream record
|
|
<li/> Full stream record assigned to an out-of-stream variable
|
|
</ul>
|
|
|
|
<p/> Example recursive copy of out-of-stream variables:
|
|
|
|
POKI_RUN_COMMAND{{mlr --opprint put -q '@v["sum"] += $x; @v["count"] += 1; end{dump; @w = @v; dump}' data/small}}HERE
|
|
|
|
<p/>Example of out-of-stream variable assigned to full stream record, where the 2nd record is stashed, and the 4th record is overwritten with that:
|
|
|
|
POKI_RUN_COMMAND{{mlr put 'NR == 2 {@keep = $*}; NR == 4 {$* = @keep}' data/small}}HERE
|
|
|
|
<p/>Example of full stream record assigned to an out-of-stream variable, finding
|
|
the record for which the <code>x</code> field has the largest value in the input
|
|
stream:
|
|
|
|
POKI_RUN_COMMAND{{cat data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr --opprint put -q 'is_null(@xmax) || $x > @xmax {@xmax=$x; @recmax=$*}; end {emit @recmax}' data/small}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Keywords for filter and put</h2>
|
|
|
|
POKI_RUN_COMMAND{{mlr --help-all-keywords}}HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Operator precedence</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_operator_precedence');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_operator_precedence" style="display: block">
|
|
|
|
<p/>Operators are listed in order of decreasing precedence, highest first.
|
|
|
|
<p/>
|
|
<div class="pokipanel">
|
|
<pre>
|
|
Operators Associativity
|
|
--------- -------------
|
|
() left to right
|
|
** right to left
|
|
! ~ unary+ unary- & right to left
|
|
binary* / // % left to right
|
|
binary+ binary- . left to right
|
|
<< >> left to right
|
|
& left to right
|
|
^ left to right
|
|
| left to right
|
|
< <= > >= left to right
|
|
== != =~ !=~ left to right
|
|
&& left to right
|
|
^^ left to right
|
|
|| left to right
|
|
? : right to left
|
|
= N/A for Miller (there is no $a=$b=$c)
|
|
</pre>
|
|
</div>
|
|
<p/>
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Operator and function semantics</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_operator_and_function_semantics');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_operator_and_function_semantics" style="display: block">
|
|
|
|
<ul>
|
|
|
|
<li/> Functions are in general pass-throughs straight to the system-standard C
|
|
library.
|
|
|
|
<li/> The <code>min</code> and <code>max</code> functions are different from other
|
|
multi-argument functions which return null if any of their inputs are null: for
|
|
<code>min</code> and <code>max</code>, by contrast, if one argument is absent-null, the other
|
|
is returned. Empty-null loses min or max against numeric or boolean; empty-null
|
|
is less than any other string.
|
|
|
|
<li/> Symmetrically with respect to the bitwise OR, XOR, and AND operators
|
|
<code>|</code>, <code>^</code>, <code>&</code>, Miller has logical operators
|
|
<code>||</code>, <code>^^</code>, <code>&&</code>: the logical XOR not existing in
|
|
C.
|
|
|
|
<li/> The exponentiation operator <code>**</code> is familiar from many languages.
|
|
|
|
<li/> The regex-match and regex-not-match operators <code>=~</code> and
|
|
<code>!=~</code> are similar to those in Ruby and Perl.
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Control structures</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_control_structures');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_control_structures" style="display: block">
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Pattern-action blocks</h2>
|
|
|
|
<p/>These are reminiscent of <code>awk</code> syntax. They can be used to allow
|
|
assignments to be done only when appropriate — e.g. for math-function
|
|
domain restrictions, regex-matching, and so on:
|
|
|
|
POKI_RUN_COMMAND{{mlr cat data/put-gating-example-1.dkvp}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$x > 0.0 { $y = log10($x); $z = sqrt($y) }' data/put-gating-example-1.dkvp}}HERE
|
|
POKI_RUN_COMMAND{{mlr cat data/put-gating-example-2.dkvp}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$a =~ "([a-z]+)_([0-9]+)" { $b = "left_\1"; $c = "right_\2" }' data/put-gating-example-2.dkvp}}HERE
|
|
|
|
<p/>This produces heteregenous output which Miller, of course, has no problems
|
|
with (see POKI_PUT_LINK_FOR_PAGE(record-heterogeneity.html)HERE). But if you
|
|
want homogeneous output, the curly braces can be replaced with a semicolon
|
|
between the expression and the body statements. This causes <code>put</code> to
|
|
evaluate the boolean expression (along with any side effects, namely,
|
|
regex-captures <code>\1</code>, <code>\2</code>, etc.) but doesn’t use it as a
|
|
criterion for whether subsequent assignments should be executed. Instead,
|
|
subsequent assignments are done unconditionally:
|
|
|
|
POKI_RUN_COMMAND{{mlr put '$x > 0.0; $y = log10($x); $z = sqrt($y)' data/put-gating-example-1.dkvp}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$a =~ "([a-z]+)_([0-9]+)"; $b = "left_\1"; $c = "right_\2"' data/put-gating-example-2.dkvp}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>If-statements</h2>
|
|
|
|
<p/>These are again reminiscent of <code>awk</code>. Pattern-action blocks are a special case of <code>if</code> with no
|
|
<code>elif</code> or <code>else</code> blocks, no <code>if</code> keyword, and parentheses optional around the boolean expression:
|
|
|
|
POKI_CARDIFY{{mlr put 'NR == 4 {$foo = "bar"}'}}HERE
|
|
POKI_CARDIFY{{mlr put 'if (NR == 4) {$foo = "bar"}'}}HERE
|
|
|
|
<p/>Compound statements use <code>elif</code> (rather than <code>elsif</code> or <code>else if</code>):
|
|
|
|
POKI_INCLUDE_ESCAPED(data/if-chain.sh)HERE
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>While and do-while loops</h2>
|
|
|
|
<p/>Miller’s <code>while</code> and <code>do-while</code> are unsurprising in
|
|
comparison to various languages, as are <code>break</code> and <code>continue</code>:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/while-example-1.sh)HERE
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/while-example-2.sh)HERE
|
|
|
|
<p/> A <code>break</code> or <code>continue</code> within nested conditional blocks or
|
|
if-statements will, of course, propagate to the innermost loop enclosing them,
|
|
if any. A <code>break</code> or <code>continue</code> outside a loop is a syntax error
|
|
that will be flagged as soon as the expression is parsed, before any input
|
|
records are ingested.
|
|
|
|
<p/> The existence of <code>while</code>, <code>do-while</code>, and <code>for</code> loops
|
|
in Miller’s DSL means that you can create infinite-loop scenarios
|
|
inadvertently. In particular, please recall that DSL statements are executed
|
|
once if in <code>begin</code> or <code>end</code> blocks, and once <i>per record</i>
|
|
otherwise. For example, <b><code>while (NR < 10)</code> will never terminate as
|
|
<code>NR</code> is only incremented between records</b>.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>For-loops</h2>
|
|
|
|
<p/>While Miller’s <code>while</code> and <code>do-while</code> statements are
|
|
much as in many other languages, <code>for</code> loops are more idiosyncratic to
|
|
Miller. They are loops over key-value pairs, whether in stream records,
|
|
out-of-stream variables, local variables, or map-literals: more reminiscent of
|
|
<code>foreach</code>, as in (for example) PHP. There are <b>for-loops over map
|
|
keys</b> and <b>for-loops over key-value tuples</b>. Additionally, Miller has a
|
|
<b>C-style triple-for loop</b> with initialize, test, and update statements.
|
|
|
|
<p/>As with <code>while</code> and <code>do-while</code>, a <code>break</code> or
|
|
<code>continue</code> within nested control structures will propagate to the
|
|
innermost loop enclosing them, if any, and a <code>break</code> or
|
|
<code>continue</code> outside a loop is a syntax error that will be flagged as soon
|
|
as the expression is parsed, before any input records are ingested.
|
|
|
|
<h3>Key-only for-loops </h3>
|
|
|
|
<p/>The <code>key</code> variable is always bound to the <i>key</i> of key-value pairs:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/single-for-example-1.sh)HERE
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/single-for-example-2.sh)HERE
|
|
|
|
<p/>Note that the value corresponding to a given key may be gotten as through a
|
|
<b>computed field name</b> using square brackets as in <code>$[key]</code> for
|
|
stream records, or by indexing the looped-over variable using square brackets.
|
|
|
|
<h3>Key-value for-loops </h3>
|
|
|
|
<p/>Single-level keys may be gotten at using either <code>for(k,v)</code> or
|
|
<code>for((k),v)</code>; multi-level keys may be gotten at using
|
|
<code>for((k1,k2,k3),v)</code> and so on. The <code>v</code> variable will be bound to
|
|
to a scalar value (a string or a number) if the map stops at that level, or to
|
|
a map-valued variable if the map goes deeper. If the map isn’t deep
|
|
enough then the loop body won’t be executed.
|
|
|
|
POKI_RUN_COMMAND{{cat data/for-srec-example.tbl}}HERE
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/for-srec-example-1.sh)HERE
|
|
POKI_RUN_COMMAND{{mlr --from data/small --opprint put 'for (k,v in $*) { $[k."_type"] = typeof(v) }'}}HERE
|
|
|
|
<p/>Note that the value of the current field in the for-loop can be gotten either using the bound
|
|
variable <code>value</code>, or through a <b>computed field name</b> using square brackets as in <code>$[key]</code>.
|
|
|
|
<p/>Important note: to avoid inconsistent looping behavior in case you’re
|
|
setting new fields (and/or unsetting existing ones) while looping over the
|
|
record, <b>Miller makes a copy of the record before the loop: loop variables
|
|
are bound from the copy and all other reads/writes involve the record
|
|
itself</b>:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/for-srec-example-2.sh)HERE
|
|
|
|
It can be confusing to modify the stream record while iterating over a copy of it, so
|
|
instead you might find it simpler to use a local variable in the loop and only update
|
|
the stream record after the loop:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/for-srec-example-3.sh)HERE
|
|
|
|
<p/>You can also start iterating on sub-hashmaps of an out-of-stream or local
|
|
variable; you can loop over nested keys; you can loop over all out-of-stream
|
|
variables. The bound variables are bound to a copy of the sub-hashmap as it
|
|
was before the loop started. The sub-hashmap is specified by square-bracketed
|
|
indices after <code>in</code>, and additional deeper indices are bound to loop
|
|
key-variables. The terminal values are bound to the loop value-variable
|
|
whenever the keys are not too shallow. The value-variable may refer to a
|
|
terminal (string, number) or it may be map-valued if the map goes deeper.
|
|
Example indexing is as follows:
|
|
|
|
POKI_INCLUDE_ESCAPED(data/for-oosvar-example-0a.txt)HERE
|
|
|
|
<p/>That’s confusing in the abstract, so a concrete example is in order.
|
|
Suppose the out-of-stream variable <code>@myvar</code> is populated as follows:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/for-oosvar-example-0b.sh)HERE
|
|
|
|
<p/> Then we can get at various values as follows:
|
|
|
|
<table><tr><td>
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/for-oosvar-example-0c.sh)HERE
|
|
</td><td>
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/for-oosvar-example-0d.sh)HERE
|
|
</td><td>
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/for-oosvar-example-0e.sh)HERE
|
|
</td></tr></table>
|
|
|
|
<h3>C-style triple-for loops</h3>
|
|
|
|
<p/> These are supported as follows:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/triple-for-example-1.sh)HERE
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/triple-for-example-2.sh)HERE
|
|
|
|
Notes:
|
|
<ul>
|
|
|
|
<li/> In <code>for (start; continuation; update) { body }</code>, the start,
|
|
continuation, and update statements may be empty, single statements, or
|
|
multiple comma-separated statements. If the continuation is empty (e.g. <code>for(i=1;;i+=1)</code>) it defaults
|
|
to true.
|
|
|
|
<li/> In particular, you may use <code>$</code>-variables and/or
|
|
<code>@</code>-variables in the start, continuation, and/or update steps (as well
|
|
as the body, of course).
|
|
|
|
<li/> The typedecls such as <code>int</code> or <code>num</code> are optional. If a
|
|
typedecl is provided (for a local variable), it binds a variable scoped to the
|
|
for-loop regardless of whether a same-name variable is present in outer scope.
|
|
If a typedecl is not provided, then the variable is scoped to the for-loop if
|
|
no same-name variable is present in outer scope, or if a same-name variable is
|
|
present in outer scope then it is modified.
|
|
|
|
<li/> Miller has no <code>++</code> or <code>--</code> operators.
|
|
|
|
<li/> As with all for/if/while statements in Miller, the curly braces are
|
|
required even if the body is a single statement, or empty.
|
|
|
|
</ul>
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Begin/end blocks</h2>
|
|
|
|
<p/>Miller supports an <code>awk</code>-like <code>begin/end</code> syntax. The
|
|
statements in the <code>begin</code> block are executed before any input records
|
|
are read; the statements in the <code>end</code> 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 <code>begin</code>, you might
|
|
use a pattern/action block of the form <code>FNR == 1 { ... }</code>.) All
|
|
statements outside of <code>begin</code> or <code>end</code> 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:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-1.sh)HERE
|
|
|
|
<p/>Since uninitialized out-of-stream variables default to 0 for
|
|
addition/substraction and 1 for multiplication when they appear on expression
|
|
right-hand sides (as in <code>awk</code>), the above can be written more succinctly
|
|
as
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-2.sh)HERE
|
|
|
|
<p/>The <b>put -q</b> option is a shorthand which suppresses printing of each
|
|
output record, with only <code>emit</code> statements being output. So to get only
|
|
summary outputs, one could write
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-3.sh)HERE
|
|
|
|
<p/>We can do similarly with multiple out-of-stream variables:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-4.sh)HERE
|
|
|
|
This is of course not much different than
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/begin-end-example-5.sh)HERE
|
|
|
|
<p/>Note that it’s a syntax error for begin/end blocks to refer to field
|
|
names (beginning with <code>$</code>), since these execute outside the context of
|
|
input records.
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Output statements</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_output_statements');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_output_statements" style="display: block">
|
|
|
|
<p/>You can <b>output</b> variable-values or expressions in <b>five ways</b>:
|
|
|
|
<ul>
|
|
|
|
<li/> <b>Assign</b> them to stream-record fields. For example,
|
|
<code>$cumulative_sum = @sum</code>. For another example, <code>$nr = NR</code> adds a
|
|
field named <code>nr</code> to each output record, containing the value of the
|
|
built-in variable <code>NR</code> as of when that record was ingested.
|
|
|
|
<li/> Use the <b>print</b> or <b>eprint</b> keywords which immediately print an
|
|
expression <i>directly to standard output or standard error</i>, respectively.
|
|
Note that <code>dump</code>, <code>edump</code>, <code>print</code>, and <code>eprint</code>
|
|
don’t output records which participate in <code>then</code>-chaining; rather,
|
|
they’re just immediate prints to stdout/stderr. The <code>printn</code> and
|
|
<code>eprintn</code> keywords are the same except that they don’t print final
|
|
newlines. Additionally, you can print to a specified file instead of
|
|
stdout/stderr.
|
|
|
|
<li/> Use the <b>dump</b> or <b>edump</b> keywords, which <i>immediately print
|
|
all out-of-stream variables as a JSON data structure to the standard output or
|
|
standard error</i> (respectively).
|
|
|
|
<li/> Use <b>tee</b> which formats the current stream record (not just an
|
|
arbitrary string as with <b>print</b>) to a specific file.
|
|
|
|
<li/> Use <b>emit</b>/<b>emitp</b>/<b>emitf</b> to send out-of-stream
|
|
variables’ current values to the output record stream, e.g. <code>@sum +=
|
|
$x; emit @sum</code> which produces an extra output record such as
|
|
<code>sum=3.1648382</code>.
|
|
|
|
</ul>
|
|
|
|
<p/>For the first two options you are populating the output-records stream
|
|
which feeds into the next verb in a <code>then</code>-chain (if any), or which otherwise
|
|
is formatted for output using <code>--o...</code> flags.
|
|
|
|
<p/>For the last three options you are sending output directly to standard
|
|
output, standard error, or a file.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Print statements</h2>
|
|
|
|
<p/>The <code>print</code> statement is perhaps self-explanatory, but with a few
|
|
light caveats:
|
|
|
|
<ul>
|
|
|
|
<li/> There are four variants: <code>print</code> goes to stdout with final
|
|
newline, <code>printn</code> goes to stdout without final newline (you can include
|
|
one using "\n" in your output string), <code>eprint</code> goes to stderr with
|
|
final newline, and <code>eprintn</code> goes to stderr without final newline.
|
|
|
|
<li/> Output goes directly to stdout/stderr, respectively: data produced this
|
|
way do not go downstream to the next verb in a <code>then</code>-chain. (Use
|
|
<code>emit</code> for that.)
|
|
|
|
<li/> Print statements are for strings (<code>print "hello"</code>), or things
|
|
which can be made into strings: numbers (<code>print 3</code>, <code>print $a +
|
|
$b</code>, or concatenations thereof (<code>print "a + b = " . ($a + $b)</code>).
|
|
Maps (in <code>$*</code>, map-valued out-of-stream or local variables, and map
|
|
literals) aren’t convertible into strings. If you print a map, you get
|
|
<code>{is-a-map}</code> as output. Please use <code>dump</code> to print maps.
|
|
|
|
<li/>You can redirect print output to a file:
|
|
<code>mlr --from myfile.dat put 'print > "tap.txt", $x'</code>
|
|
<code>mlr --from myfile.dat put 'o=$*; print > $a.".txt", $x'</code>.
|
|
|
|
<li/> See also the <a href="#Redirected-output_statements">section on redirected output</a> for examples.
|
|
|
|
</ul>
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Dump statements</h2>
|
|
|
|
<p/>The <code>dump</code> statement is for printing expressions, including maps,
|
|
directly to stdout/stderr, respectively:
|
|
|
|
<ul>
|
|
|
|
<li/> There are two variants: <code>dump</code> prints to stdout; <code>edump</code>
|
|
prints to stderr.
|
|
|
|
<li/> Output goes directly to stdout/stderr, respectively: data produced this
|
|
way do not go downstream to the next verb in a <code>then</code>-chain. (Use
|
|
<code>emit</code> for that.)
|
|
|
|
<li/> You can use <code>dump</code> to output single strings, numbers,
|
|
or expressions including map-valued data. Map-valued data are printed
|
|
as JSON. Miller allows string and integer keys in its map literals while
|
|
JSON allows only string keys, so use <code>mlr put --jknquoteint</code> if
|
|
you want integer-valued map keys not double-quoted.
|
|
|
|
<li/> If you use <code>dump</code> (or <code>edump</code>) with no arguments, you get a
|
|
JSON structure representing the current values of all out-of-stream variables.
|
|
|
|
<li/> As with <code>print</code>, you can redirect output to files.
|
|
|
|
<li/> See also the <a href="#Redirected-output_statements">section on redirected output</a> for examples.
|
|
|
|
</ul>
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Tee statements</h2>
|
|
|
|
<p/> Records produced by a <code>mlr put</code> go downstream to the next verb in
|
|
your <code>then</code>-chain, if any, or otherwise to standard output. If you want
|
|
to additionally copy out records to files, you can do that using <code>tee</code>.
|
|
|
|
<p/>The syntax is, by example, <code>mlr --from myfile.dat put 'tee >
|
|
"tap.dat", $*' then sort -n index</code>. First is <code>tee ></code>, then the
|
|
filename expression (which can be an expression such as
|
|
<code>"tap.".$a.".dat"</code>), then a comma, then <code>$*</code>. (Nothing else but
|
|
<code>$*</code> is teeable.)
|
|
|
|
<p/> See also the <a href="#Redirected-output_statements">section on redirected
|
|
output</a> for examples.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Redirected-output statements</h2>
|
|
|
|
The <b>print</b>, <b>dump</b> <b>tee</b>, <b>emitf</b>, <b>emit</b>, and
|
|
<b>emitp</b> keywords all allow you to redirect output to one or more files or
|
|
pipe-to commands. The filenames/commands are strings which can be constructed
|
|
using record-dependent values, so you can do things like splitting a table into
|
|
multiple files, one for each account ID, and so on.
|
|
|
|
<p/> Details:
|
|
|
|
<ul>
|
|
|
|
<li/> The <code>print</code> and <code>dump</code> keywords produce output immediately
|
|
to standard output, or to specified file(s) or pipe-to command if present.
|
|
|
|
POKI_RUN_COMMAND{{mlr --help-keyword print}}HERE
|
|
POKI_RUN_COMMAND{{mlr --help-keyword dump}}HERE
|
|
|
|
<li/> <code>mlr put</code> sends the current record (possibly modified by the
|
|
<code>put</code> expression) to the output record stream. Records are then input to
|
|
the following verb in a <code>then</code>-chain (if any), else printed to standard
|
|
output (unless <code>put -q</code>). The <b>tee</b> keyword <i>additionally</i>
|
|
writes the output record to specified file(s) or pipe-to command, or
|
|
immediately to <code>stdout</code>/<code>stderr</code>.
|
|
|
|
POKI_RUN_COMMAND{{mlr --help-keyword tee}}HERE
|
|
|
|
<li/> <code>mlr put</code>’s <code>emitf</code>, <code>emitp</code>, and
|
|
<code>emit</code> send out-of-stream variables to the output record stream. These
|
|
are then input to the following verb in a <code>then</code>-chain (if any), else
|
|
printed to standard output. When redirected with <code>></code>,
|
|
<code>>></code>, or <code>|</code>, they <i>instead</i> write the out-of-stream
|
|
variable(s) to specified file(s) or pipe-to command, or immediately to
|
|
<code>stdout</code>/<code>stderr</code>.
|
|
|
|
POKI_RUN_COMMAND{{mlr --help-keyword emitf}}HERE
|
|
POKI_RUN_COMMAND{{mlr --help-keyword emitp}}HERE
|
|
POKI_RUN_COMMAND{{mlr --help-keyword emit}}HERE
|
|
|
|
</ul>
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Emit statements</h2>
|
|
|
|
<p/>There are three variants: <code>emitf</code>, <code>emit</code>, and
|
|
<code>emitp</code>. Keep in mind that out-of-stream variables are a nested,
|
|
multi-level hashmap (directly viewable as JSON using <code>dump</code>), whereas
|
|
Miller output records are lists of single-level key-value pairs. The three emit
|
|
variants allow you to control how the multilevel hashmaps are flatten down to
|
|
output records. You can emit any map-valued expression, including <code>$*</code>,
|
|
map-valued out-of-stream variables, the entire out-of-stream-variable
|
|
collection <code>@*</code>, map-valued local variables, map literals, or map-valued
|
|
function return values.
|
|
|
|
<p/>Use <b>emitf</b> to output several out-of-stream variables side-by-side in the same output record.
|
|
For <code>emitf</code> these mustn’t have indexing using <code>@name[...]</code>. Example:
|
|
|
|
POKI_RUN_COMMAND{{mlr put -q '@count += 1; @x_sum += $x; @y_sum += $y; end { emitf @count, @x_sum, @y_sum}' data/small}}HERE
|
|
|
|
<p/>Use <b>emit</b> to output an out-of-stream variable. If it’s non-indexed you’ll get a simple key-value pair:
|
|
|
|
POKI_RUN_COMMAND{{cat data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum += $x; end { dump }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum += $x; end { emit @sum }' data/small}}HERE
|
|
|
|
<p/>If it’s indexed then use as many names after <code>emit</code> as there are indices:
|
|
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a] += $x; end { dump }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a] += $x; end { emit @sum, "a" }' data/small}}HERE
|
|
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { dump }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { emit @sum, "a", "b" }' data/small}}HERE
|
|
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b][$i] += $x; end { dump }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b][$i] += $x; end { emit @sum, "a", "b", "i" }' data/small}}HERE
|
|
|
|
<p/>Now for <b>emitp</b>: if you have as many names following <code>emit</code> as
|
|
there are levels in the out-of-stream variable’s hashmap, then <code>emit</code> and <code>emitp</code> do the same
|
|
thing. Where they differ is when you don’t specify as many names as there are hashmap levels. In this
|
|
case, Miller needs to flatten multiple map indices down to output-record keys: <code>emitp</code> includes full
|
|
prefixing (hence the <code>p</code> in <code>emitp</code>) while <code>emit</code> takes the deepest hashmap key as the
|
|
output-record key:
|
|
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { dump }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { emit @sum, "a" }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { emit @sum }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { emitp @sum, "a" }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { emitp @sum }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr --oxtab put -q '@sum[$a][$b] += $x; end { emitp @sum }' data/small}}HERE
|
|
|
|
<p/>Use <b>--oflatsep</b> to specify the character which joins multilevel
|
|
keys for <code>emitp</code> (it defaults to a colon):
|
|
|
|
POKI_RUN_COMMAND{{mlr put -q --oflatsep / '@sum[$a][$b] += $x; end { emitp @sum, "a" }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q --oflatsep / '@sum[$a][$b] += $x; end { emitp @sum }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr --oxtab put -q --oflatsep / '@sum[$a][$b] += $x; end { emitp @sum }' data/small}}HERE
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Multi-emit statements</h2>
|
|
|
|
<p/>You can emit <b>multiple map-valued expressions side-by-side</b> by
|
|
including their names in parentheses:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/emit-lashed.sh)HERE
|
|
|
|
What this does is walk through the first out-of-stream variable
|
|
(<code>@x_sum</code> in this example) as usual, then for each keylist found (e.g.
|
|
<code>pan,wye</code>), include the values for the remaining out-of-stream variables
|
|
(here, <code>@x_count</code> and <code>@x_mean</code>). You should use this when all
|
|
out-of-stream variables in the emit statement have <b>the same shape and the same
|
|
keylists</b>.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>Emit-all statements</h2>
|
|
|
|
<p/>Use <b>emit all</b> (or <code>emit @*</code> which is synonymous) to output all
|
|
out-of-stream variables. You can use the following idiom to get various
|
|
accumulators output side-by-side (reminiscent of <code>mlr stats1</code>):
|
|
|
|
POKI_RUN_COMMAND{{mlr --from data/small --opprint put -q '@v[$a][$b]["sum"] += $x; @v[$a][$b]["count"] += 1; end{emit @*,"a","b"}'}}HERE
|
|
POKI_RUN_COMMAND{{mlr --from data/small --opprint put -q '@sum[$a][$b] += $x; @count[$a][$b] += 1; end{emit @*,"a","b"}'}}HERE
|
|
POKI_RUN_COMMAND{{mlr --from data/small --opprint put -q '@sum[$a][$b] += $x; @count[$a][$b] += 1; end{emit (@sum, @count),"a","b"}'}}HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Unset statements</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_unset_statements');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_unset_statements" style="display: block">
|
|
|
|
<p/>You can clear a map key by assigning the empty string as its value: <code>$x=""</code> or <code>@x=""</code>.
|
|
Using <code>unset</code> you can remove the key entirely. Examples:
|
|
|
|
POKI_RUN_COMMAND{{cat data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put 'unset $x, $a' data/small}}HERE
|
|
|
|
<p/>This can also be done, of course, using <code>mlr cut -x</code>. You can also
|
|
clear out-of-stream or local variables, at the base name level, or at an
|
|
indexed sublevel:
|
|
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { dump; unset @sum; dump }' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put -q '@sum[$a][$b] += $x; end { dump; unset @sum["eks"]; dump }' data/small}}HERE
|
|
|
|
<p/>If you use <code>unset all</code> (or <code>unset @*</code> which is synonymous), that will unset all out-of-stream
|
|
variables which have been defined up to that point.
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Filter statements</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_filter_statements');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_filter_statements" style="display: block">
|
|
|
|
<p/> You can use <code>filter</code> within <code>put</code>. In fact, the
|
|
following two are synonymous:
|
|
|
|
POKI_RUN_COMMAND{{mlr filter 'NR==2 || NR==3' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put 'filter NR==2 || NR==3' data/small}}HERE
|
|
|
|
<p/>The former, of course, is much easier to type. But the latter allows you to define more complex expressions
|
|
for the filter, and/or do other things in addition to the filter:
|
|
|
|
POKI_RUN_COMMAND{{mlr put '@running_sum += $x; filter @running_sum > 1.3' data/small}}HERE
|
|
POKI_RUN_COMMAND{{mlr put '$z = $x * $y; filter $z > 0.3' data/small}}HERE
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Built-in functions for filter and put</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_built_in_functions');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_built_in_functions" style="display: block">
|
|
|
|
<p/>Each function takes a specific number of arguments, as shown below, except
|
|
for functions marked as variadic such as <code>min</code> and <code>max</code>. (The
|
|
latter compute min and max of any number of numerical arguments.) There is no
|
|
notion of optional or default-on-absent arguments. All argument-passing is
|
|
positional rather than by name; arguments are passed by value, not by
|
|
reference.
|
|
|
|
<p/>You can get a list of all functions using <b>mlr -F</b>.
|
|
|
|
POKI_RUN_CONTENT_GENERATOR(mk-func-h2s.sh)HERE
|
|
|
|
<!-- ================================================================ -->
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>User-defined functions and subroutines</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_user_defined_functions');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_user_defined_functions" style="display: block">
|
|
|
|
<p/> As of Miller 5.0.0 you can define your own functions, as well as subroutines.
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>User-defined functions</h2>
|
|
|
|
<p/>Here’s the obligatory example of a recursive function to compute the factorial function:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/factorial-example.sh)HERE
|
|
|
|
<p/>Properties of user-defined functions:
|
|
|
|
<ul>
|
|
|
|
<li/> Function bodies start with <code>func</code> and a parameter list, defined
|
|
outside of <code>begin</code>, <code>end</code>, or other <code>func</code> or
|
|
<code>subr</code> blocks. (I.e. the Miller DSL has no nested functions.)
|
|
|
|
<li/> A function (uniqified by its name) may not be redefined: either by
|
|
redefining a user-defined function, or by redefining a built-in function.
|
|
However, functions and subroutines have separate namespaces: you can define a
|
|
subroutine <code>log</code> which does not clash with the mathematical <code>log</code>
|
|
function.
|
|
|
|
<li/> Functions may be defined either before or after use (there is an
|
|
object-binding/linkage step at startup). More specifically, functions may be
|
|
either recursive or mutually recursive. Functions may not call subroutines.
|
|
|
|
<li/> Functions may be defined and called either within <code>mlr put</code> or
|
|
<code>mlr put</code>.
|
|
|
|
<li/> Functions have read access to <code>$</code>-variables and
|
|
<code>@</code>-variables but may not modify them.
|
|
See also
|
|
<a href="cookbook.html#Memoization_with_out-of-stream_variables">this cookbook item</a> for an example.
|
|
|
|
<li/> Argument values may be reassigned: they are not read-only.
|
|
|
|
<li/> When a return value is not implicitly returned, this results in a return
|
|
value of absent-null. (In the example above, if there were records for which
|
|
the argument to <code>f</code> is non-numeric, the assignments would be skipped.)
|
|
See also the section on
|
|
<a href="#Null_data:_empty_and_absent">empty_and_absent null data</a>.
|
|
|
|
<li/> See the section on <a href="#Local_variables">local variables</a> for
|
|
information on scope and extent of arguments, as well as for information on the
|
|
use of local variables within functions.
|
|
|
|
<li/> See the section on <a href="#Expressions_from_files">expressions from
|
|
files</a> for information on the use of <code>-f</code> and <code>-e</code> flags.
|
|
|
|
</ul>
|
|
|
|
<!-- ================================================================ -->
|
|
<h2>User-defined subroutines</h2>
|
|
|
|
<p/>Example:
|
|
|
|
POKI_INCLUDE_AND_RUN_ESCAPED(data/subr-example.sh)HERE
|
|
|
|
<p/>Properties of user-defined subroutines:
|
|
|
|
<ul>
|
|
|
|
<li/> Subroutine bodies start with <code>subr</code> and a parameter list, defined
|
|
outside of <code>begin</code>, <code>end</code>, or other <code>func</code> or
|
|
<code>subr</code> blocks. (I.e. the Miller DSL has no nested subroutines.)
|
|
|
|
<li/> A subroutine (uniqified by its name) may not be redefined.
|
|
However, functions and subroutines have separate namespaces: you can define a
|
|
subroutine <code>log</code> which does not clash with the mathematical <code>log</code>
|
|
function.
|
|
|
|
<li/> Subroutines may be defined either before or after use (there is an
|
|
object-binding/linkage step at startup). More specifically, subroutines may be
|
|
either recursive or mutually recursive. Subroutines may call functions.
|
|
|
|
<li/> Subroutines may be defined and called either within <code>mlr put</code> or
|
|
<code>mlr put</code>.
|
|
|
|
<li/> Subroutines have read/write access to <code>$</code>-variables and
|
|
<code>@</code>-variables.
|
|
|
|
<li/> Argument values may be reassigned: they are not read-only.
|
|
|
|
<li/> See the section on <a href="#Local_variables">local variables</a> for
|
|
information on scope and extent of arguments, as well as for information on the
|
|
use of local variables within functions.
|
|
|
|
<li/> See the section on <a href="#Expressions_from_files">expressions from
|
|
files</a> for information on the use of <code>-f</code> and <code>-e</code> flags.
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>Errors and transparency</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_transparency');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_transparency" style="display: block">
|
|
|
|
<p/>As soon as you have a programming language, you start having the problem
|
|
<i>What is my code doing, and why?</i> This includes getting syntax errors
|
|
— which are always annoying — as well as the even more annoying
|
|
problem of a program which parses without syntax error but doesn’t do
|
|
what you expect.
|
|
|
|
<p/> The <code>syntax error</code> message is cryptic: it says <code>syntax error at
|
|
</code> followed by the next symbol it couldn’t parse. This is good, but
|
|
(as of 5.0.0) it doesn’t say things like <code>syntax error at line 17,
|
|
character 22</code>. Here are some common causes of syntax errors:
|
|
|
|
<ul>
|
|
|
|
<li/> Don’t forget <code>;</code> at end of line, before another statement on
|
|
the next line.
|
|
|
|
<li/> Miller’s DSL lacks the <code>++</code> and <code>--</code> operators.
|
|
|
|
<li/> Curly braces are required for the bodies of
|
|
<code>if</code>/<code>while</code>/<code>for</code> blocks, even when the body is a single
|
|
statement.
|
|
|
|
</ul>
|
|
|
|
<p/>Now for transparency:
|
|
|
|
<ul>
|
|
|
|
<li/>As in any language, you can do
|
|
<a href="#Print_statements"><code>print</code></a> (or <code>eprint</code> to print to
|
|
stderr). See also <a href="#Dump_statements"><code>dump</code></a> and <a
|
|
href="#Emit_statements"><code>emit</code></a>.
|
|
|
|
<li/> The <code>-v</code> option to <code>mlr put</code> and <code>mlr filter</code> prints
|
|
abstract syntax trees for your code. While not all details here will be of
|
|
interest to everyone, certainly this makes questions such as operator
|
|
precedence completely unambiguous.
|
|
|
|
<li/> The <code>-T</code> option prints a trace of each statement executed.
|
|
|
|
<li/> The <code>-t</code> and <code>-a</code> options show low-level details for the
|
|
parsing process and for stack-variable-index allocation, respectively. These
|
|
will likely be of interest to people who enjoy compilers, and probably less
|
|
useful for a more general audience.
|
|
|
|
<li/> Please see the <a href="#Type-checking">type-checking section</a> for
|
|
type declarations and type-assertions you can use to make sure expressions and
|
|
the data flowing them are evaluating as you expect. I made them optional
|
|
because one of Miller’s important use-cases is being able to say simple
|
|
things like <code>mlr put '$y = $x + 1' myfile.dat</code> with a minimum of
|
|
punctuational bric-a-brac — but for programs over a few lines I generally
|
|
find that the more type-specification, the better.
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
<!-- ================================================================ -->
|
|
<h1>A note on the complexity of Miller’s expression language</h1>
|
|
<button style="font-weight:bold;color:maroon;border:0" padding=0 onclick="vis_toggle_by_name('body_section_toggle_a_note_on_complexity');" href="javascript:;">Toggle section visibility</button>
|
|
<div id="body_section_toggle_a_note_on_complexity" style="display: block">
|
|
|
|
<p/> One of Miller’s strengths is its brevity: it’s much quicker
|
|
— and less error-prone — to type <code>mlr stats1 -a sum -f x,y -g
|
|
a,b</code> than having to track summation variables as in <code>awk</code>, or using
|
|
Miller’s out-of-stream variables. And the more language features
|
|
Miller’s put-DSL has (for-loops, if-statements, nested control
|
|
structures, user-defined functions, etc.) then the <i>less</i> powerful it
|
|
begins to seem: because of the other programming-language features it
|
|
<i>doesn’t</i> have (classes, execptions, and so on).
|
|
|
|
<p/> When I was originally prototyping Miller in 2015, the decision I had was
|
|
whether to hand-code in a low-level language like C or Rust, with my own
|
|
hand-rolled DSL, or whether to use a higher-level language (like Python or Lua
|
|
or Nim) and let the <code>put</code> statements be handled by the implementation
|
|
language’s own <code>eval</code>: the implementation language would take the
|
|
place of a DSL. Multiple performance experiments showed me I could get better
|
|
throughput using the former, and using C in particular — by a wide margin. So
|
|
Miller is C under the hood with a hand-rolled DSL.
|
|
|
|
<p/> I do want to keep focusing on what Miller is good at — concise
|
|
notation, low latency, and high throughput — and not add too much in
|
|
terms of high-level-language features to the DSL. That said, some sort of
|
|
customizability is a basic thing to want. As of 4.1.0 we have recursive
|
|
for/while/if structures on about the same complexity level as <code>awk</code>; as
|
|
of 5.0.0 we have user-defined functions and map-valued variables, again on
|
|
about the same complexity level as <code>awk</code> along with optional
|
|
type-declaration syntax. While I’m excited by these powerful language
|
|
features, I hope to keep new features beyond 5.0.0 focused on Miller’s
|
|
sweet spot which is speed plus simplicity.
|
|
|
|
</div>
|