How to Convert Between Tabs Spaces and Commas the Right Way
Convert tabs, spaces and commas between delimiters with tr, sed, awk, expand and unexpand, and see which methods break on quoted fields or repeated spaces.

Changing a delimiter looks trivial, and it is, until the data contains the new delimiter or the old one comes in runs. Choosing the tool by what the data looks like avoids corrupt output. The examples use GNU coreutils 9.4, gawk 5.3 and sed 4.9; for real CSV, see extracting CSV columns.
Tabs to commas and back
printf 'id\tname\n1\tAlice\n' | tr '\t' ','
printf 'id,name\n1,Alice\n' | tr ',' '\t'
sed 's/\t/,/g' data.tsvid,name
1,Alice
tr replaces one byte with another and is the fastest tool for the job. The caution: if a tab-separated field contains a comma, the result has one more column than the header, and nothing says so. Check first with grep -c ',' data.tsv, or count fields per line with awk -F'\t' '{print NF}' data.tsv | sort | uniq -c. GNU sed understands \t; BSD sed on macOS does not, and needs $'\t' from the shell, covered in GNU versus BSD commands.
Runs of spaces to a single delimiter
ps aux | head -3 | tr -s ' ' | cut -d' ' -f1,2,11-
ps aux | head -3 | awk -v OFS=, '{$1=$1; print}'tr -s ' ' squeezes each run of spaces into one, so columns align for cut. Setting $1=$1 in awk makes it rebuild the line with the output separator OFS, which also squeezes whitespace. Both treat spaces inside values as separators, so a command column containing spaces splits into many fields, which is why they suit tidy columnar output, not free text. For fixed-width columns, use field positions, as in fixed-width files with awk.
Indentation: tabs and spaces
expand -t 4 Makefile.orig > spaces.txt
unexpand -t 4 --first-only spaces.txt > tabs.txt
git ls-files '*.sh' | xargs sed -i 's/\t/ /g'expand converts each tab to the spaces needed to reach the next tab stop, which keeps alignment, while a plain sed replacement does not. unexpand --first-only converts only leading spaces, which avoids touching spaces inside text. Makefiles require real tabs in recipes, so converting those to spaces breaks them with the error "missing separator". Python is sensitive to mixing the two inside one block.
Other direction: commas to spaces or aligned columns
column -s, -t data.csv
tr ',' ' ' < data.csvcolumn -t -s, aligns a delimited file into readable columns for the terminal; it does not change the file. Use it for viewing, and a converter for data that goes elsewhere.
What goes wrong with each tool
Every converter makes a different assumption about the data, and the failure is silent when the assumption is false. tr works on single bytes with no idea of fields, so it cannot tell a delimiter from a character inside a value. sed works on lines and patterns, which allows a restricted conversion, such as replacing only the first tab or only tabs after the third column, but still has no notion of quotes. awk splits each line into fields with a separator you choose, and rejoining with another output separator gives a clean conversion of well-formed data. A real CSV parser, such as the one in csvkit, Miller or Python's csv module, knows about quoting rules, escaped quotes and embedded newlines, and it is the only category that handles every legal file.
The decision is therefore about the data, not the command. If every field is a number or a short token with no punctuation, tr is correct and fast. If some fields are free text, check whether any contains the delimiter. If they might, or if the file came from a spreadsheet export, use a parser and stop worrying about it.
Counting before converting
A cheap validation step catches most problems. Count the fields in each line of the original with the current delimiter, and confirm that all lines agree. awk -F'\t' '{print NF}' data.tsv | sort | uniq -c prints how many lines have each field count. A single value means a consistent file. Two or more values mean some rows are ragged, which is the first sign that a delimiter appears inside a field, or that a row was truncated. Do the same after conversion with the new delimiter, and the two reports should show the same distribution. When they differ, a field contained the new delimiter and the output is no longer the same table.
The same check helps with header rows, blank lines and a trailing delimiter. Many exports end each line with a delimiter, producing an empty last field, and a converter turns that into a trailing comma that some importers reject.
Line endings and encodings
Delimiter work often hides a second conversion. Files from Windows end lines with a carriage return and a line feed, and a trailing \r becomes part of the last field. A comparison of that field against a string fails for reasons that look unrelated. Remove the carriage returns first with tr -d '\r' or dos2unix. A byte order mark at the start of the file, three bytes in UTF-8, ends up glued to the first header name and makes a column called id appear as an unprintable prefix plus id. Strip it with sed '1s/^\xEF\xBB\xBF//' if the importer cannot cope.
Encoding matters for width-aware tools. expand and column count bytes or characters depending on version and locale, and alignment with accented letters or CJK text can be off by a column or two in older releases. For output meant to be read, check it in the terminal that will be used.
Converting in place safely
Some conversions are applied to a source tree, as in the tabs example. Run them on a clean working tree under version control so that git diff shows exactly what changed, and use git diff --stat to confirm that only the expected files moved. Whitespace-only changes make blame noisy, so commit the reformat on its own and record the commit hash in .git-blame-ignore-revs. For editors, an .editorconfig file with indent_style and indent_size keeps the result consistent afterwards, and a CI check that fails on mixed indentation prevents a slow drift back.
Avoid sed -i on files you have not backed up on macOS, where the flag needs an explicit suffix argument, -i '', and a GNU-style command fails there. Writing to a temporary file and moving it over the original behaves the same on both systems and leaves the old data in place if the command fails halfway.
Where this does not apply
If values are quoted, such as "Smith, Jo", a delimiter swap on the raw text corrupts them; use csvkit or Miller, which understand quoting. Multi-byte delimiters and Unicode whitespace such as non-breaking spaces are not handled by tr in a UTF-8 locale; use sed or perl -CSD. See the POSIX tr specification and the GNU expand manual.


