Cutting and Joining Text (cut, paste, tr)

The cut, paste, and tr commands are three small, single-purpose Unix tools for reshaping plain text on the command line. cut pulls columns (fields, characters, or byte ranges) out of each line of input, paste glues the lines of two or more files together side by side, and tr translates, deletes, or squeezes individual characters in a stream. None of them understand regular expressions or whole records the way sed or awk do – they are deliberately narrow, which is exactly what makes them fast, predictable, and easy to chain together in pipelines.

Overview / How it works

Linux has no built-in idea of a “spreadsheet” or a “table.” A CSV file, a log file, and the output of ps aux are all just streams of bytes broken into lines by newline characters. Any structure you see – columns, fields, delimiters – is something each tool imposes on the text as it reads it, one line at a time. cut, paste, and tr each impose a different, very simple structure, and understanding what each one actually does under the hood explains both their power and their limitations.

cut reads its input one line at a time and, for each line, either slices out a fixed range of byte or character positions, or splits the line on a single literal delimiter character and keeps only the requested fields. It never runs a regular expression, and it never collapses repeated delimiters – a comma-separated file with exactly one comma between fields works perfectly, but a whitespace-padded ls -l listing with a variable number of spaces between columns will come out misaligned, because cut treats every single space as its own field boundary. The selected fields are reassembled and printed with the same delimiter that was used to split them, unless you override it, and they are printed in the order you listed the field numbers – not necessarily the order they appeared in the original line.

paste works in the opposite direction: instead of slicing a line apart, it stitches separate lines together. Internally it opens every file argument as its own input stream and reads one line from each file in round-robin fashion, joining what it read with a delimiter (a tab by default) and writing the combined result as one output line. This is why paste is described as merging files “in lockstep”: line 1 of every file becomes one output line, then line 2 of every file becomes the next output line, and so on, until the longest file is exhausted (shorter files simply contribute empty fields once they run out of lines).

tr is the odd one out: it has no concept of lines or fields at all. It reads its input as a raw stream of individual characters and performs a positional mapping between two character sets you give it – the character at position N in SET1 is replaced by the character at position N in SET2 everywhere it occurs, or, with -d, characters found in SET1 are deleted outright, or, with -s, consecutive repeats of characters in SET1 are squeezed down to a single occurrence. Because tr thinks in individual characters instead of lines or files, it can only ever read from standard input – it has no FILE argument at all, which trips up almost everyone the first time they reach for it.

Syntax

cut -d DELIM -f FIELD_LIST [FILE]...
cut -c CHAR_LIST [FILE]...
paste [-d DELIM_LIST] [-s] [FILE]...
tr [OPTION]... SET1 [SET2]

cut options

Option Meaning
-d DELIM Use DELIM as the field delimiter instead of the default tab character. Must be a single character.
-f LIST Print only the listed fields, e.g. -f1,3 or -f2-4.
-c LIST Print only the listed character positions, e.g. -c1-10.
-b LIST Print only the listed byte positions (differs from -c on multi-byte UTF-8 text).
-s Suppress lines that do not contain the delimiter at all, instead of printing them unchanged.
--complement Print all fields or characters except the ones listed.
--output-delimiter=STR Join the selected fields on output with STR instead of reusing the input delimiter.

paste options

Option Meaning
-d LIST Cycle through the characters in LIST as delimiters instead of the default tab.
-s Serial mode: merge all the lines of each file into one line, instead of merging across files.
- as a filename Read that input from standard input instead of a file.

tr options

Option Meaning
-d Delete every character found in SET1; no SET2 is given.
-s Squeeze runs of repeated characters from SET1 down to a single occurrence.
-c, -C Complement SET1 – operate on every character not listed instead.
-t Truncate SET1 to the length of SET2, instead of the default of repeating SET2’s last character to fill it out.

Examples

Example 1: Extracting columns from a CSV file with cut

cat employees.csv
id,name,department,salary
101,Ravi Kumar,Engineering,85000
102,Ananya Singh,Marketing,62000
103,Wei Chen,Engineering,91000
cut -d',' -f2,4 employees.csv
name,salary
Ravi Kumar,85000
Ananya Singh,62000
Wei Chen,91000

-d',' tells cut that a comma separates fields, and -f2,4 keeps only the 2nd and 4th field of every line, including the header. Note that cut has no idea the first line is a header – it processes every line identically.

Example 2: Extracting a fixed-width timestamp with cut -c

cat /var/log/app.log
2026-08-04 09:15:02 INFO  Starting worker pool
2026-08-04 09:15:03 ERROR Failed to connect to db
cut -c1-19 /var/log/app.log
2026-08-04 09:15:02
2026-08-04 09:15:03

Here there is no delimiter to split on at all – every log line starts with a timestamp of exactly the same width, so -c1-19 simply keeps the first 19 characters of every line, counting the date, the space, and the time.

Example 3: Merging two files side by side with paste

cat names.txt
Ravi
Ananya
Wei
cat scores.txt
88
95
79
paste -d',' names.txt scores.txt
Ravi,88
Ananya,95
Wei,79

paste read line 1 from names.txt and line 1 from scores.txt, joined them with a comma, and printed the result, then repeated for line 2 and line 3. Without -d, it would have used a tab instead of a comma.

Example 4: Translating and cleaning a stream with tr

echo "Hello World" | tr 'a-z' 'A-Z'
HELLO WORLD
cut -d':' -f1 /etc/passwd | tr 'a-z' 'A-Z' | head -n 3
ROOT
DAEMON
BIN
tr -s ' ' < /var/log/app.log | cut -d' ' -f3
INFO
ERROR

The first command maps every lowercase letter to its uppercase counterpart. The second chains three tools: cut pulls the username field out of /etc/passwd, tr uppercases it, and head limits the output. The third squeezes the log file’s runs of spaces down to single spaces with tr -s ' ' before handing it to cut, which is what makes field 3 line up correctly even though the original spacing wasn’t consistent.

How it works step by step

For cut -d',' -f2,4 employees.csv: the shell opens employees.csv and hands it to cut, which reads one line at a time. For each line it scans left to right, splitting the line into fields wherever a literal comma appears (fields are 1-indexed). It then looks at the requested list, 2,4, pulls exactly those fields, and writes them back out joined by the same comma delimiter, before moving on to the next line. No line is held in memory longer than necessary, and no field is inspected beyond checking for the delimiter character.

For paste -d',' names.txt scores.txt: the shell opens both files as separate file descriptors before paste starts reading. paste then reads exactly one line from the first descriptor and one line from the second, concatenates them with the delimiter, and writes the combined line to standard output. It repeats this read-join-write cycle until every input file has been read to its end.

For the tr -s ' ' < /var/log/app.log | cut -d' ' -f3 pipeline: the shell sets up two processes connected by a pipe, so the standard output of tr becomes the standard input of cut. tr reads the log file byte by byte (via the < redirection) and, whenever it sees two or more consecutive spaces, writes only one space to its output stream; single spaces pass through unchanged. cut, running concurrently, reads whatever tr has produced so far, splits each line on single spaces, and keeps field 3 – which is now reliably the log level, because the squeezing step already normalized the spacing.

Common Mistakes

Mistake 1: Assuming cut splits on spaces by default

cut‘s default delimiter is a tab character, not a space. Forgetting to pass -d on a comma-separated file silently returns the whole line as “field 1” every time, because no tab character was ever found.

cut -f2 employees.csv
id,name,department,salary
101,Ravi Kumar,Engineering,85000
102,Ananya Singh,Marketing,62000
103,Wei Chen,Engineering,91000

Corrected – specify the delimiter explicitly:

cut -d',' -f2 employees.csv
name
Ravi Kumar
Ananya Singh
Wei Chen

Mistake 2: Passing a file directly to tr

tr never takes a FILE operand – it only ever reads from standard input. Giving it a filename is interpreted as an extra SET argument, and tr refuses to run.

tr 'a-z' 'A-Z' employees.csv
tr: extra operand 'employees.csv'
Only one string may be given when deleting without squeezing repeats.
Try 'tr --help' for more information.

Corrected – redirect the file into standard input, or pipe it in with cat:

tr 'a-z' 'A-Z' < employees.csv
ID,NAME,DEPARTMENT,SALARY
101,RAVI KUMAR,ENGINEERING,85000
102,ANANYA SINGH,MARKETING,62000
103,WEI CHEN,ENGINEERING,91000

Mistake 3: A shorter SET2 silently repeats its last character

If SET2 has fewer characters than SET1, tr does not error out – it extends SET2 by repeating its final character until the lengths match, which can produce very surprising output.

echo "hello" | tr 'a-z' 'AB'
BBBBB

Here SET1 is the 26 letters a through z, but SET2 is only AB, so it gets padded to A followed by 25 copies of B. Only the letter a maps to A; every other letter, including every letter in “hello”, maps to B. If the intent was to swap specific letters, list exactly as many characters in SET2 as in SET1, or use -t to truncate SET1 instead of stretching SET2:

echo "hello" | tr -t 'a-z' 'AB'
Bello

Mistake 4: Expecting paste without -s to join one file’s lines into one

paste only merges across files by default; a single file passed without -s is written back out unchanged, one line at a time, which surprises people who wanted a one-line, comma-joined list.

paste names.txt
Ravi
Ananya
Wei

Corrected – add -s to serialize the file’s own lines into a single output line:

paste -s -d',' names.txt
Ravi,Ananya,Wei

Best Practices

  • Reach for awk instead of cut when a delimiter can repeat (like padded whitespace) or when you need to transform, not just extract, fields.
  • Always pass -d explicitly to cut rather than relying on its tab default – it avoids a whole class of silent “field 1 is the whole line” bugs.
  • Squeeze repeated delimiters with tr -s before piping whitespace-separated output into cut.
  • Remember tr only reads standard input – redirect a file in with < or pipe it with cat file | tr ....
  • Keep SET1 and SET2 the same length in tr unless you deliberately want the last-character-repeats behavior.
  • Use --output-delimiter with cut when you want to reformat a file, not merely slice it.
  • Try destructive-looking commands like cut -c or tr -d on a small sample or with head first, before running them over an entire large file.

Practice Exercises

  • You have inventory.csv with columns sku,item,quantity,warehouse. Write one command that prints only the item and warehouse columns. (Hint: pick the right -d and -f values.)
  • You have fruits.txt (one fruit name per line) and prices.txt (one price per line, same order). Produce a comma-separated list like apple,1.20 per line, and separately turn fruits.txt alone into a single comma-separated line. (Hint: you’ll need two different paste invocations, only one of which uses -s.)
  • A file usernames.txt contains mixed-case usernames, one per line. Produce a version where every username is entirely lowercase, without changing anything else in the file. (Hint: tr‘s two character sets don’t have to be letters written as ranges only in one direction.)

Summary

  • cut extracts columns by delimiter (-d/-f) or by fixed position (-c/-b); it splits on a single literal character and never collapses repeated delimiters.
  • paste merges files line by line in lockstep using a delimiter, or serializes one file’s own lines into a single line with -s.
  • tr transforms a raw character stream through a positional SET1-to-SET2 mapping, deletion with -d, or squeezing with -s – it only ever reads standard input, never a file argument.
  • None of the three tools use regular expressions; reach for awk or sed when you need pattern matching or need to handle repeated delimiters.
  • Be explicit about cut‘s delimiter rather than relying on its tab default, and check that tr‘s two character sets are the length you expect.