Why Reach for a CLI?

Most data transformation tasks can be done with a script in Python or JavaScript. But for small, focused jobs, a command-line tool often wins on three fronts. First, a one-liner is easier to read later than a script you wrote months ago. Second, installers like Homebrew or Chocolatey make CLIs far simpler to set up than managing a language runtime. Third, command-line tools are old and change slowly, so your knowledge doesn't go stale as fast as it would with a modern framework.

Miller fits that bill neatly. It's a standalone binary, so there's no virtual environment or package manager drama. It streams data rather than loading it into memory, which means you can process large files without freezing your machine. And it's actively maintained, which is always reassuring.

Getting Started

Installation is straightforward:

  • Linux: apt-get install miller or Homebrew
  • macOS: brew install miller
  • Windows: choco install miller

That gives you the mlr command. Verify with mlr help topics if you like, though the built-in docs may not be necessary for what follows.

Every Miller command follows the same pattern. You specify the input format, choose an operation (called a "verb"), and point at a file:

mlr [input/output file formats] [verbs] [file]

Here, --csv declares the input type, filter is the verb (dropping rows where color equals "red"), and example.csv is the target. Other common verbs include sort and cut.

Basic Operations on TV Rating Data

Peeking at Data

To illustrate the basics, we'll use a CSV of IMDb ratings for American TV dramas. The first useful verb is head, which shows only the top of a file instead of dumping everything to the console. Combining it with output formatting keeps things readable:

`mlr --csv head ./tv_ratings.csv`

That prints a table view:

titleId,seasonNumber,title,date,av_rating,share,genres
tt2879552,1,11.22.63,2016-03-10,8.489,0.51,"Drama,Mystery,Sci-Fi"
tt3148266,1,12 Monkeys,2015-02-27,8.3407,0.46,"Adventure,Drama,Mystery"
tt3148266,2,12 Monkeys,2016-05-30,8.8196,0.25,"Adventure,Drama,Mystery"
tt3148266,3,12 Monkeys,2017-05-19,9.0369,0.19,"Adventure,Drama,Mystery"
tt3148266,4,12 Monkeys,2018-06-26,9.1363,0.38,"Adventure,Drama,Mystery"
tt1837492,1,13 Reasons Why,2017-03-31,8.437,2.38,"Drama,Mystery"
tt1837492,2,13 Reasons Why,2018-05-18,7.5089,2.19,"Drama,Mystery"
tt0285331,1,24,2002-02-16,8.5641,6.67,"Action,Crime,Drama"
tt0285331,2,24,2003-02-09,8.7028,7.13,"Action,Crime,Drama"
tt0285331,3,24,2004-02-09,8.7173,5.88,"Action,Crime,Drama"

You can switch to a more human-friendly layout with --opprint, which shows aligned columns instead of raw CSV. There's also a shortcut, --c2p, that combines --csv (input) and --opprint (output).

Chaining Verbs

The real power comes from chaining operations with the then keyword rather than running separate commands.

Suppose the titleId column is noise. The cut verb removes it:

mlr --c2p cut -x -f titleId then head ./tv_ratings.csv

Now, to keep only rows for first seasons, use filter with an expression:

mlr --c2p filter '$seasonNumber == 1' then head ./tv_ratings.csv

Sorting by a column is just as direct. The command below orders by descending rating:

mlr --c2p sort -nr av_rating then head ./tv_ratings.csv

That reveals Parenthood (1990) as the highest-rated entry.

Saving Results and Changing Formats

Miller prints to the console by default. Redirect with > to write a new file:

mlr --csv sort -nr av_rating ./tv_ratings.csv > sorted.csv

To convert CSV to JSON, use the --c2j flag instead of --csv or --c2p:

mlr --c2j sort -nr av_rating ./tv_ratings.csv > sorted.json

Working Example: Top Medal Winners in Rio 2016

Now let's apply these verbs to a real problem. We have a CSV listing every athlete in the 2016 Rio Olympics, with columns for name, nationality, and medal counts per type. We want the five athletes with the most total medals.

mlr --c2p head ./athletes.csv

The file header shows the fields available:

id        name                nationality sex    date_of_birth height weight sport      gold silver bronze info
736041664 A Jesus Garcia      ESP         male   1969-10-17    1.72    64     athletics    0    0      0      -
532037425 A Lam Shin          KOR         female 1986-09-23    1.68    56     fencing      0    0      0      -
435962603 Aaron Brown         CAN         male   1992-05-27    1.98    79     athletics    0    0      1      -
521041435 Aaron Cook          MDA         male   1991-01-02    1.83    80     taekwondo    0    0      0      -
33922579  Aaron Gate          NZL         male   1990-11-26    1.81    71     cycling      0    0      0      -
173071782 Aaron Royle         AUS         male   1990-01-26    1.80    67     triathlon    0    0      0      -
266237702 Aaron Russell       USA         male   1993-06-04    2.05    98     volleyball   0    0      1      -
382571888 Aaron Younger       AUS         male   1991-09-25    1.93    100    aquatics     0    0      0      -
87689776  Aauri Lorena Bokesa ESP         female 1988-12-14    1.80    62     athletics    0    0      0      -

Start by dropping columns you won't need — info, id, weight, and date_of_birth:

mlr --csv -I cut -x -f id,info,weight,date_of_birth athletes.csv

The dataset only gives separate counts for bronze, silver, and gold. To get a total, compute a new field with put:

mlr --c2p put '$medals=$bronze+$silver+$gold' then head ./athletes.csv

That adds a medals column equal to the sum of the three medal types. Then sort by that new field, descending:

mlr --c2p put '$medals=$bronze+$silver+$gold' \
    then sort -nr medals \
    then head ./athletes.csv

Restrict to the top five with head -n 5:

mlr --c2p put '$medals=$bronze+$silver+$gold' \
    then sort -nr medals \
    then head -n 5 ./athletes.csv

Finally, convert to JSON:

mlr --c2j put '$medals=$bronze+$silver+$gold' \
    then sort -nr medals \
    then head -n 5 ./athletes.csv > top5.json

That single command chain computes a new column, sorts, truncates, and outputs JSON. If you only wanted the top women, add a filter step:

mlr --c2p put '$medals=$bronze+$silver+$gold' then sort -nr medals then filter '$sex == "female"' then head -n 5 ./athletes.csv

The result is a clean data file:

name              nationality   sex       height   sport        gold silver bronze medals
Katie Ledecky     USA           female    1.83     aquatics       4    1      0      5
Simone Biles      USA           female    1.45     gymnastics     4    0      1      5
Emma McKeon       AUS           female    1.80     aquatics       1    2      1      4
Katinka Hosszu    HUN           female    1.75     aquatics       3    1      0      4
Madeline Dirado   USA           female    1.76     aquatics       2    1      1      4

Miller handles the whole pipeline — cleaning, transforming, sorting, and converting — without writing a single line of application code. Next time you reach for a throwaway script, consider whether a command-line verb chain can do the job faster.