In vim it seems possible to do like this :

:s/^\t\+/\=repeat('    ',len(submatch(0)))

I didn't find out how this translates into sed. I tried the following :

sed -ri "s/^\t\+/\=repeat('    ',len(submatch(0)))/g" test.txt

The command seems to have no effect.

  • You're using the -r option, which enables extended regular expressions: that means you need to use + not \+ for the quantifier. This code looks for one leading tab followed by a plus character. Jun 5 at 13:16

This is what expand is for, e.g. expand -t 4 file.txt.

expand won't edit the original file, but you can redirect to a new file and mv it over the original, or use sponge if you have it installed, e.g.

    expand -t 4 file.txt > file.new && mv file.new file.txt
    expand -t 4 file.txt | sponge file.txt

FYI, see unexpand to do the reverse, convert multiple spaces to tabs.

Both expand and unexpand support using fixed-width tabs (e.g. -t 4 or -t 8, etc) or a list of tab positions (e.g. -t 4,12,32). They also have options to only convert initial, leading spaces/tabs. See the man pages for details.

For more info about sponge, see man sponge and Is there a standard alternative to sponge to pipe a file into itself?. In short, it does the redirect and mv for you, using a temporary file. BTW, never try to redirect stdout to the same file that's currently being used as stdin. The shell will overwrite it before it even gets read.

sponge is from the moreutils package.

PS: See also GNU indent or one of many other similar source-code reformatting tools.

  • expand has at least the -i for the leading tabs, which is what I want, but it has not the -i from sed to replace in the file directly, it would be good if I could do something like expand -i -t 4 */**. So if I want to replace in , say, all java files, I would do something like : find . -type f -name '*.java' -print0 | xargs -0 -Ifile sh -c 'expand -i -t 4 file > file.new && mv file.new file' ?
    – Sybuser
    Jun 5 at 8:22
  • I'd use find ... -exec and not bother with xargs. Something like find . -type f -name '*.java' -exec sh -c 'for f in "$@"; do t=$(mktemp); expand -i -t 4 "$f" > "$t" && mv "$t" "$f" || rm "$t"; done' sh {} +. Or, with sponge: find . -type f -name '*.java' -exec sh -c 'for f in "$@"; do expand -i -t 4 "$f" | sponge "$f" ; done' sh {} +`
    – cas
    Jun 5 at 9:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.