Converting between tab- and space-separated text is a routine pipeline task: log fields, CSV-ish exports, and Makefile recipes (where a leading tab still matters). Turning tabs into spaces is usually expand; turning repeated spaces into tabs is unexpand. For tab literals in Bash, use $'\t' or printf '\t' instead of pasting a literal tab character.
Tested on Ubuntu 25.04, GNU coreutils
expand/unexpand9.5, Bash 5.2.37.
Linux convert tabs to spaces (expand)
expand reads text and replaces tab characters with spaces so that tab stops line up at fixed columns (default often 8; override with -t).
printf '%s\n' $'name\tvalue\nfoo\tbar' | expand -t 8 | cat -Aname value$
foo bar$cat -A shows ^I for a tab and plain spaces otherwise—handy when verifying conversion in a script:
expand -t 4 input.txt > output.txtPipe instead of files when you only need a one-off conversion step in a larger pipeline.
Convert spaces to tabs (unexpand, sed, tr)
unexpand (GNU coreutils)
unexpand merges leading spaces (and optionally other blanks) into tabs.
printf ' line with 4 leading spaces\n' | unexpand -t 4 --first-only | cat -A^Iline with 4 leading spaces$Flags worth reading in man unexpand: -a (all blanks on the line, not only leading), tab width -t.
One-line sed / tr for simple space runs
Collapse multiple spaces and turn separators into tabs when the line is predictable:
printf '%s\n' 'col1 col2 col3' | tr -s ' ' | sed 's/ /\t/g' | cat -Acol1^Icol2^Icol3$For heavier field logic, awk is often clearer—see awk examples.
Tab in bash ($'\t', printf, heredocs)
Tab literals in Bash:
printf 'A%sB\n' $'\t' | cat -A
printf 'A\tB\n' | cat -AA^IB$
A^IB$In a <<EOF heredoc, a real tab character is a tab; many style guides prefer <<'EOF' when you do not want expansions, which also freezes tabs literally. Avoid invisible tabs in conditionals—they are easy to miscount in review.
Aligned columns: prefer explicit widths over fragile tab math
Older scripts sometimes measured string length with wc -c and expr to decide how many spaces to print. That breaks on multi-byte characters and is harder to read than printf field widths:
printf '%-12s : %s\n' "col1" "col2" "col3" "col4"col1 : col2
col3 : col4If you truly need tab stops between dynamic fields, build the line with $'\t' between segments, then run expand -t n once for consistent viewing.
Summary
Use expand to convert tabs to spaces (tune stops with -t). Converting runs of spaces to tabs leans on unexpand or small sed / tr filters when the layout is simple. Tab literals in Bash are $'\t' or printf '\t'; verify with cat -A. For human-readable tables in scripts, fixed-width printf is usually more robust than hand-tuned tab padding. GNU details live in man expand and man unexpand.
Further reading: GNU expand manual, GNU unexpand manual. For timed loops that sometimes format output, see bash while loop until a specific time.

