str_pad() in Bash: Pad a String to a Fixed Length
PHP's str_pad() pads a string to a given length, on the right, the left or both sides, with any pad string you like. In Bash, printf covers the common cases (space padding and zero-padded numbers) in one call. For the rest, especially centring and custom pad strings, here is a function that gives the same results as PHP.
I use padding whenever a script prints a report: aligned columns of hostnames and load averages, sequence numbers like backup-007, or a centred banner in a log file.
The function
# str_pad() -- pad a string to a given length
# Usage: str_pad string length [pad_string] [pad_type]
# pad_type: STR_PAD_RIGHT (default), STR_PAD_LEFT or STR_PAD_BOTH
str_pad() {
[ $# -ge 2 ] && [ $# -le 4 ] || return 1
local input="$1" length="$2" pad="${3- }" type="${4:-STR_PAD_RIGHT}"
[[ $length =~ ^[0-9]+$ ]] && [ -n "$pad" ] || return 1
local need=$(( 10#$length - ${#input} )) left=0 right=0
if (( need <= 0 )); then
printf '%s\n' "$input"
return 0
fi
case $type in
STR_PAD_RIGHT) right=$need ;;
STR_PAD_LEFT) left=$need ;;
STR_PAD_BOTH) left=$(( need / 2 )); right=$(( need - left )) ;;
*) return 1 ;;
esac
local fill="$pad"
while (( ${#fill} < need )); do
fill+="$fill"
done
printf '%s%s%s\n' "${fill:0:left}" "$input" "${fill:0:right}"
}The arguments follow PHP: the string, the target length, an optional pad string (default a single space) and an optional pad type (default STR_PAD_RIGHT). It returns 1 for a missing or non-numeric length, an empty pad string or an unknown pad type.
How it works
need is how many characters are missing: the target length minus ${#input}, the length of the string. If that is zero or negative, the string is already long enough and is returned unchanged, exactly as PHP does. str_pad never truncates. The 10# prefix forces base 10, so a length written as 08 is not rejected as an invalid octal number.
The case splits need between left and right. For STR_PAD_BOTH the left side gets the smaller half and the right side gets any odd character, which matches PHP. Then the pad string is doubled until it is at least need characters long, and ${fill:0:n} takes the first n characters for each side. Both sides start from the beginning of the pad string, again like PHP, so a pad of -=+ gives -=+ on the left and -=+- on the right.
Usage
$ str_pad "Alien" 10 | cat -A
Alien $
$ str_pad "Alien" 10 "-=" STR_PAD_LEFT
-=-=-Alien
$ str_pad "Alien" 10 "_" STR_PAD_BOTH
__Alien___
$ str_pad "Alien" 6 "___"
Alien_
$ str_pad "Alien" 3 "*"
Alien
$ str_pad "Alien" 12 "-=+" STR_PAD_BOTH
-=+Alien-=+-
$ str_pad "42" 6 "0" STR_PAD_LEFT
000042
$ str_pad "Alien" 10 "x" STR_PAD_MIDDLE; echo "rc=$?"
rc=1The first five are the examples from the PHP manual. I ran the same inputs through PHP 8.1's str_pad() and got identical output for every one. cat -A on the first line just makes the trailing spaces visible.
When printf is enough
For space padding, printf does it directly with a field width. A minus sign means left-aligned, which is padding on the right:
printf '[%-10s]\n' "left"
printf '[%10s]\n' "right"
printf '[%.3s]\n' "truncate"
printf '[%*s]\n' 8 "star"[left ]
[ right]
[tru]
[ star]A precision (%.3s) truncates, which str_pad never does, and combining the two gives a fixed-width column: %-10.10s is always exactly ten characters. * takes the width from the next argument, so you do not need to build the format string from a variable. This is the cleanest way to print a table:
printf '%-10s %8s\n' "Host" "Load"
printf '%-10s %8.2f\n' "web01" 0.5 "db01" 12.25Host Load
web01 0.50
db01 12.25The format is reused for each pair of arguments, so one printf prints all the rows.
Zero-padding numbers
For numbers, %05d pads with zeros to five digits and puts the minus sign in front:
$ printf '[%05d]\n' 42 -42
[00042]
[-0042]
$ printf '%05d\n' 08
bash: printf: 08: invalid octal number
00000
$ printf '%05d\n' $((10#08))
00008The middle example is a common mistake. %d follows C rules, so a leading zero means octal, and 08 and 09 are not valid octal numbers. This causes problems when you take a day or month from date +%d and do arithmetic on it. Strip the base with $((10#$n)) first. Also note that the 0 flag only works with numeric conversions: printf '%05s' 42 pads with spaces, not zeros. For a string that may not be a number, use str_pad "$id" 5 0 STR_PAD_LEFT.
Padding with a custom character
printf always pads with spaces, but you can pad with spaces and then replace them. This draws a 20-character rule without a loop:
printf -v line '%*s' 20 ''
printf '%s\n' "${line// /-}"--------------------That works for a run of pad characters on its own. To pad an actual string this way, you would also replace the spaces inside the string, so for anything else use str_pad, or str_repeat() to build the fill.
Multibyte text and width
This is where Bash and PHP differ, and where table columns stop lining up. ${#input} counts characters in a UTF-8 locale, but PHP's str_pad() counts bytes, and Bash's printf field width also counts bytes:
$ str_pad "café" 8 "."
café....
$ LC_ALL=C bash -c '. ./sp.sh; str_pad "café" 8 "."'
café...
$ printf '[%-8s]\n' "café" "Alien"
[café ]
[Alien ]The é is two bytes in UTF-8. In en_GB.UTF-8, str_pad sees four characters and adds four dots. In the C locale it sees five bytes and adds three, which is what PHP 8.1 printed. printf added only three spaces, so the column is one short. I got the same byte-counting from printf on Bash 4.4 and Bash 5.2.
Characters are not the same as screen columns either. CJK characters usually take two columns on a terminal, so "日本" is two characters, six bytes and four columns wide. No pure-Bash method knows the display width. GNU wc -L reports it (printf '%s\n' "日本" | wc -L gives 4) if you really need to align such text. For ASCII data, which is most of what sysadmin scripts print, none of this matters.
Pitfalls
- Length is a total, not an amount to add.
str_pad "abc" 5adds two characters, not five. - No truncation. Longer input comes back untouched. Use
${s:0:n}or%.nsif you need a hard limit. - Bash 3.2: nothing here uses Bash 4 features (
printf -vis from 3.1), so the stock macOS shell should be fine. I only tested on 4.4 and 5.2.
The PHP original is documented at php.net/str_pad. The Bash printf builtin is in the Bash builtins chapter.
Related functions
- str_repeat(): repeat a string n times
- trim(): remove padding again
- strlen(): string length and the UTF-8 question
- is_num(): validate a number before zero-padding it
- PHP string functions in Bash: the full index