Number Files Precisely with nl
You will finish with a repeatable way to add line numbers to a file, include or skip blank lines, and format the result for a review or script. The examples use GNU nl from coreutils 9.4, installed on this machine. nl reads its input and writes numbered text; it does not edit the source file.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell and a readable text file. The normal examples need no elevated privileges. Do not use sudo unless the file permissions genuinely require it, and do not redirect output over the original file while experimenting.
1. Check the installed command
Confirm the version before relying on details in a script:
nl --version
Expected output begins with:
nl (GNU coreutils) 9.4
Version differences matter when you are comparing output from another host. The behaviour below is based on the local manual page and this installed command.
2. Number a file without changing it
Pass one or more files as arguments. With no file, or with -, nl reads standard input:
nl README.txt
The default is easy to miss: non-empty lines are numbered, while blank lines remain unnumbered. The number is right-justified in six columns and is followed by a tab:
1 First line
2 Second line
3 Third line
The visible gap before each number is padding, not part of the original file. Check that the source was not altered by comparing its checksum before and after a run:
sha256sum README.txt
nl README.txt > /tmp/README.numbered
sha256sum README.txt
The two checksums for README.txt should match. The output file under /tmp is disposable; the source remains in place.
3. Choose whether blank lines receive numbers
Use -b or --body-numbering with one of four styles. The most useful alternatives are t, the default, and a, every line:
nl -ba notes.txt
For a file containing alpha, a blank line, then beta, the result is:
1 alpha
2
3 beta
Use -b n to number no body lines, leaving the file text visible but removing body numbers. Use -b pBRE to number only non-empty lines matching a basic regular expression. For example, number lines beginning with a capital letter:
nl -b 'p^[A-Z]' notes.txt
That expression is a basic regular expression, not a shell glob. Quote it so the shell passes the pattern to nl unchanged. A common trap is assuming -b a means "all lines in every part of a logical page". It controls body lines; headers and footers have separate styles.
4. Make numbering suitable for a report
Several small options control the number sequence and its appearance:
nl -ba -v 100 -i 10 -w 4 -n rz -s ': ' notes.txt
This starts at 100, increases by 10, reserves four columns, pads with zeroes, and places a colon and space after each number. The beginning of the output is therefore:
0100: alpha
0110:
0120: beta
The -n formats are ln for left-justified numbers, rn for right-justified numbers without leading zeroes, and rz for right-justified numbers with leading zeroes. -s changes the separator, while -w changes the number width. If the sequence grows beyond the selected width, the number can occupy more columns, so choose a width that covers the largest expected value.
5. Understand sections and resets
nl recognises logical page sections using delimiter lines. By default the delimiter is \: in the input, meaning a line containing two backslashes followed by a colon identifies a section boundary. The header, body and footer styles can number the parts of that section:
nl -h a -b a -f a report.txt
Most ordinary text files do not contain these delimiters, so their section behaviour is usually invisible. When sections are present, the default numbering restarts at 1 for each section. Add -p or --no-renumber to keep the sequence moving across sections:
nl -p report.txt
Do not add section options simply to number a normal file. First inspect the input if numbering appears to restart unexpectedly. You can change the delimiter with -d CC; an omitted second delimiter character implies a colon. The GNU extension -d '' disables section matching entirely.
6. Use standard input in a pipeline
A hyphen makes the input boundary explicit and lets another command produce the text:
printf '%s\n' 'ready' '' 'done' | nl -ba
Expected output is:
1 ready
2
3 done
For a script, preserve the exit status and avoid parsing padded columns unless the format is part of your interface. If another tool needs a stable delimiter, choose one deliberately, for example -s ': '. Do not treat line numbers as permanent identifiers: inserting or removing a line changes every number after it.
7. Recover from common mistakes
If the output looks unchanged, check whether you selected -b n or a pattern that matches nothing. If blank lines are missing from the sequence, use -b a. If numbering restarts, inspect for section delimiters and try -p. If the command says it cannot open a file, check the path and permissions without changing them:
test -r notes.txt && echo 'readable' || echo 'not readable'
Reading a protected file with sudo nl /path/to/file may be appropriate for a one-off inspection, but keep the output destination writable by your normal account. Never use a privileged redirection such as sudo nl file > file as an editing method: the shell opens the destination before sudo runs, and the command would still not provide an in-place update.
There is no undo operation because nl does not modify its input. If you created a temporary numbered file, remove that specific file after checking it, for example rm -- /tmp/README.numbered. Confirm the path before removing anything.
Done means
- You confirmed the local GNU coreutils version.
- You numbered a file or pipeline without changing the source.
- You selected the correct body style for blank and matching lines.
- You can control starting value, increment, width, format and separator.
- You know when section resets apply and how
-pchanges them.