Guides

cut command: fields, characters and real examples on Linux

The cut command prints fields (-d, -f), characters (-c) or bytes (-b) from each line. Ranges, --complement, log and /etc/passwd examples, and the CSV trap.

Published ~7 min read
On this page

Short answer: the cut command prints selected parts of every line. cut -d: -f1 /etc/passwd splits each line at : and prints the first field, -c1-10 prints characters 1 to 10, and -b works on bytes. Without -d the delimiter is a tab. Lists like 1,3 and ranges like 2- pick several fields.

any Linux
cut -d: -f1 /etc/passwd             # user names
cut -d' ' -f1 access.log            # client IPs from an nginx log
cut -c1-10 app.log                  # the date of ISO-timestamped lines
cut -d= -f2- .env                   # everything after the first =

Every example below was run on Debian 12 with GNU coreutils 9.1, the version Debian 12 ships, in a scratch directory against sample files; the output shown is copied from those runs.

cut cheat sheet

TaskCommand
First field, colon-separatedcut -d: -f1 file
Fields 1 and 7cut -d: -f1,7 file
Fields 1 to 3cut -d: -f1-3 file
Field 3 to the end of the linecut -d: -f3- file
Everything except field 2cut -d: -f2 --complement file
Join the output with another separatorcut -d: -f1,6 --output-delimiter=' ' file
Tab-separated input (the default)cut -f2 file.tsv
Skip lines that have no delimitercut -s -d: -f2 file
Characters 1 to 15cut -c1-15 file
Bytes instead of characterscut -b1-4 file
Read from a pipegetent passwd | cut -d: -f1

-d and -f: cutting fields

-d sets the delimiter, one character, and -f says which fields to print, counted from 1. /etc/passwd is the classic input: seven fields separated by colons (name, password placeholder, UID, GID, comment, home directory, shell).

any Linux
$ cut -d: -f1 /etc/passwd
root
daemon
www-data
sshd
deploy

$ cut -d: -f1,7 /etc/passwd
root:/bin/bash
daemon:/usr/sbin/nologin
www-data:/usr/sbin/nologin
sshd:/usr/sbin/nologin
deploy:/bin/bash

The selected fields are joined with the input delimiter again, so -f1,7 keeps the colon. A delimiter that the shell treats specially needs quoting: -d' ' for a space, -d, is fine as it is, and -d$'\t' spells out a tab in bash (though tab is the default anyway). For real systems, getent passwd | cut -d: -f1 is the better source, because it also includes accounts from LDAP or other directories; listing users on Linux explains the difference.

Order and duplicates: cut always keeps the input order

any Linux
$ cut -d: -f7,1 /etc/passwd | head -n 2
root:/bin/bash
daemon:/usr/sbin/nologin

Asking for field 7 before field 1 changes nothing: cut writes the selected fields in the order they appear in the line, each once, so -f1,1,1 prints field 1 a single time. To reorder or repeat columns, use awk -F: '{print $7, $1}', covered in the awk command guide.

Lists and ranges

WriteSelectsExample output on root:x:0:0:root:/root:/bin/bash
-f3Field 3 only0
-f1,3Fields 1 and 3root:0
-f1-3Fields 1 to 3root:x:0
-f6-Field 6 to the end/root:/bin/bash
-f-2From the start to field 2root:x
-f1,6-Combinedroot:/root:/bin/bash

The open range N- is the one to remember for values that may contain the delimiter themselves. In a KEY=value file, a password like s3cr=t loses everything after its own = with -f2, and comes out whole with -f2-:

any Linux
$ cat .env
DB_HOST=127.0.0.1
DB_PASS=s3cr=t

$ cut -d= -f2 .env
127.0.0.1
s3cr

$ cut -d= -f2- .env
127.0.0.1
s3cr=t

-c and -b: characters and bytes

-c selects by position instead of by delimiter, which suits fixed-width text. The classic syslog format starts every line with a 15-character timestamp, as in this sample auth.log:

any Linux
$ cut -c1-15 auth.log
Oct 21 09:14:02
Oct 21 09:20:45

$ cut -c17- auth.log
web1 sshd[812]: Accepted publickey for deploy from 203.0.113.7 port 52144 ssh2
web1 sshd[901]: Failed password for root from 192.0.2.41 port 40022 ssh2

Look at the first line before counting characters. rsyslog can also write ISO 8601 timestamps such as 2026-10-21T09:14:02.123456+00:00, and then cut -d' ' -f1 is the cleaner way to the timestamp. On the Debian 12 host these examples ran on, rsyslog is not installed and there is no /var/log/auth.log at all: SSH logins are in the systemd journal, which journalctl reads.

--complement: everything except

any Linux
$ cut -d: -f2 --complement /etc/passwd | head -n 2
root:0:0:root:/root:/bin/bash
daemon:1:1:daemon:/usr/sbin:/usr/sbin/nologin

$ echo '2026-10-21T09:14:02Z' | cut -c11 --complement
2026-10-2109:14:02Z

--complement inverts the selection, for fields, characters or bytes alike. It is shorter than listing every field you want to keep when you only want to drop one, such as a password or token column before sharing a file.

--output-delimiter: change the separator on the way out

any Linux
$ cut -d: -f1,6 --output-delimiter=' ' /etc/passwd
root /root
daemon /usr/sbin
www-data /var/www
sshd /run/sshd
deploy /home/deploy

The output delimiter can be any string, not only one character, and --output-delimiter=$'\t' in bash turns colon-separated input into tab-separated output. Both --complement and --output-delimiter are GNU extensions; the BSD cut on macOS does not have them, per its man page.

Tabs, -s, and lines without a delimiter

Two defaults surprise people. The default delimiter is a tab, not a space. And a line that does not contain the delimiter at all is printed whole, not skipped:

any Linux
$ printf 'alpha\tbeta\tgamma\nno tab here\n' > tabbed.txt
$ cut -f2 tabbed.txt
beta
no tab here

$ cut -s -f2 tabbed.txt
beta

-s (--only-delimited) drops those lines, which is what you want for input with comments or blank lines mixed in. A field number beyond the last field simply prints an empty line.

cut on log files

nginx and Apache write their access logs in the combined format, where the parts are separated by single spaces and quotes. Against a six-line sample log:

sample line
203.0.113.7 - - [21/Oct/2026:09:14:02 +0000] "GET / HTTP/1.1" 200 5120 "-" "Mozilla/5.0"
any Linux
$ cut -d' ' -f1 access.log | sort | uniq -c | sort -rn     # requests per IP
      3 203.0.113.7
      2 192.0.2.41
      1 198.51.100.23

$ cut -d' ' -f9 access.log | sort | uniq -c                # status codes
      3 200
      1 302
      2 404

$ grep '" 404 ' access.log | cut -d' ' -f7                 # paths that returned 404
/wp-login.php
/.env

$ cut -d: -f2 access.log | sort | uniq -c                  # requests per hour (IPv4 clients)
      3 09
      3 10

$ cut -d'"' -f2 access.log | head -n 2                     # the request line
GET / HTTP/1.1
GET /pricing HTTP/1.1

The per-hour count works because the first colon in the line is the one after the date; an IPv6 client address has colons of its own and shifts the field. Cutting at " is the robust way to reach the request and the user agent, because those can contain spaces. Two cuts in a row extract text between two different characters, for example the timestamp between the brackets: cut -d'[' -f2 access.log | cut -d']' -f1. To narrow the lines first, grep in front of cut is the usual pairing, as in the 404 example. Two more one-liners that come up on every server:

any Linux
sha256sum backup.tar.gz | cut -d' ' -f1     # just the hash, without the file name
echo "$PATH" | cut -d: -f1                   # the first directory in PATH

Columns padded with several spaces

cut treats every single delimiter as a field boundary, so two spaces in a row mean an empty field between them. Output aligned for humans, from df, ps or ls -l, therefore breaks it:

any Linux
$ cat df.txt
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/vda1      ext4   79G   31G   45G  41% /

$ cut -d' ' -f2 df.txt          # prints empty lines: field 2 is the empty gap

$ tr -s ' ' < df.txt | cut -d' ' -f6
Use%
41%

tr -s ' ' squeezes runs of spaces into one, after which cut works. That still fails on right-aligned numbers: a line that starts with spaces gets an empty field 1, so the field numbers shift between lines.

any Linux
$ cat ps.txt
  812 sshd
12345 node

$ tr -s ' ' < ps.txt | cut -d' ' -f2
812
node

$ awk '{print $2}' ps.txt
sshd
node

awk splits on runs of blanks and ignores leading ones, so it gets this right without any preparation. For anything aligned with spaces, awk is the better tool.

The CSV caveat: quoted fields

cut -d, -f3 looks like the obvious way to read a CSV column, and it works until a value contains a comma inside quotes, which real CSV allows:

any Linux
$ cat users.csv
id,name,city,plan
1,Anna,Amsterdam,Standard KVM 2
2,"Smith, John",Frankfurt,Hi-CPU KVM 1
3,"O'Neil ""Jr""",Amsterdam,Storage VPS

$ cut -d, -f3 users.csv
city
Amsterdam
 John"
Amsterdam

Row 2 returns half a name instead of the city, with no error. cut knows nothing about quoting, so use a tool that does. Python’s csv module is on almost every server; gawk’s FPAT handles simple quoted fields too:

any Linux
$ python3 -c 'import csv,sys; [print(r[2]) for r in csv.reader(sys.stdin)]' < users.csv
city
Amsterdam
Frankfurt
Amsterdam

$ gawk -v FPAT='([^,]*)|("[^"]*")' '{print $3}' users.csv
city
Amsterdam
Frankfurt
Amsterdam

Dedicated CSV tools such as Miller (mlr) or csvkit do the same for whole pipelines. Files exported on Windows have one more trap: lines end in \r\n, so the last field keeps an invisible carriage return (cat -A shows it as ^M). tr -d '\r' < file.csv | cut -d, -f2 removes it first. To skip a header row, tail -n +2 file.csv | cut -d, -f1.

cut vs awk vs tr

ToolBest atFalls short when
cutFields split by one exact character (:, =, tab, comma without quotes), fixed character positionsColumns padded with several spaces, quoted CSV, reordering columns
awkWhitespace-aligned columns, reordering, conditions on a field, sums and countsQuoted CSV (gawk FPAT helps), JSON
trReplacing or deleting single characters: tr ':' '\n', tr -s ' ', tr -d '\r'Selecting fields: it never splits a line into columns
sedEditing text in place: substitute, delete or insert linesArithmetic or per-column logic

In practice they work together: tr cleans the input, cut picks the field, sort | uniq -c counts. When a job needs a condition on a column or a total, switch to awk; when it needs a file edited in place, the sed command is the tool.

Common cut errors

Message (GNU coreutils 9.1)CauseFix
you must specify a list of bytes, characters, or fieldsNo -f, -c or -b givenAdd -f with a field list
the delimiter must be a single characterA multi-character -d such as -d '::' or -d ', 'Use awk -F for multi-character separators
fields are numbered from 1-f0Fields start at 1
invalid decreasing range-f3-1Write ranges low to high: -f1-3
only one list may be specified-f and -c togetherPick one mode, or pipe two cuts
an input delimiter may be specified only when operating on fields-d together with -c or -bDrop -d, or use -f

cut on a server

  • It is always there. cut is part of coreutils, so every Debian, Ubuntu and Rocky Linux install has it, including minimal images and containers based on them.
  • It is fast on big logs. It does no regex matching and keeps no state, so a cut | sort | uniq -c pipeline over a large access log is mostly the cost of sort.
  • Rotated logs work too. zcat access.log.2.gz | cut -d' ' -f1 reads a compressed rotation without unpacking it to disk.

Frequently asked

How do I get one column with the cut command?

Give the delimiter with -d and the field number with -f: cut -d: -f1 /etc/passwd prints the first colon-separated field of every line. Fields count from 1, -f1,3 selects two fields, -f2- selects field 2 to the end of the line, and without -d the delimiter is a tab.

Why does cut -d' ' print empty lines?

The columns are separated by several spaces, and cut treats every single space as a delimiter, so the field you asked for is an empty gap between two spaces. Squeeze the spaces first with tr -s ' ', or use awk '{print $2}', which splits on runs of whitespace and ignores leading blanks.

Can cut change the order of fields?

No. cut always prints the selected fields in the order they appear in the input, each one once, so -f3,1 gives the same output as -f1,3. Use awk -F: '{print $3, $1}' to reorder or repeat fields.

Can I use cut on CSV files?

Only on simple CSV without quoted fields. A value such as "Smith, John" contains the delimiter, and cut -d, splits it in two without any warning. Use a CSV-aware tool instead: Python's csv module, gawk with FPAT, Miller or csvkit.

Related articles

All guides