# From one-liner to shell alias A one-liner is disposable by design. The moment you type the same one twice, something has earned a place in your shell config. This chapter covers the mechanics of lifting a pperl recipe into a bash or zsh shell function, how to parametrise it, and when to stop and write a real script instead. Assumed baseline: you have a bash or zsh shell, a `~/.bashrc` or `~/.zshrc` (or an `rc.d` / `functions` directory your shell sources), and the patience to close and reopen one terminal after editing it. ## Alias versus function Bash and zsh have two mechanisms. They are not interchangeable. - `alias` is textual substitution. It runs at the start of a command line, replacing the alias name with its value before the shell re-parses. No arguments, no pipelining quirks beyond what substitution gives you. - A shell function is a named piece of shell code. It takes positional arguments (`$1`, `$2`, `$@`), reads stdin like any other command, and can pipe into and out of other commands. **Rule of thumb.** If the recipe takes no arguments, alias is enough. If it needs any argument (a column number, a regex, a filename), use a function. ### Alias — the zero-argument recipe ```bash # In ~/.bashrc alias sum-stdin='pperl -MList::Util=sum -lane '"'"'print sum @F'"'" alias count-lines='pperl -lne '"'"'END { print $. }'"'" ``` The quoting is ugly because a bash single-quoted string cannot contain a single quote; the idiom `'"'"'` closes-and-reopens. This is the main reason to prefer functions: they let you quote the Perl once, cleanly. ### Function — the common case ```bash # In ~/.bashrc sum-stdin() { pperl -MList::Util=sum -lane 'print sum @F'; } count-lines() { pperl -lne 'END { print $. }'; } ``` Usage is the same: ```bash $ printf '1 2 3\n4 5 6\n' | sum-stdin 6 15 ``` (parametrised-functions)= ## Parametrised functions The shell interpolates `$1`, `$@`, `"$1"` into the function body before executing it. Inside the Perl program, the shell-level `$1` appears as a literal value — Perl's own `$1` (the regex capture) is untouched by shell interpolation only if you quote with single quotes. The mixture is solvable with care. Two idioms cover almost every case. ### Idiom 1: shell-interpolated column / value Use double quotes around the Perl program so the shell expands `$1`, then escape Perl's own `$` with `\$`. ```bash # Sum column N of stdin: sumcol N sumcol() { pperl -MList::Util=sum -lane "\$s += \$F[$1]; END { print \$s }" } # Usage $ cat data.tsv 10 apple 3 15 pear 7 20 plum 2 $ sumcol 2 < data.tsv 12 ``` Every `\$` is a literal dollar sign for Perl; every `$1` (unescaped) is the first positional argument expanded by the shell. ### Idiom 2: program in single quotes, data via environment Single-quoted Perl stays readable, and values reach the program via `%ENV`. ```bash # Grep with a pperl regex: pgrep PATTERN [FILE...] pgrep() { local pat=$1 shift pat="$pat" pperl -ne 'print if /$ENV{pat}/' "$@" } $ pgrep '\berror\b' app.log ``` This keeps `$1` (Perl capture) usable and avoids the backslash cascade. ### Idiom 3: mixing both ```bash # Replace column N's commas with semicolons, in place: csv-fix N FILE... csv-fix() { local col=$1 shift col="$col" pperl -i.bak -F'\t' -lane ' my $c = $ENV{col}; $F[$c] =~ tr/,/;/; print join("\t", @F); ' "$@" } ``` The column number arrives in `$ENV{col}` — no escaping dance. The file list propagates via `"$@"` so spaces in filenames survive. ## Practical aliases worth keeping Annotated list of recipes that earn their place in a shell config for anyone who processes text at the command line daily. ### `sumcol N` — sum column N (whitespace-separated) ```bash sumcol() { pperl -MList::Util=sum -lane "\$s += \$F[$1]; END { print \$s }" } ``` ### `sumcolf N FS` — sum column N with custom field separator ```bash sumcolf() { local col=$1 fs=$2 col="$col" pperl -MList::Util=sum -F"$fs" -lane ' $s += $F[$ENV{col}]; END { print $s } ' } $ sumcolf 3 : < /etc/passwd # sum of UIDs ``` ### `colstats N` — min / max / mean / count for column N ```bash colstats() { col="$1" pperl -MList::Util=qw(min max sum) -lane ' my $c = $ENV{col}; push @v, $F[$c]; END { printf "n=%d min=%g max=%g mean=%.4f\n", scalar @v, min(@v), max(@v), sum(@v) / @v } ' } ``` ### `uniq-stream` — streaming unique, keeping first sighting ```bash uniq-stream() { pperl -ne 'print unless $seen{$_}++'; } ``` Unlike `sort -u`, order is preserved. Unlike `uniq`, input need not be sorted. ### `extract PAT` — print every match of a regex, one per line ```bash extract() { pat="$1" pperl -lne 'print for /$ENV{pat}/g'; } $ extract '\b\d{3}-\d{4}\b' < phonebook.txt 555-1212 555-9876 ``` ### `between A B` — print everything between regex A and regex B, inclusive ```bash between() { a="$1" b="$2" pperl -ne 'print if /$ENV{a}/ .. /$ENV{b}/' } $ between 'BEGIN cert' 'END cert' < bundle.pem ``` ### `json-pretty` — pretty-print a single JSON document ```bash json-pretty() { pperl -MJSON::PP -0777 -ne ' print JSON::PP->new->pretty->canonical->encode( JSON::PP->new->decode($_) ) ' } ``` (json-passwd)= ### `passwd-json` — /etc/passwd as NDJSON ```bash passwd-json() { pperl -MJSON::PP -F: -lane ' print JSON::PP->new->encode({ user => $F[0], uid => 0+$F[2], gid => 0+$F[3], home => $F[5], shell => $F[-1] }) ' < /etc/passwd } ``` ### `words-by-freq` — count word frequencies, sorted ```bash words-by-freq() { pperl -ne ' $w{lc $_}++ for /\b[[:alpha:]]+\b/g; END { print "$w{$_} $_\n" for sort { $w{$b} <=> $w{$a} } keys %w } ' } $ words-by-freq < essay.txt | head -20 ``` ### `strip-comments LANG` — strip comments for a few common languages ```bash strip-comments() { case "$1" in sh|py|rb|pl) pperl -pe 's/(?&2; return 1 ;; esac } ``` ## When to stop and write a script The one-liner → alias transition ends when: - The program needs an `if` / `else` branch longer than a single `? :` conditional. - There are more than two or three `-M` modules. - You find yourself debugging via trial-and-error on the command line. - The program is the only alias in your config that gets a comment above it explaining what it does. - A colleague is going to run it. Move to a script file. Drop the switches into the shebang line and let the program read naturally: ```perl #!/usr/bin/env pperl use strict; use warnings; use List::Util qw(sum); my $col = shift @ARGV // die "usage: $0 COLUMN [FILE...]\n"; my $total = 0; while (<>) { my @f = split; $total += $f[$col]; } print "$total\n"; ``` Give it a sensible name, `chmod +x`, and move on. The one-liner did its job: it was the shortest path from "I have an idea" to "I know this works"; the script is the path from "I know this works" to "my colleague can read it." ## Find out more - [progression](progression) — the recipes these aliases wrap. - [gotchas](quoting) — quoting inside shell functions and aliases. - [numeric](numeric) — more candidates for the alias list, especially the date-arithmetic ones.