Skip to content
Linux Basics: Convert Tabs and Spaces with expand and unexpand

Linux Basics: Convert Tabs and Spaces with expand and unexpand

October 24, 2026

A tab advances to the next tab stop; it does not always mean a fixed number of spaces. GNU expand and unexpand let you make that convention explicit when preparing plain-text reports or checking indentation changes. Work on copies because changing whitespace can also change meaning.

These examples were tested on Linux with GNU Bash 5.2.37 and GNU coreutils 9.7. They use ASCII fixtures with four-column tab stops. They are not a recommendation to rewrite all whitespace in a codebase.

Prepare a scratch directory

You need Bash, GNU expand, unexpand, and cat, plus cmp, mktemp, rm, rmdir, and a text editor. Keep using the same terminal. Save each named source file here and run it once in order. No administrator access is needed.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Expand to tab stops, not fixed replacements

Save this as expand-tab-stops.sh:

#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C
printf 'a\t1\nabc\t2\nabcd\t3\n' > tab-columns.txt
expand --tabs=4 tab-columns.txt > space-columns.txt
cat -A space-columns.txt

Run it:

bash expand-tab-stops.sh

Output:

a   1$
abc 2$
abcd    3$

The three tabs became three, one, and four spaces respectively. After a, three spaces reach the next stop; after abc, one suffices. After abcd, the next stop is another four positions away. Replacing every tab with exactly four spaces would produce different alignment.

The dollar signs come from cat -A, which marks line endings for inspection. They are not stored in space-columns.txt. GNU expand defaults to eight-column stops; this script deliberately selects four. See the expand manual.

2. Change indentation but leave internal tabs alone

Save this as expand-leading-only.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
printf '\tname\tvalue\n' > mixed-tabs.txt
expand --initial --tabs=4 mixed-tabs.txt > leading-spaces.txt
cat -A leading-spaces.txt

Run it:

bash expand-leading-only.sh

Output:

    name^Ivalue$

The leading tab became four spaces. The tab between name and value remains a tab, shown by cat -A as ^I. The literal file does not contain a caret and an I at that position.

--initial limits expansion to tabs before the first nonblank character on each line. That is useful when a text format uses indentation for appearance but retains tabs between fields. It still changes the leading bytes, so it is not safe for every format.

3. Convert only indentation back to tabs

Save this as unexpand-leading-only.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
printf '    key    value\n' > indentation-spaces.txt
unexpand --tabs=4 --first-only indentation-spaces.txt > indentation-tabs.txt
cat -A indentation-tabs.txt
expand --tabs=4 indentation-tabs.txt > indentation-restored.txt
cmp -s indentation-spaces.txt indentation-restored.txt
printf 'Round trip matches: yes\n'

Run it:

bash unexpand-leading-only.sh

Output:

^Ikey    value$
Round trip matches: yes

The leading four spaces became one tab; the four spaces after key stayed intact. The comparison checks the expanded result against the original fixture byte for byte. If that comparison failed, set -e would stop before the success message.

There is an important GNU detail: supplying unexpand --tabs also enables conversion beyond indentation. Add --first-only when leading whitespace is the only intended target. The unexpand manual documents this interaction.

Keep in mind

The demonstrated round trip proves equality for this fixture, not a universal reversal. A file that originally mixed tabs and spaces can expand to the same visual layout as a differently encoded file. Expansion loses that distinction.

Never use an input file as its own redirected output. The shell can truncate it before the command reads it. The examples intentionally use different input and output names so you can compare before replacing anything.

Tabs may be semantically significant in Make recipes and tab-separated data. Indentation also matters in some programming languages. Check the target format, editor settings, and project conventions first. These ASCII examples do not establish display-width behavior for arbitrary Unicode text or terminal control sequences.

Clean up

Remove only these practice files, then return to your original directory:

rm -- expand-tab-stops.sh expand-leading-only.sh unexpand-leading-only.sh \
  tab-columns.txt space-columns.txt mixed-tabs.txt leading-spaces.txt \
  indentation-spaces.txt indentation-tabs.txt indentation-restored.txt
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on