How to replace multiple spaces with a single space using Bash?

13,850

Solution 1

Using tr:

$ echo "too         many       spaces." | tr -s ' '
too many spaces

man tr:

-s, --squeeze-repeats
       replace each sequence of a repeated character that is listed  in
       the last specified SET, with a single occurrence of that charac‐
       ter

Edit: Oh, by the way:

$ s="foo      bar"
$ echo $s
foo bar
$ echo "$s"
foo      bar

Edit 2: On the performance:

$ shopt -s extglob
$ s=$(for i in {1..100} ; do echo -n "word   " ; done) # 100 times: word   word   word...
$ time echo "${s//+([[:blank:]])/ }" > /dev/null

real    0m7.296s
user    0m7.292s
sys     0m0.000s
$ time echo "$s" | tr -s ' ' >/dev/null

real    0m0.002s
user    0m0.000s
sys     0m0.000s

Over 7 seconds?! How is that even possible. Well, this mini laptop is from 2014 but still. Then again:

$ time echo "${s//+( )/ }" > /dev/null

real    0m1.198s
user    0m1.192s
sys     0m0.000s

Solution 2

Here is a way to do this using pure bash and extglob:

s="too         many       spaces."

shopt -s extglob
echo "${s//+([[:blank:]])/ }"

too many spaces.
  • Bracket expression [[:blank:]] matches a space or tab character
  • +([[:blank:]]) matches one or more of the bracket expression (requires extglob)

Solution 3

Another simple sed expression using BRE is:

sed 's/[ ][ ]*/ /g'

For example:

$ echo "too         many       spaces." | sed 's/[ ][ ]*/ /g'
too many spaces.

There are a number of ways to skin the cat.

If the enclosed whitespace could consist of mixed spaces and tabs, then you could use:

sed 's/\s\s*/ /g'

And if you simply want to have bash word-splitting handle it, just echo your string without quotes, e.g.

$ echo "too         many       spaces." | while read line; do echo $line; done
too many spaces.

Continuing with that same thought, if your string with spaces is already stored in a variable, you can simply use echo unquoted within command substitution to have bash remove the additional whitespace for your, e.g.

$ foo="too         many       spaces."; bar=$(echo $foo); echo "$bar"
too many spaces.
Share:
13,850
Bruno Negrão Zica
Author by

Bruno Negrão Zica

Senior Software Developer on Ame Digital. https://github.com/bnegrao

Updated on July 26, 2022

Comments

  • Bruno Negrão Zica
    Bruno Negrão Zica almost 2 years

    I'd like to use bash to replace multiple adjacent spaces in a string by a single space. Example:

    Original string:

    "too         many       spaces."
    

    Transformed string:

    "too many spaces."
    

    I've tried things like "${str//*( )/.}" or awk '{gsub(/[:blank:]/," ")}1' but I can't get it right.

    Note: I was able to make it work with <CMD_THAT_GENERATES_THE_INPUT_STRINGH> | perl -lpe's/\s+/ /g' but I had to use perl to do the job. I'd like to use some bash internal syntax instead of calling an external program, if that is possible.

  • Charles Duffy
    Charles Duffy about 6 years
    I'd certainly go this route on a POSIX shell where bash extensions weren't available. That said, it's going to be much slower to run (with reasonable input sizes) than the approach given by @anubhava when one can use bash.
  • James Brown
    James Brown about 6 years
    Added a test with a hundred words and 3 spaces between each.
  • James Brown
    James Brown about 6 years
    Over 7 seconds every time. echo $BASH_VERSION 4.4.12(1)-release
  • Charles Duffy
    Charles Duffy about 6 years
    ...waitaminute, found it -- I didn't have extglob enabled. Okay, that's a legitimate result. Might add shopt -s extglob to make it copy-and-pasteable for folks trying to repro.