|

Linux CLI 43 🐧 cut paste and join commands

cut -d ' ' -f 1 data.txt
cut -d ' ' -f 1,3 data.txt
cut -c 1-3 example.txt

cat data.txt
cat example.txt
paste data.txt example.txt
paste -d ',' data.txt example.txt
paste data.txt example.txt > csvfile.csv
cat csvfile.csv

cat file1.txt
cat file2.txt
join file1.txt file2.txt
join -v 1 file1.txt file2.txt
join -a 1 file1.txt file2.txt

cut, paste, and join are the column-level tools of the Unix text toolbox. cut pulls fields out of each line, paste puts lines from multiple files side by side, and join combines two files on a shared key — like a miniature SQL join at the command line.

Key point: These tools work on structured text — files organized into columns or fields. cut and paste are about layout; join is about relational matching. Together they handle most of what you’d otherwise reach for a spreadsheet or database to do.


a – cut command

cut is used to extract sections from each line of input. Extraction is based on delimiters and field positions. It is mainly used for processing text files — logs, CSVs, /etc/passwd, and anything else with columns.

Syntax:

cut [options] [file...]

Common options:

OptionPurpose
-f NExtract field N
-f N,MExtract fields N and M
-f N-MExtract a range of fields
-d CHARUse CHAR as the delimiter (default: TAB)
-c N-MExtract characters N through M
-b N-MExtract bytes N through M
--complementPrint everything except the selected fields
-sSuppress lines with no delimiter
--output-delimiter=STRUse STR between output fields

Examples:

# Look at the data first
$ cat data.txt
alice 95 A
bob 87 B
charlie 92 A
dave 78 C

# Extract the first field (space-delimited)
$ cut -d ' ' -f 1 data.txt
alice
bob
charlie
dave

# Extract fields 1 and 3
$ cut -d ' ' -f 1,3 data.txt
alice A
bob B
charlie A
dave C

# Extract a range of fields (1 through 3)
$ cut -d ' ' -f 1-3 data.txt
alice 95 A
bob 87 B
charlie 92 A
dave 78 C

# Everything except field 2
$ cut -d ' ' -f 2 --complement data.txt
alice A
bob B
charlie A
dave C

# Extract characters 1-3 of each line
$ cat example.txt
hello world
goodbye moon
hi there

$ cut -c 1-3 example.txt
hel
goo
hi

# Extract specific characters
$ cut -c 1,3,5 example.txt
hlo
gob
h hr

# Use a different delimiter — colon
$ cut -d ':' -f 1,3 /etc/passwd | head -3
root:0
daemon:1
bin:2

# Change the output delimiter
$ cut -d ':' -f 1,3 --output-delimiter=',' /etc/passwd | head -3
root,0
daemon,1
bin,2

# Suppress lines with no delimiter
$ printf "a b\nno_delim\nc d\n" | cut -d ' ' -f 1 -s
a
c

-f vs -c — fields vs characters:

ModeWhat it doesUse when
-fSplits by delimiter, selects fieldsStructured columns
-cSelects character positionsFixed-width data
# Fixed-width data — use -c
$ cat fixed.txt
2024-01-15T10:00:00
2024-01-16T11:30:00

$ cut -c 1-10 fixed.txt
2024-01-15
2024-01-16

Note: cut requires a single-character delimiter. For multi-character delimiters (like :: or , ), use awk -F instead. Also, cut doesn’t understand quotes — a comma inside a quoted CSV field will break it.


b – paste command

paste merges lines of files side by side. It reads data from each file and writes it to standard output. By default, each line from each file is separated by a TAB character.

Syntax:

paste [options] [file...]

Common options:

OptionPurpose
-d 'CHAR'Use CHAR as the delimiter (default: TAB)
-sMerge files in series (one file per line)
-Read from stdin

Examples:

# Look at the files
$ cat data.txt
alice
bob
charlie

$ cat example.txt
95
87
92

# Merge side by side (TAB separated)
$ paste data.txt example.txt
alice	95
bob	87
charlie	92

# Use a comma as the delimiter
$ paste -d ',' data.txt example.txt
alice,95
bob,87
charlie,92

# Save to a file
$ paste -d ',' data.txt example.txt > csvfile.csv
$ cat csvfile.csv
alice,95
bob,87
charlie,92

# Series mode — all of file1 on one line, then all of file2
$ paste -s data.txt example.txt
alice	bob	charlie
95	87	92

# Series mode with a delimiter
$ paste -s -d ',' data.txt
alice,bob,charlie

# Cycle through multiple delimiters
$ paste -d ',;' data.txt example.txt
alice,95
bob;87
charlie,92

# Combine more than two files
$ paste data.txt example.txt example.txt
alice	95	95
bob	87	87
charlie	92	92

# Paste from stdin with -
$ echo "extra" | paste data.txt -
alice	extra
bob	
charlie	

paste vs cat:

CommandEffect
cat a.txt b.txtConcatenates — file B below file A
paste a.txt b.txtMerges — file B beside file A
cat a.txt b.txt          paste a.txt b.txt
┌──────────┐             ┌──────────┐
│ a1       │             │ a1  b1   │
│ a2       │             │ a2  b2   │
│ a3       │             │ a3  b3   │
│ b1       │             └──────────┘
│ b2       │
│ b3       │
└──────────┘

Tip: paste is perfect for building CSVs from separate column files, or for adding a line number next to each line: paste <(seq 1 10) file.txt.


c – join command

join combines lines from two files on the basis of one or more fields. By default it assumes the first field is the join key. Files need to be sorted on that key for join to work correctly — just like uniq.

Syntax:

join [options] file1 file2

Common options:

OptionPurpose
-a NDisplay unmatched lines from file N
-v NDisplay only unmatched lines from file N
-t CHARUse CHAR as the field delimiter
-iIgnore case
-1 NJoin on field N of file 1
-2 NJoin on field N of file 2
-o FORMATSpecify output fields
-e STRReplace missing fields with STR

Examples:

# Look at the files
$ cat file1.txt
1 alice
2 bob
3 charlie
4 dave

$ cat file2.txt
1 95
2 87
3 92
5 78

# Join on the first field (default)
$ join file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92
# Note: "4 dave" and "5" have no match, so they're omitted

# Show unmatched lines from file 1
$ join -a 1 file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92
4 dave
# "4 dave" is included with no match

# Show unmatched lines from file 2
$ join -a 2 file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92
5 78

# Show only unmatched lines from file 1
$ join -v 1 file1.txt file2.txt
4 dave

# Show only unmatched lines from file 2
$ join -v 2 file1.txt file2.txt
5 78

# Show unmatched from BOTH files
$ join -a 1 -a 2 file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92
4 dave
5 78

# Use a comma delimiter
$ cat f1.csv
1,alice
2,bob
3,charlie

$ cat f2.csv
1,95
2,87
3,92

$ join -t ',' f1.csv f2.csv
1,alice,95
2,bob,87
3,charlie,92

# Join on a different field
$ cat users.txt
alice 1
bob 2
charlie 3

$ cat scores.txt
95 1
87 2
92 3

$ join -1 2 -2 2 users.txt scores.txt
1 alice 95
2 bob 87
3 charlie 92

# Ignore case
$ join -i file1.txt file2.txt

# Custom output format
$ join -o 1.1,1.2,2.2 file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92

# Replace missing fields
$ join -a 1 -e "N/A" -o 1.1,1.2,2.2 file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92
4 dave N/A

Why sorting matters:

┌──────────────────────────────────────────────┐
│           Unsorted input                     │
│                                              │
│  file1:      file2:                          │
│  3 charlie   1 95                            │
│  1 alice     3 92                            │
│  2 bob       2 87                            │
│                                              │
│  join walks both files in parallel,          │
│  comparing current keys. Unsorted = chaos.   │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│           Sorted input                       │
│                                              │
│  file1:      file2:                          │
│  1 alice     1 95                            │
│  2 bob       2 87                            │
│  3 charlie   3 92                            │
│                                              │
│  join matches 1-1, 2-2, 3-3 cleanly          │
│                                              │
└──────────────────────────────────────────────┘

Always sort both files on the join key first: sort -k1,1 file1 > f1.sorted then join f1.sorted f2.sorted. The default sort on the whole line usually works if the key is the first field.


Complete Example Session

# ============================================
# PART 1: CUT — EXTRACT FIELDS
# ============================================

$ cat data.txt
alice 95 A
bob 87 B
charlie 92 A
dave 78 C

$ cut -d ' ' -f 1 data.txt
alice
bob
charlie
dave

$ cut -d ' ' -f 1,3 data.txt
alice A
bob B
charlie A
dave C

# ============================================
# PART 2: CUT — EXTRACT CHARACTERS
# ============================================

$ cat example.txt
hello world
goodbye moon
hi there

$ cut -c 1-3 example.txt
hel
goo
hi

# ============================================
# PART 3: CUT ON /etc/passwd
# ============================================

$ cut -d ':' -f 1,3 /etc/passwd | head -5
root:0
daemon:1
bin:2
sys:3
sync:4

# ============================================
# PART 4: PASTE — MERGE SIDE BY SIDE
# ============================================

$ cat data.txt
alice
bob
charlie

$ cat example.txt
95
87
92

$ paste data.txt example.txt
alice	95
bob	87
charlie	92

$ paste -d ',' data.txt example.txt
alice,95
bob,87
charlie,92

$ paste -d ',' data.txt example.txt > csvfile.csv
$ cat csvfile.csv
alice,95
bob,87
charlie,92

# ============================================
# PART 5: PASTE — SERIES MODE
# ============================================

$ paste -s data.txt example.txt
alice	bob	charlie
95	87	92

$ paste -s -d ',' data.txt
alice,bob,charlie

# ============================================
# PART 6: JOIN — BASIC
# ============================================

$ cat file1.txt
1 alice
2 bob
3 charlie
4 dave

$ cat file2.txt
1 95
2 87
3 92
5 78

$ join file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92

# ============================================
# PART 7: JOIN — UNMATCHED LINES
# ============================================

$ join -v 1 file1.txt file2.txt
4 dave

$ join -a 1 file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92
4 dave

$ join -a 1 -a 2 file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92
4 dave
5 78

# ============================================
# PART 8: COMBINED PIPELINE
# ============================================

# Build a report: username, uid, shell
$ cut -d ':' -f 1,3,7 /etc/passwd | head -5
root:0:/bin/bash
daemon:1:/usr/sbin/nologin
bin:2:/usr/sbin/nologin
sys:3:/usr/sbin/nologin
sync:4:/bin/sync

# CSV from two columns
$ paste -d ',' <(cut -d: -f1 /etc/passwd) <(cut -d: -f3 /etc/passwd) | head -5
root,0
daemon,1
bin,2
sys,3
sync,4

# Join users with their shells
$ cut -d: -f1,3 /etc/passwd | sort -k1,1 > users.txt
$ cut -d: -f1,7 /etc/passwd | sort -k1,1 > shells.txt
$ join users.txt shells.txt | head -5
root 0 /bin/bash
daemon 1 /usr/sbin/nologin
bin 2 /usr/sbin/nologin
sys 3 /usr/sbin/nologin
sync 4 /bin/sync

Quick Reference

cut

CommandPurpose
cut -d ' ' -f 1 FILEFirst field
cut -d ' ' -f 1,3 FILEFields 1 and 3
cut -d ' ' -f 1-3 FILERange of fields
cut -d ' ' -f 2 --complement FILEAll but field 2
cut -c 1-3 FILEFirst 3 characters
cut -c 1,3,5 FILESpecific characters
cut -d ':' -f 1 /etc/passwdColon-delimited field
cut -d ':' -f 1,3 --output-delimiter=',' FILECustom output delimiter
cut -d ' ' -f 1 -s FILESuppress lines without delimiter

paste

CommandPurpose
paste A BMerge side by side (TAB)
paste -d ',' A BMerge with comma
paste -s AAll lines of A on one line
paste -s -d ',' ASeries with comma
paste A B > out.csvSave merged output
paste -d ',;' A BCycle delimiters
paste A -Merge A with stdin

join

CommandPurpose
join A BJoin on first field
join -a 1 A BInclude unmatched from A
join -a 2 A BInclude unmatched from B
join -a 1 -a 2 A BInclude all unmatched
join -v 1 A BOnly unmatched from A
join -t ',' A BCustom delimiter
join -i A BCase-insensitive
join -1 N -2 M A BJoin on different fields
join -o 1.1,1.2,2.2 A BCustom output format
join -e STR -a 1 A BFill missing fields

Tool Comparison

ToolDirectionPurpose
cutVerticalExtract fields/columns
pasteHorizontalMerge files side by side
joinRelationalMatch on shared key

Best Practices

Do This:

# Use -d to set the delimiter explicitly
cut -d ' ' -f 1 data.txt              # ✅

# Use -s to skip lines without delimiter
cut -d ' ' -f 1 -s data.txt           # ✅

# Use paste to build CSVs from columns
paste -d ',' col1.txt col2.txt > out.csv  # ✅

# Sort both files before join
sort -k1,1 f1 > f1.sorted
sort -k1,1 f2 > f2.sorted
join f1.sorted f2.sorted              # ✅

# Use -a 1 -a 2 to include all lines
join -a 1 -a 2 f1 f2                  # ✅

# Use process substitution to feed cut into paste
paste <(cut -d: -f1 /etc/passwd) <(cut -d: -f3 /etc/passwd)  # ✅

# Verify with a small sample first
head file.txt | cut -d ' ' -f 1       # ✅

Don’t Do This:

# Don't use cut with multi-character delimiters
cut -d '::' -f 1 file.txt             # ❌ only first char used

# Don't use cut for quoted CSV fields
cut -d ',' -f 2 data.csv              # ❌ breaks on "a,b"

# Don't forget to sort before join
join f1 f2                            # ❌ wrong results if unsorted

# Don't confuse -a and -v
join -v 1 f1 f2                       # ✅ only unmatched
join -a 1 f1 f2                       # ✅ matched + unmatched

# Don't paste files with different line counts blindly
paste short.txt long.txt              # ⚠️  short lines padded with tabs

# Don't use cut when awk is clearer
cut -d ' ' -f 2- file.txt             # ⚠️  works, but awk is cleaner

Common Pitfalls

PitfallProblemSolution
Multi-char delimitercut uses only the first charUse awk -F
Unsorted join inputWrong or missing matchessort first
Quoted CSVcut breaks on commas in quotesUse a real CSV parser
TAB vs spacesDefault delimiter is TABSpecify -d ' '
-a vs -v confusionWrong set of lines-a = include, -v = only
Field numberingStarts at 1-f 1 = first field
Empty linespaste pads with tabsPre-filter blank lines
Range overlap-f 1-3,2 is redundantUse -f 1-3

Real-World Examples

1. Extract Usernames from /etc/passwd

$ cut -d ':' -f 1 /etc/passwd | head -5
root
daemon
bin
sys
sync

2. Extract Username and UID

$ cut -d ':' -f 1,3 /etc/passwd | head -5
root:0
daemon:1
bin:2
sys:3
sync:4

3. Build a CSV from Two Columns

$ paste -d ',' <(cut -d: -f1 /etc/passwd) <(cut -d: -f3 /etc/passwd) | head -5
root,0
daemon,1
bin,2
sys,3
sync,4

4. Add Line Numbers to a File

$ paste <(seq 1 5) file.txt
1	line one
2	line two
3	line three
4	line four
5	line five

5. Convert a Column into a Single Line

$ paste -s -d ',' data.txt
alice,bob,charlie,dave

6. Join Users with Their Scores

$ sort -k1,1 users.txt > u.sorted
$ sort -k1,1 scores.txt > s.sorted
$ join u.sorted s.sorted
alice 95
bob 87
charlie 92

7. Show Users Without Scores

$ join -v 1 u.sorted s.sorted
dave

8. Full Outer Join

$ join -a 1 -a 2 u.sorted s.sorted
alice 95
bob 87
charlie 92
dave
eve 78

9. Join with a Different Delimiter

$ join -t ',' f1.csv f2.csv
1,alice,95
2,bob,87
3,charlie,92

10. Extract Fixed-Width Columns

$ cat fixed.txt
2024-01-15T10:00:00 ERROR disk
2024-01-16T11:30:00 WARN cpu

$ cut -c 1-10,21-25 fixed.txt
2024-01-15ERROR
2024-01-16WARN

11. Merge Multiple Files Side by Side

$ paste name.txt age.txt city.txt
alice	30	Paris
bob	25	London
charlie	35	Berlin

12. Join and Format Output

$ join -o 1.1,1.2,2.2 file1.txt file2.txt
1 alice 95
2 bob 87
3 charlie 92

13. Filter Columns and Reorder

$ cut -d ':' -f 3,1 /etc/passwd | head -3
0:root
1:daemon
2:bin

14. Combine cut + sort + uniq

# Unique shells used on the system
$ cut -d ':' -f 7 /etc/passwd | sort -u
/bin/bash
/bin/sync
/usr/sbin/nologin

15. Build a Report from /etc/passwd

$ paste -d ',' \
    <(cut -d: -f1 /etc/passwd) \
    <(cut -d: -f3 /etc/passwd) \
    <(cut -d: -f7 /etc/passwd) | head -5
root,0,/bin/bash
daemon,1,/usr/sbin/nologin
bin,2,/usr/sbin/nologin
sys,3,/usr/sbin/nologin
sync,4,/bin/sync

Visual: cut vs paste vs join

┌──────────────────────────────────────────────┐
│                  cut                         │
│                                              │
│  Input:     Output (field 1):                │
│  a1 a2 a3   a1                               │
│  b1 b2 b3   b1                               │
│  c1 c2 c3   c1                               │
│                                              │
│  Extracts columns vertically                 │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│                 paste                        │
│                                              │
│  File A:   File B:     Output:               │
│  a1        b1          a1  b1                │
│  a2        b2          a2  b2                │
│  a3        b3          a3  b3                │
│                                              │
│  Merges files horizontally                   │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│                  join                        │
│                                              │
│  File A:      File B:     Output:            │
│  1 alice      1 95        1 alice 95         │
│  2 bob        2 87        2 bob 87           │
│  3 charlie    3 92        3 charlie 92       │
│                                              │
│  Matches rows on a shared key                │
│                                              │
└──────────────────────────────────────────────┘

Summary

CommandPurposeExample
cut -d ' ' -f 1 FILEFirst fieldcut -d ' ' -f 1 data.txt
cut -d ' ' -f 1,3 FILEFields 1 and 3cut -d ' ' -f 1,3 data.txt
cut -c 1-3 FILEFirst 3 charscut -c 1-3 example.txt
cut -d ':' -f 1 /etc/passwdColon fieldcut -d: -f1 /etc/passwd
cut -f 2 --complement FILEAll but field 2cut -d ' ' -f 2 --complement f
cut -d ' ' -f 1 -s FILESuppress no-delim linescut -d ' ' -f 1 -s f
paste A BMerge side by sidepaste data.txt example.txt
paste -d ',' A BMerge with commapaste -d ',' a.txt b.txt
paste -s AAll on one linepaste -s data.txt
paste A B > out.csvSave merged outputpaste a b > out.csv
join A BJoin on first fieldjoin file1.txt file2.txt
join -a 1 A BInclude unmatched from Ajoin -a 1 f1 f2
join -a 1 -a 2 A BInclude all unmatchedjoin -a 1 -a 2 f1 f2
join -v 1 A BOnly unmatched from Ajoin -v 1 f1 f2
join -t ',' A BCustom delimiterjoin -t ',' f1.csv f2.csv
join -i A BCase-insensitivejoin -i f1 f2
join -1 N -2 M A BJoin on different fieldsjoin -1 2 -2 2 f1 f2
join -o FMT A BCustom outputjoin -o 1.1,1.2,2.2 f1 f2
join -e STR -a 1 A BFill missing fieldsjoin -e N/A -a 1 f1 f2

Key takeaways:

  • cut extracts fields (-f) or characters (-c) from each line
  • Use -d to set the delimiter — default is TAB, not space
  • cut requires single-character delimiters — for multi-char, use awk -F
  • paste merges files side by side, TAB-separated by default
  • Use paste -d to choose a delimiter — perfect for building CSVs
  • Use paste -s to collapse a file into a single line
  • join combines two files on a shared key — like SQL’s JOIN
  • Always sort both files on the join key before joining
  • Use -a N to include unmatched lines, -v N for only unmatched
  • Use -t for a custom delimiter, -i for case-insensitive matching
  • Use -o to control the output fields and -e to fill in missing values
  • Combine all three with process substitution for powerful pipelines

Remember: cut is vertical — it slices columns. paste is horizontal — it stitches files together. join is relational — it matches rows. All three assume structured text, so know your delimiters. cut breaks on multi-char delimiters and quoted CSV fields — reach for awk when you need more power. join needs sorted input, just like uniq. And paste is your go-to for turning separate columns into a CSV. Master these three, and you can reshape tabular text without ever opening a spreadsheet.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!