Transforming text
Single-page view · as markdown
Transforming text
Peios / Using Peios / Peiosutils / Transforming text
This topic covers the commands that reshape text: putting lines in order, dropping duplicates, swapping characters, reflowing paragraphs to a width, counting what is there. Its companion, Viewing and joining text, covers printing and combining files; this topic is about changing the text itself.
The commands #
Ordering lines
| Command | Purpose |
|---|---|
sort | Sort lines — alphabetically, numerically, and many other ways. |
shuf | The opposite of sorting: put lines into a random order. |
tsort | Topological sort — order items so that each comes after the things it depends on. |
Filtering lines
| Command | Purpose |
|---|---|
uniq | Collapse or report adjacent repeated lines. |
Substituting characters
| Command | Purpose |
|---|---|
tr | Translate, squeeze, or delete individual characters. |
Whitespace
Reflowing and paginating
| Command | Purpose |
|---|---|
fmt | Reflow paragraphs to a target width. |
fold | Hard-wrap long lines at a fixed width. |
pr | Paginate and columnate text for printing. |
Indexing and counting
| Command | Purpose |
|---|---|
ptx | Produce a permuted index of the words in a file. |
wc | Count the lines, words, and bytes in a file. |
Two commands that need sorted input #
A theme worth knowing before you start: uniq only collapses adjacent duplicate lines, so duplicates scattered through a file are not caught unless the file is sorted first. sort and uniq are almost always used together — and sort -u does both jobs in one step.
Where to start #
sort is the workhorse of the topic and the one with the most depth — start there.
sort
Peios / Using Peios / Peiosutils / Transforming text
sort reads lines and writes them out in order.
sort [options] [file...]
$ sort names.txt
With no file, sort reads standard input. Given several files, it sorts all of their lines together as one stream. By default it compares lines as plain text, character by character.
How lines are compared #
The default text comparison is often not the order you want — 10 sorts before 9, and case matters. These options change the comparison:
| Option | Compares lines as… |
|---|---|
-n, --numeric-sort | numbers. 9 sorts before 10. |
-g, --general-numeric-sort | numbers, including scientific notation. Slower than -n; use it only when -n is not enough. |
-h, --human-numeric-sort | human-readable sizes — 2K before 1M before 3G. |
-M, --month-sort | month names — JAN before FEB. |
-V, --version-sort | version numbers — 1.2.10 after 1.2.9. |
-R, --random-sort | a random order (a shuffle that groups equal lines together). |
And these adjust what is compared:
| Option | Effect |
|---|---|
-f, --ignore-case | Treat lower and upper case as the same. |
-d, --dictionary-order | Consider only letters, digits, and blanks. |
-i, --ignore-nonprinting | Ignore non-printing characters. |
-b, --ignore-leading-blanks | Ignore blanks at the start of a line (or field). |
Sort order options #
| Option | Effect |
|---|---|
-r, --reverse | Reverse the result. |
-u, --unique | Output only the first line of each run of equal lines — sort and de-duplicate in one step. |
-s, --stable | Keep equal lines in their original relative order. |
Sorting by a field #
By default sort compares whole lines. -k sorts on a key — a chosen part of each line — which is what you need for tabular data.
A line is split into fields (by default at whitespace). A key is written FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]] — a start field, an optional start character within it, and an optional end. The single-letter comparison options above can be appended to a key to apply only to it.
$ sort -k 2 -n scores.txt # sort on the 2nd field, numerically
$ sort -t , -k 3 people.csv # sort a CSV on the 3rd field
| Option | Effect |
|---|---|
-k, --key=KEYDEF | Sort on the key KEYDEF. May be given more than once; keys are tried in order. |
-t, --field-separator=SEP | Use SEP to split fields, instead of the whitespace default. |
Checking and merging #
| Option | Effect |
|---|---|
-c, --check | Do not sort — just check whether the input is already sorted, and report the first line that is out of order. |
-C, --check=silent | Check, but report only through the exit status. |
-m, --merge | Treat the inputs as already-sorted files and merge them, which is faster than a full sort. |
Output and resources #
| Option | Effect |
|---|---|
-o, --output=FILE | Write to FILE instead of standard output. FILE may be one of the inputs. |
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
--parallel=N | Use N threads. |
-S, --buffer-size=SIZE | Set the size of the in-memory sort buffer. |
-T, --temporary-directory=DIR | Put temporary files in DIR rather than the default location. |
--files0-from=F | Take the list of input files from F, NUL-separated. |
--debug | Underline the part of each line that was actually used as the sort key — invaluable when a -k key is not behaving. |
Exit status #
| Code | Meaning |
|---|---|
0 | The lines were sorted — or, with -c/-C, the input was already sorted. |
1 | With -c/-C, the input was not sorted. |
2 | An error — a file could not be read, or an option was invalid. |
uniq
Peios / Using Peios / Peiosutils / Transforming text
uniq deals with repeated lines: it can collapse a run of identical lines into one, count them, or print only the ones that repeated.
uniq [options] [input [output]]
$ uniq events.txt
input and output may be given as operands; with neither, uniq reads standard input and writes standard output.
uniq only sees adjacent duplicates #
This is the one thing to know about uniq: it only compares each line with the one before it. It collapses adjacent repeats. Duplicate lines scattered through a file, with other lines between them, are not caught.
So uniq is almost always paired with sort, which brings every copy of a line together first:
$ sort events.txt | uniq
If collapsing duplicates is all you need, sort -u does both jobs in one command. Use uniq itself when you want what sort -u cannot do — counting repeats, or printing only the duplicated lines.
What to output #
By default uniq prints every line, with each run of adjacent duplicates reduced to a single copy. These options change that:
| Option | Effect |
|---|---|
-c, --count | Prefix each line with the number of times it occurred. |
-d, --repeated | Print only lines that were repeated — one copy of each. |
-D | Print all copies of every repeated line, not just one. |
-u, --unique | Print only lines that were not repeated. |
--group[=WHICH] | Print every line, with runs separated by a blank line. WHICH is separate, prepend, append, or both. |
What counts as "the same" #
| Option | Effect |
|---|---|
-i, --ignore-case | Treat lines differing only in letter case as the same. |
-f, --skip-fields=N | Ignore the first N fields when comparing. |
-s, --skip-chars=N | Ignore the first N characters when comparing. |
-w, --check-chars=N | Compare at most N characters of each line. |
Other options #
| Option | Effect |
|---|---|
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read or written. |
tr
Peios / Using Peios / Peiosutils / Transforming text
tr — "translate" — works on text one character at a time. It can replace characters with other characters, remove characters, or collapse runs of a character into one.
tr [options] set1 [set2]
tr reads standard input and writes standard output — it has no file arguments, so it always sits in a pipeline.
$ tr 'a-z' 'A-Z' < notes.txt # lower-case to upper-case
$ tr -d ' ' < spaced.txt # delete every space
$ tr -s ' ' < padded.txt # collapse runs of spaces to one
The three jobs #
What tr does depends on the options and how many sets you give it.
- Translate — give two sets. Each character in
set1is replaced by the character at the same position inset2.tr 'abc' 'xyz'turns everyaintox,bintoy,cintoz. - Delete (
-d) — give one set. Every character inset1is removed. - Squeeze (
-s) — each run of a repeated character that appears in the relevant set is collapsed to a single occurrence.
-d and -s can be combined: delete one set of characters, then squeeze another.
Writing a set #
A set is a string of characters, with a few shorthands:
| Notation | Means |
|---|---|
a-z | A range — every character from a to z. |
[:alpha:], [:digit:], [:space:], … | A named character class. |
[=c=] | An equivalence class — every character that sorts the same as c. |
[c*n] | The character c repeated n times — for padding a set to a length. |
\n, \t, \\, \NNN | Escapes — newline, tab, backslash, an octal byte. |
Options #
| Option | Effect |
|---|---|
-d, --delete | Delete the characters in set1 instead of translating. |
-s, --squeeze-repeats | Collapse each run of a repeated character listed in the last set into one. |
-c, -C, --complement | Operate on the complement of set1 — every character it does not list. |
-t, --truncate-set1 | Before translating, shorten set1 to the length of set2, rather than reusing set2's last character to cover the surplus. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A usage error — the wrong number of sets, or a malformed set. |
expand
Peios / Using Peios / Peiosutils / Transforming text
expand converts tab characters into spaces, so that text lines up the same way no matter what tab width something is viewed with.
expand [options] [file...]
$ expand source.txt > source-spaces.txt
A tab is not a fixed number of spaces — it jumps to the next tab stop, and where those stops are is a display setting. expand removes the ambiguity: it replaces each tab with exactly the number of spaces needed to reach the same column, baking the layout in. With no file, it reads standard input.
unexpand is the reverse operation.
Options #
| Option | Effect |
|---|---|
-t, --tabs=N | Set tab stops every N columns instead of the default 8. |
-t, --tabs=LIST | Given a comma-separated list, place tab stops at those explicit column positions. |
-i, --initial | Convert only the tabs that come before the first non-blank character on each line — leave tabs within the text alone. Useful for re-indenting code without disturbing aligned tabs inside it. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or a tab specification was invalid. |
unexpand
Peios / Using Peios / Peiosutils / Transforming text
unexpand is the reverse of expand: it converts runs of spaces back into tab characters.
unexpand [options] [file...]
$ unexpand spaced.txt > tabbed.txt
Where a run of spaces reaches a tab stop, unexpand replaces it with a tab. By default it only converts the leading spaces on each line — the indentation — and leaves spacing within the text alone. With no file, it reads standard input.
Options #
| Option | Effect |
|---|---|
-t, --tabs=N | Set tab stops every N columns instead of the default 8. A comma-separated list places stops at explicit column positions. Using -t also enables -a. |
-a, --all | Convert all runs of spaces that reach a tab stop, not only the leading indentation. |
--first-only | Convert only the leading run of blanks on each line. Overrides -a. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or a tab specification was invalid. |
fmt
Peios / Using Peios / Peiosutils / Transforming text
fmt reflows paragraphs: it rejoins the lines of a paragraph and re-breaks them so each comes out close to a target width. Words move freely between lines to make the result fit.
fmt [options] [file...]
$ fmt -w 72 draft.txt
fmt works on paragraphs — runs of non-blank lines separated by blank lines — and treats word boundaries as places it may break. The result reads naturally. This is what distinguishes it from fold, which simply cuts every line at a fixed column without regard to words.
With no file, fmt reads standard input.
Setting the width #
| Option | Effect |
|---|---|
-w, --width=WIDTH | Fill lines up to at most WIDTH columns. The default is 75. |
-g, --goal=WIDTH | Aim for WIDTH columns — the width fmt targets, while -w is the hard maximum. Defaults to about 93% of the maximum. |
Controlling the reflow #
| Option | Effect |
|---|---|
-s, --split-only | Only split lines that are too long; never join short lines together. |
-u, --uniform-spacing | Put exactly one space between words and two between sentences. |
-c, --crown-margin | Preserve the indentation of a paragraph's first two lines; indent the rest to match the second. |
-t, --tagged-paragraph | Like -c, but the first line must be indented differently from the second, or the two are treated as separate paragraphs. |
-q, --quick | Break lines faster, accepting a more ragged right edge. |
Selecting which lines to format #
| Option | Effect |
|---|---|
-p, --prefix=PREFIX | Reformat only lines that begin with PREFIX; reattach PREFIX afterwards. Useful for reflowing comment blocks in source code. |
-m, --preserve-headers | Detect and preserve mail-style header lines rather than reflowing them. |
--tab-width=N | Treat a tab as N columns when measuring line length. Tabs are kept in the output; this affects measurement only. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or an option value was invalid. |
fold
Peios / Using Peios / Peiosutils / Transforming text
fold breaks long lines so that none exceeds a fixed width. Every line longer than the limit is cut into pieces.
fold [options] [file...]
$ fold -w 80 wide.txt
By default fold wraps at 80 columns, and it cuts at exactly that column — even in the middle of a word. That hard cut is the difference between fold and fmt: fmt reflows paragraphs and breaks between words; fold just enforces a width, mechanically. Use fold when you need a guarantee that no line is wider than N; use fmt when you want the result to read well.
With no file, fold reads standard input.
Options #
| Option | Effect |
|---|---|
-w, --width=WIDTH | Wrap at WIDTH columns instead of 80. |
-s, --spaces | Break at a space within the width where one exists, rather than mid-word — a gentler wrap. |
-b, --bytes | Count bytes rather than display columns. Control characters such as tab and newline then count as ordinary bytes. |
-c, --characters | Count character positions rather than display columns. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or the width was invalid. |
pr
Peios / Using Peios / Peiosutils / Transforming text
pr prepares text for printing on paper. It breaks a file into pages, puts a header and a trailer on each, and can arrange the text into columns.
pr [options] [file...]
$ pr report.txt
Run plainly, pr divides the input into 66-line pages, each beginning with a five-line header — the date, the file name, and a page number — and ending with a trailing margin. With no file, it reads standard input.
Columns #
pr can lay text out in several columns per page.
| Option | Effect |
|---|---|
-COLUMN | Produce COLUMN columns per page (e.g. -3 for three). Text fills down the first column, then the next. |
-a, --across | Fill the columns across — line 1 in column 1, line 2 in column 2, and so on — rather than down. |
-m, --merge | Print several files side by side, one per column. |
-w, --width=WIDTH | Set the page width, for multi-column output. |
The header and the page #
| Option | Effect |
|---|---|
-h, --header=TEXT | Use TEXT in the header instead of the file name. |
-t, --omit-header | Print neither the header nor the trailer. |
-T, --omit-pagination | Drop the header, the trailer, and all pagination — just the text. |
-l, --length=LINES | Set the page length to LINES instead of 66. |
-o, --indent=N | Indent every line by N spaces. |
-D, --date-format=FORMAT | Format the header's date with FORMAT. |
-F, -f, --form-feed | Separate pages with a form-feed character rather than blank lines. |
Selecting and numbering #
| Option | Effect |
|---|---|
--pages=FIRST[:LAST] | Print only pages FIRST through LAST. |
-n, --number-lines[=SEP[WIDTH]] | Number every line. |
-N, --first-line-number=N | Begin line numbering at N. |
-d, --double-space | Double-space the output. |
Other options #
| Option | Effect |
|---|---|
-s, --separator-char[=CHAR] | Separate columns with a single CHAR (default tab) rather than padding with spaces. |
-S, --sep-string[=STRING] | Separate columns with STRING. |
-e, --expand-tabs[=CHAR[WIDTH]] | Expand input tabs to spaces. |
-J, --join-lines | Merge full lines, turning off width truncation. |
-r, --no-file-warnings | Do not warn when a file cannot be opened. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or an option was invalid. |
ptx
Peios / Using Peios / Peiosutils / Transforming text
ptx produces a permuted index — a concordance. For each significant word in the input, it prints a line showing that word together with the text around it, so the same passage appears once per keyword it contains.
ptx [options] [input...]
$ ptx manual.txt
A permuted index is the kind of index found at the back of a reference book: every keyword, in alphabetical order, each shown in its context. ptx builds one from plain text. With no file, it reads standard input.
Choosing the keywords #
| Option | Effect |
|---|---|
-W, --word-regexp=REGEXP | Treat anything matching REGEXP as a keyword. |
-b, --break-file=FILE | Take the set of word-break characters from FILE. |
-i, --ignore-file=FILE | Read a list of words to exclude from FILE. |
-o, --only-file=FILE | Index only the words listed in FILE. |
-f, --ignore-case | Fold case together when sorting. |
Output format #
| Option | Effect |
|---|---|
-w, --width=N | Set the output width, in columns. |
-g, --gap-size=N | Set the gap, in columns, between the output fields. |
-A, --auto-reference | Generate a reference (file name and line number) for each entry automatically. |
-r, --references | Treat the first field of each input line as a reference for that line. |
-R, --right-side-refs | Put references on the right, and exclude them from the width count. |
-F, --flag-truncation=STRING | Use STRING to mark where a line was truncated. |
-O, --format=roff | Emit the index as roff typesetting directives. |
-T, --format=tex | Emit the index as TeX typesetting directives. |
-M, --macro-name=NAME | Use NAME as the macro name in roff/TeX output. |
-G, --traditional | Behave like the older System V ptx, without the extended features. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A file could not be read, or an option value was invalid. |
tsort
Peios / Using Peios / Peiosutils / Transforming text
tsort performs a topological sort. Given a set of "this must come before that" rules, it produces an order in which everything appears after the things it depends on.
tsort [file]
$ tsort dependencies.txt
With no file, tsort reads standard input.
The input #
tsort reads its input as a flat sequence of whitespace-separated tokens, taken in pairs. Each pair A B means "A must come before B" — an edge in a dependency graph.
compiler linker
linker installer
compiler installer
That input says the linker depends on the compiler, the installer on the linker, and the installer on the compiler. tsort reads the pairs and prints the items in a valid order:
compiler
linker
installer
A token paired with itself (A A) simply introduces A with no dependency — a way to list an item that nothing else mentions.
Cycles #
A topological order only exists if the dependencies form no cycle. If A must come before B and B must come before A, there is no valid order. tsort detects this, reports the loop it found, and exits with a failure status — it still prints an order, but the cycle means that order cannot satisfy every rule.
Where it is used #
tsort answers "what order do I do these in?" — building components in dependency order, scheduling tasks, sequencing anything where some steps must precede others. It is the dependency-aware counterpart to sort, which orders by value rather than by dependency.
Exit status #
| Code | Meaning |
|---|---|
0 | A valid order was produced. |
1 | The input contained a cycle, had an odd number of tokens, or could not be read. |
wc
Peios / Using Peios / Peiosutils / Transforming text
wc — "word count" — counts what is in a file: its lines, its words, and its bytes.
wc [options] [file...]
$ wc report.txt
214 1832 12044 report.txt
By default wc prints three numbers per file — lines, words, bytes — followed by the file name. Given several files, it adds a total line. With no file, it reads standard input.
Choosing what to count #
Pass any of these and wc prints only the counts you ask for, in the fixed order lines, words, characters/bytes, longest-line:
| Option | Counts |
|---|---|
-l, --lines | The number of lines. |
-w, --words | The number of words — runs of non-whitespace. |
-c, --bytes | The number of bytes. |
-m, --chars | The number of characters. This differs from -c for multi-byte text, where one character is several bytes. |
-L, --max-line-length | The length of the longest line. |
$ wc -l report.txt # just the line count
214 report.txt
Other options #
| Option | Effect |
|---|---|
--files0-from=F | Take the list of files to count from F, as NUL-terminated names. - reads the list from standard input. |
--total=WHEN | Control the total line: auto (the default — shown for more than one file), always, only (just the total, no per-file lines), or never. |
Exit status #
| Code | Meaning |
|---|---|
0 | Every file was counted. |
1 | A file could not be read. |
shuf
Peios / Using Peios / Peiosutils / Transforming text
shuf shuffles: it outputs its input lines in a random order. Every possible ordering is equally likely.
shuf [options] [file]
shuf -e [options] arg...
shuf -i lo-hi [options]
$ shuf playlist.txt
shuf is the opposite of sort — instead of imposing an order, it removes one. With no file, it reads standard input.
Where the lines come from #
shuf has three ways of getting its input, one per usage form:
| Option | Input is… |
|---|---|
| (a file, or stdin) | the lines of the file. |
-e, --echo | the command-line arguments, each treated as one line. |
-i, --input-range=LO-HI | the integers from LO to HI, each treated as one line. |
$ shuf -e red green blue # shuffle three given words
$ shuf -i 1-100 # the numbers 1..100 in random order
Shaping the output #
| Option | Effect |
|---|---|
-n, --head-count=COUNT | Output at most COUNT lines — a random sample rather than the whole shuffle. |
-r, --repeat | Allow lines to be repeated, so output can be longer than the input. Pair with -n to set how many. |
-o, --output=FILE | Write to FILE instead of standard output. |
-z, --zero-terminated | Treat the NUL character as the line delimiter. |
shuf -n 1 is a common idiom — pick one random line.
Reproducible shuffles #
A shuffle is random by default. To get the same shuffle every time — for a repeatable test, say — fix the randomness:
| Option | Effect |
|---|---|
--random-seed=STRING | Seed the shuffle with STRING. The same seed gives the same shuffle. |
--random-source=FILE | Take the random bytes from FILE. The same file gives the same shuffle. Cannot be combined with --random-seed. |
Exit status #
| Code | Meaning |
|---|---|
0 | Success. |
1 | A failure — the input could not be read, or -r was used with no lines to repeat. |