65

I have a file, f1.txt:

ID     Name
1      a
2         b
3   g
6            f

The number of spaces is not fixed. What is the best way to replace all the white spaces with one space using only tr?

This is what I have so far:

cat f1.txt | tr -d " "

But the output is:

IDName
1a
2b
3g
6f

But I want it to look like this:

ID Name
1 a
2 b
3 g
6 f

Please try and avoid sed.

  • 6
    Why is it so important to avoid sed? Use whatever works! – David Richerby Jul 22 '14 at 23:00
  • 7
    Because I know how to do it with sed. Wanted to know other ways : ) – gkmohit Jul 22 '14 at 23:07
102

With tr, use the squeeze repeat option:

$ tr -s " " < file
ID Name
1 a
2 b
3 g
6 f

Or you can use an awk solution:

$ awk '{$2=$2};1' file
ID Name
1 a
2 b
3 g
6 f

When you change a field in record, awk rebuild $0, takes all field and concat them together, separated by OFS, which is a space by default.

That will squeeze sequences of space and tabs (and possibly other blank characters depending on the locale and implementation of awk) into one space, but also remove the leading and trailing blanks off each line.

  • This is a great solution too . . . I dont know which one to choose now :/ @Gnouc – gkmohit Jul 22 '14 at 19:11
  • Feel free to chose any solution that you like and it works for you. A note that my solution is different with @polym's answer. – cuonglm Jul 22 '14 at 19:13
  • 1
    :)) yay! @Gnouc s answer is really dynamic, because he uses awk, he can do anything. You can also accept his solution. Just one thing: Gnouc can you possibly explain what the awk format in your command does? Also can you add tabs/spaces so that the output is conforming to Unknown's expected output? – polym Jul 22 '14 at 19:17
  • 1
    @polym: With Unknown's last edit, he seems only want one space, not output like column -t does. Add explaination for awk. – cuonglm Jul 22 '14 at 19:30
  • 4
    There is a small difference here. tr will replace two spaces at the end of a line with a single space. awk will remove all trailing spaces. – Anne van Rossum Sep 24 '15 at 10:40
19

Just use column:

column -t inputFile

Output:

ID  Name
1   a
2   b
3   g
6   f
  • Wonderful and a quick reply :) – gkmohit Jul 22 '14 at 19:04
  • 1
    @Unknown Great to be at service :)! – polym Jul 22 '14 at 19:05
  • 1
    @Gnouc wow cool, column also takes a file as argument. nice thanks! – polym Jul 22 '14 at 19:05
  • How can I get the second column only if I want ? I tried column -t f1.txt | cut -d " " -f2 But was not a solution i expected – gkmohit Jul 22 '14 at 19:08
  • 2
    Use awk then: column -t file | awk '{print $2}' prints the second column only – polym Jul 22 '14 at 19:09
7

If you want to squeeze "white space" you will want to use tr's pre-defined character sets ":blank:" (horizontal whitespace tab and space) or ":space:" (verical whitespace) :

/bin/echo -e  "val1\t\tval2   val3" | tr -s "[:blank:]"

Examples were run on Red Hat 5 (GNU tr).

In my case I wanted to normalize all whitespace to a single space so I could rely on the space as a delmitter.

As pointed out by dastrobu's second comment I missed the wording in the man page:

 -s uses the last specified SET, and occurs after translation or deletion.

This allows us to eliminate the first tr. Kudo's to scott for his patiences in the face of my denseness.

Before, parsing port from Redis config. file:

grep "^port" $redisconf | tr "[:blank:]" " " | tr -s "[:blank:]"  | cut -d" " -f2

After, with SET2 being specified with the squeeze:

grep "^port" $redisconf | tr -s "[:blank:]" " " | cut -d" " -f2

Output:

6379

For more details covering the nuances of whitespace

Demonstrate where squeeze alone fails when successive mixed characters which fall into the [:blank:] character class are involved:

 /usr/bin/printf '%s \t %s' id myname | tr -s "[:blank:]"  | od -cb
0000000   i   d      \t       m   y   n   a   m   e
        151 144 040 011 040 155 171 156 141 155 145
0000013

Note: My two string fields in the printf format are separated by 1 space, 1 tab, 1 space. After the squeeze this sequence still exists. In the output of the Octal dump this is represented by ascii sequence 040 011 040.

  • 1
    Do you really need tr "[:blank:]" " " | tr -s "[:blank:]"? I guess the first part will suffice, i.e. tr "[:blank:]" " " since it normalizes whitespace and does the substitution already. From man page: "Squeeze multiple occurrences of the characters [...] This occurs after all deletion and translation is completed." – dastrobu Mar 24 '18 at 12:21
  • 2
    so ´tr -s "[:blank:]" " "´ should do it it first translates all blanks to spaces and then squeezes the spaces. No need for a second ´tr´. – dastrobu Mar 28 '18 at 13:49
  • 1
    I tried printf 'ID \t Name\n' | tr -s "[:blank:]" " " | od -cb (as suggested by @dastrobu) and I got ID Name\n (with one space) as output.  Did you actually try it, @user3183018? – Scott Apr 11 at 17:03
  • 1
    OK, let me try to say this again. I did printf 'ID␣\t␣Name\n' | tr -s "[:blank:]" "␣"  (as suggested by @dastrobu), where represents a space, and I got ID␣Name\n (with one space) as output.  This is exactly the same as your example of “Port<SPACE><TAB><SPACE>6379” except I used the heading strings from the question.  I’m wondering whether you tried tr -s "[:blank:]" (without the final "␣" argument). – Scott Apr 12 at 19:29
  • 1
    When I do printf 'ID \t Name\n' | od -cb, it shows exactly what it’s supposed to: ID ⁠  \t ⁠  N a m e \n (i.e., ID 040 011 040 N a m e\n). Meanwhile, by your own evidence, you’re making exactly the error that I guessed that you were: you are running tr -s "[:blank:]" (i.e., tr with one option and one argument), instead of the command that @dastrobu and I have presented four times now: tr -s '[:blank:]' '␣' (i.e., tr with one option and two arguments). – Scott Apr 13 at 22:42
5

Who needs a program (other than the shell)?

while read a b
do
    echo "$a $b"
done < f1.txt

If you want the values in the second column to line up, as in polym’s column answer, use printf instead of echo:

while read a b
do
    printf '%-2s %s\n' "$a" "$b"
done < f1.txt
  • 1
    In the first place, when compared with tr - this is a terribly weak suggestion efficiency-wise unless the input is just too small too outweigh the tiny cost of tr's invocation - which is not to mention how much more work it takes to write. Last, wouldn't you say that this post does not actually answer the question as asked? What is the best way to replace all the white spaces with one space using only tr? – mikeserv Jul 24 '14 at 23:22
  • 1
    And besides - couldn't you more easily just do something with $IFS? Maybe like: IFS=' <tab>' set -f ; echo $(cat <file)? – mikeserv Jul 24 '14 at 23:49
2

This is an old question and solved many times. Just for completeness: I had a simillar issue, but wanted to pass lines via pipe to antother program. I used xargs.

-L max-lines
   Use at most max-lines nonblank input lines per command line.
   Trailing blanks cause an input line to be logically continued 
   on the next input line.  Implies -x.

so cat f1.txt | xargs -L1 seems to output exactly what you want.

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.