# 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
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($_)
)
'
}
```
### `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.md) — the recipes these aliases wrap.
- [gotchas](gotchas.md#quoting) — quoting inside shell functions and
aliases.
- [numeric](numeric.md) — more candidates for the alias list, especially
the date-arithmetic ones.