When reviewing source code, examining system logs, or preparing text files for documentation, having clear line numbers is invaluable for pinpointing specific references. While many Linux users rely on cat -n to prepend simple numbers to lines, the basic cat utility offers little flexibility: it numbers every single line sequentially, including blank spaces, without any options for formatting, justification, or numbering increments.
The standard Unix/Linux nl (Number Lines) utility provides a far more powerful and customisable solution. With nl, you can control line number formatting, choose whether to number empty lines, set custom delimiters, specify numbering increments, and even partition documents into distinct logical sections. In this guide, we explore how to master the nl command in the Linux terminal.
In This Guide
- Why Use nl Instead of cat -n
- Basic Syntax and Default Behaviour
- Numbering Non-Empty vs All Lines (-b Option)
- Setting Custom Delimiters and Separators (-s Option)
- Controlling Number Justification and Leading Zeros (-n Option)
- Customising Field Width and Step Increments (-w and -i Options)
- Specifying Starting Line Numbers (-v Option)
- Using nl in Shell Pipelines
Why Use nl Instead of cat -n
While cat -n is convenient for quick inspections, it treats every byte sequence equally. In contrast, nl was intentionally designed as a text filter with advanced formatting capabilities:
- Intelligent Blank Line Handling: By default,
nlnumbers only non-empty lines, keeping clean whitespace intact without cluttering empty rows with meaningless digits. - Custom Alignment: You can right-justify, left-justify, or pad numbers with leading zeros (e.g.
001,002,003). - Custom Separators: Replace default tab spacing with colons, arrows, or custom brackets between the line number and text content.
- Logical Document Sections: Supports page headers, body sections, and footers with independent numbering logic.
Basic Syntax and Default Behaviour
The primary syntax of the nl command is as follows:
nl [options] [file...]
When executed without arguments, nl reads the target file and prints only non-empty lines with right-justified line numbers separated by a tab character:
nl script.sh
Notice that empty lines in the file remain completely blank without receiving a number. This clean presentation makes scanning code functions significantly easier.
Numbering Non-Empty vs All Lines (-b Option)
The -b (body numbering) flag controls which lines are assigned a number:
- Number non-empty lines only (default):
(Thenl -b t filename.txttstands for “translatable” non-empty lines.) - Number all lines including blanks:
This behaves similarly tonl -b a filename.txtcat -n, numbering every line sequentially regardless of content. - Do not number any lines in the body:
Suppresses numbering entirely for that section.nl -b n filename.txt - Number only lines matching a Regular Expression:
This command numbers only lines that begin with “ERROR”, leaving other lines unnumbered.nl -b p'^ERROR' application.log
Setting Custom Delimiters and Separators (-s Option)
By default, nl places a tab character between the line number and the line’s content. If you are generating Markdown tables, CSV extracts, or formatted terminal outputs, you can define a custom delimiter using the -s option:
To separate numbers with a colon and a space:
nl -s ": " source_code.c
To separate numbers with an arrow:
nl -s " -> " notes.txt
Controlling Number Justification and Leading Zeros (-n Option)
The -n option governs the visual format and alignment of the line numbers:
- Right-justified without leading zeros (default):
nl -n rn document.txt - Left-justified without leading zeros:
Aligns the digits against the far-left margin.nl -n ln document.txt - Right-justified with leading zeros:
Produces numbers formatted likenl -n rz document.txt000001,000002, which is ideal for maintaining fixed-width columnar alignment in data pipelines.
Customising Field Width and Step Increments (-w and -i Options)
By default, nl reserves a field width of 6 characters for numbers. For shorter scripts or compact outputs, you can narrow this field width using -w:
nl -w 3 -n rz -s " | " checklist.txt
This produces output formatted cleanly as: 001 | Item one, 002 | Item two.
If you need line numbers to increment in steps of 5 or 10 (common in legacy programming language lines or test routines), use the -i (increment) flag:
nl -i 10 -s ": " basic_code.txt
The lines will be numbered 1, 11, 21, 31, and so forth.
Specifying Starting Line Numbers (-v Option)
When processing partitioned files or continuing line counts from an earlier segment, use the -v flag to define an arbitrary starting number:
nl -v 100 chapter2.txt
The first line of the file will begin with number 100.
Using nl in Shell Pipelines
Like all standard Unix text filters, nl reads from standard input when no file operand is supplied, allowing seamless integration into shell pipelines:
To number active listening network ports:
ss -tulpn | nl -w 2 -s ". "
To number and inspect unique logged user logins:
last | cut -d' ' -f1 | sort | uniq | nl -w 3 -s ") "