Skip to content
shellhacks.dev
Bash

How to Pad a String With Zeros or Spaces in Bash Output

Pad a string in Bash with printf width flags for zeros and spaces, left or right alignment, dynamic widths, and a note on why padding multibyte text drifts.

6 min read
Summarize in AI
Illustration for "How to Pad a String With Zeros or Spaces in Bash Output": a terminal session, a scrolling log, a disk usage gauge.

Fixed-width output is what makes a log, a report or a file name sort correctly: file-9 sorts after file-10, but file-09 does not. Bash does this with printf, the same tool covered in the variables guide for formatting values. The commands assume Bash 5.2.

Zero padding numbers

Shell
printf '%05d\n' 42
printf '%03d\n' 7 12 123
n=9
name=$(printf 'backup-%03d.tar' "$n")
echo "$name"
Text
00042
007
012
123
backup-009.tar

The format is reused for each extra argument, which is handy for padding a list. Assigning with $(printf ...) costs a subprocess, so on Bash 3.1 and later use printf -v name 'backup-%03d.tar' "$n" to write straight into a variable.

Padding with spaces

Shell
printf '[%-10s]\n' "left"
printf '[%10s]\n' "right"
printf '%-12s %8s\n' "NAME" "SIZE"
printf '%-12s %8d\n' "app.log" 2048
Text
[left      ]
[     right]
NAME           SIZE
app.log        2048

A leading - left-aligns, and the number is the field width. Text longer than the width is not truncated, so a column can overflow; add a precision to cut it: %-10.10s pads and truncates to ten characters.

A width from a variable

Shell
width=15
printf '%*s|\n' "$width" "padded"
printf '%-*s|\n' "$width" "padded"
printf '%0*d\n' 6 42
Text
         padded|
padded         |
000042

The * consumes an argument as the width, which lets you compute it from the longest value in a list with ${#var}. That pattern is how a script builds a table whose columns fit its data, in the spirit of cut, paste and column.

printf formats for padding

The printf builtin handles padding through width and flags in the format. %5s right-aligns a string in a field of five characters, %-5s left-aligns it, and %05d pads a number with zeros. Combine with precision to truncate: %.10s cuts a string at ten characters, and %-10.10s gives an exact width, padding short strings and trimming long ones. This is the best tool for building aligned tables and fixed-width records, and it runs without a subprocess. The format is reused if there are more arguments than conversions, so one format string can print a whole list as rows.

The width counts bytes or characters depending on the implementation and locale. The Bash builtin counts characters in a UTF-8 locale, while an external /usr/bin/printf may count bytes, so accented text aligns differently. Test with non-ASCII data if your output includes it.

Numbers that look like octal

A leading zero makes Bash arithmetic treat a number as octal, so 08 is an error and 010 equals eight. This hits scripts that pad a number with zeros and then do arithmetic on it, as with months and days read from date output. Strip the zeros first, or force base ten with $((10#$n)). Printing a padded number from an unpadded integer is safe: printf '%02d' "$n" takes the decimal value and pads it. The trap appears when padded text goes back into arithmetic, so keep the numeric value in one variable and the display form in another.

Zero padding is also the standard way to make file names sort in numeric order: frame-007.png sorts before frame-010.png in a plain listing, which would not happen with unpadded names. Choose the width for the largest number you expect, with room to spare.

Padding with other characters and widths from variables

Pad with a character other than a space by generating the padding separately. A pattern is to print spaces with printf '%*s' "$n" '' and replace them using parameter expansion, ${pad// /.}, giving dots. The asterisk in a format takes the width from an argument, so printf '%*s' "$width" "$text" aligns text to a width held in a variable, and a negative width left-aligns. To centre text, compute the left padding as half the difference between the width and the length, then print that many spaces before the text. Find the longest item first with a loop and ${#item}, so every column fits.

When a table is for people to read in a terminal, the column -t command may be simpler than computing widths in the script, as long as the data has no embedded spaces.

Trimming and cutting to length

The reverse operation, shortening, uses substring expansion: ${text:0:20} takes the first twenty characters, and adding an ellipsis only when the text was longer keeps output tidy. Remove leading and trailing whitespace with parameter expansion patterns under extglob, or with xargs for a quick trim. In fixed-width files, such as legacy exports, build each record by padding every field to its declared width and concatenating, then verify the total length with ${#record}. A small test with a value that is too long, one that is empty and one that contains a multibyte character catches the usual mistakes. Wrap the logic in a function that takes the text and width, so alignment rules live in one place and a change to the format is a single edit.

Where the output feeds a machine, not a person, avoid decoration. Padding with spaces in a CSV changes the data, and the receiving program may read the spaces as part of the value. Pad only for display, and keep a separate unpadded copy for processing. For reports sent by email, use a monospaced block so the columns line up in the reader's client, and check the result in more than one client. Alignment that looks right in a terminal can be ruined by a proportional font.

Where this does not apply

Zero-padded numbers are text, and arithmetic treats a leading zero as octal: echo $((08)) fails with "value too great for base". Strip the padding or force base ten with $((10#08)) before computing. Multibyte characters can pad wrongly, since some printf implementations count bytes: a column holding é may end one space short. For aligned output with unknown content, column -t is more reliable. The Bash builtins reference documents printf -v and the format flags.

For a sequence of files, combine padding with a loop: for i in {1..12}; do printf -v n '%02d' "$i"; touch "log-$n.txt"; done creates log-01.txt to log-12.txt, which sort in the right order in ls. Without padding, log-10.txt lands between log-1.txt and log-2.txt, and anything that globs the directory processes them out of sequence. Pick the width from the largest number you expect, and leave one digit of headroom. See the Bash manual on arithmetic for the octal rule.

For a column in a report, compute the width first: loop over the values, keep the largest ${#v}, then reuse it with %-*s. That is how string length and padding combine into a table that fits its data, and it removes the guesswork of hardcoded widths that overflow the first time a long name appears. Zero padding follows the same rule, so a width of three covers 999 items before it silently grows to four.

Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.