150

I have a simple shell script that removes trailing whitespace from a file. Is there any way to make this script more compact (without creating a temporary file)?

sed 's/[ \t]*$//' $1 > $1__.tmp
cat $1__.tmp > $1
rm $1__.tmp
5
  • 2
    You can use mv instead of cat and rm. Why are you using cat like that anyway? Why not use cp? Commented Dec 14, 2010 at 11:14
  • 1
    I used the knowledge I learned from this question to create a shell script for recursively removing trailing whitespace. Commented Aug 23, 2013 at 1:06
  • 1
    Your solution is actually better when using MinGW due to a bug in sed on Windows: stackoverflow.com/questions/14313318/… Commented Apr 17, 2014 at 18:04
  • See also: How to remove trailing whitespaces for multiple files? Commented Oct 14, 2015 at 12:36
  • 1
    Note that using cat to overwrite the original file rather than mv will actually replace the data in the original file (ie, it will not break hard links). Using sed -i as proposed in many solutions will not do that. IOW, just keep doing what you're doing. Commented Aug 22, 2019 at 17:26

13 Answers 13

216

You can use the in place option -i of sed for Linux and Unix:

sed -i 's/[ \t]*$//' "$1"

Be aware the expression will delete trailing t's on OSX (you can use gsed to avoid this problem). It may delete them on BSD too.

If you don't have gsed, here is the correct (but hard-to-read) sed syntax on OSX:

sed -i '' -E 's/[ '$'\t'']+$//' "$1"

Three single-quoted strings ultimately become concatenated into a single argument/expression. There is no concatenation operator in bash, you just place strings one after the other with no space in between.

The $'\t' resolves as a literal tab-character in bash (using ANSI-C quoting), so the tab is correctly concatenated into the expression.

Sign up to request clarification or add additional context in comments.

9 Comments

I get the following on my machine which I cannot update: sed: Not a recognized flag: i
hm. its also buggy in the sense that it will remove all trailing "t"s :)
"sed: Not a recognized flag: i –" This happens on OSX. You need to add an extension for the backup file after -i on Macs. e.g.: sed -i .bak 's/[ \t]*$//' $1
@GoodPerson If you were't kidding, you likely forget to escape the t :) \t is a tab, for those who may not already know.
@SeanAllred was not kidding: its utterly broken unless you happen to be using GNU sed (which is broken in so many other ways)
|
74

At least on Mountain Lion, Viktor's answer will also remove the character 't' when it is at the end of a line. The following fixes that issue:

sed -i '' -e's/[[:space:]]*$//' "$1"

4 Comments

My sed also wanted a -E indicating "extended (modern) regular expressions"
codaddict's answer has the same problem on OS X (now macOS). This is the only solution on this platform.
@JaredBeck Mine sed on El Capitan didn't.
thx! so to trim: |sed -r -e 's"^[[:space:]]*""' -e 's"[[:space:]]*$""'"`
20

Thanks to codaddict for suggesting the -i option.

The following command solves the problem on Snow Leopard

sed -i '' -e's/[ \t]*$//' "$1"

3 Comments

Like @acrollet says, you cannot use \t with sed other than GNU sed and it gets interpreted as a literal letter t. The command only appears to work, probably because there are no TAB's in the trailing whitespace nor a t at the end of a sentence in your file. Using '' without specifying a backup suffix is not recommended.
If the resolution is indicated for Snow Leopard only, maybe the question should be 'how to remove trailing whitespace on Macos????'
16

It is best to also quote $1:

sed -i.bak 's/[[:blank:]]*$//' "$1"

1 Comment

The only correct answer now. The rest uses either outdated methods or incorrect [[:space:]] instead of [[:blank:]].
5

I have a script in my .bashrc that works under OSX and Linux (bash only !)

function trim_trailing_space() {
  if [[ $# -eq 0 ]]; then
    echo "$FUNCNAME will trim (in place) trailing spaces in the given file (remove unwanted spaces at end of lines)"
    echo "Usage :"
    echo "$FUNCNAME file"
    return
  fi
  local file=$1
  unamestr=$(uname)
  if [[ $unamestr == 'Darwin' ]]; then
    #specific case for Mac OSX
    sed -E -i ''  's/[[:space:]]*$//' $file
  else
    sed -i  's/[[:space:]]*$//' $file
  fi
}

to which I add:

SRC_FILES_EXTENSIONS="js|ts|cpp|c|h|hpp|php|py|sh|cs|sql|json|ini|xml|conf"

function find_source_files() {
  if [[ $# -eq 0 ]]; then
    echo "$FUNCNAME will list sources files (having extensions $SRC_FILES_EXTENSIONS)"
    echo "Usage :"
    echo "$FUNCNAME folder"
    return
  fi
  local folder=$1

  unamestr=$(uname)
  if [[ $unamestr == 'Darwin' ]]; then
    #specific case for Mac OSX
    find -E $folder -iregex '.*\.('$SRC_FILES_EXTENSIONS')'
  else
    #Rhahhh, lovely
    local extensions_escaped=$(echo $SRC_FILES_EXTENSIONS | sed s/\|/\\\\\|/g)
    #echo "extensions_escaped:$extensions_escaped"
    find $folder -iregex '.*\.\('$extensions_escaped'\)$'
  fi
}

function trim_trailing_space_all_source_files() {
  for f in $(find_source_files .); do trim_trailing_space $f;done
}

Comments

5
var1="\t\t Test String trimming   "
echo $var1
Var2=$(echo "${var1}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
echo $Var2

1 Comment

Hey, that's just what I needed! The other sed solutions posted had issue integrating with a piped (and piped and piped...) variable assignment in my bash script, but yours worked out of the box.
4

For those who look for efficiency (many files to process, or huge files), using the + repetition operator instead of * makes the command more than twice faster.

With GNU sed:

sed -Ei 's/[ \t]+$//' "$1"
sed -i 's/[ \t]\+$//' "$1"   # The same without extended regex

I also quickly benchmarked something else: using [ \t] instead of [[:space:]] also significantly speeds up the process (GNU sed v4.4):

sed -Ei 's/[ \t]+$//' "$1"

real    0m0,335s
user    0m0,133s
sys 0m0,193s

sed -Ei 's/[[:space:]]+$//' "$1"

real    0m0,838s
user    0m0,630s
sys 0m0,207s

sed -Ei 's/[ \t]*$//' "$1"

real    0m0,882s
user    0m0,657s
sys 0m0,227s

sed -Ei 's/[[:space:]]*$//' "$1"

real    0m1,711s
user    0m1,423s
sys 0m0,283s

Comments

2

In the specific case of sed, the -i option that others have already mentioned is far and away the simplest and sanest one.

In the more general case, sponge, from the moreutils collection, does exactly what you want: it lets you replace a file with the result of processing it, in a way specifically designed to keep the processing step from tripping over itself by overwriting the very file it's working on. To quote the sponge man page:

sponge reads standard input and writes it out to the specified file. Unlike a shell redirect, sponge soaks up all its input before writing the output file. This allows constructing pipelines that read from and write to the same file.

https://joeyh.name/code/moreutils/

Comments

2

To remove trailing whitespace for all files in the current directory, I use

ls | xargs sed -i 's/[ \t]*$//'

1 Comment

This won't work for BSD sed: echo test | sed 's/[ \t]*$//' gets truncated to tes.
1

Just for fun:

#!/bin/bash

FILE=$1

if [[ -z $FILE ]]; then
   echo "You must pass a filename -- exiting" >&2
   exit 1
fi

if [[ ! -f $FILE ]]; then
   echo "There is not file '$FILE' here -- exiting" >&2
   exit 1
fi

BEFORE=`wc -c "$FILE" | cut --delimiter=' ' --fields=1`

# >>>>>>>>>>
sed -i.bak -e's/[ \t]*$//' "$FILE"
# <<<<<<<<<<

AFTER=`wc -c "$FILE" | cut --delimiter=' ' --fields=1`

if [[ $? != 0 ]]; then
   echo "Some error occurred" >&2
else
   echo "Filtered '$FILE' from $BEFORE characters to $AFTER characters"
fi

Comments

0

These answers confused me. Both of these sed commands worked for me on a Java source file:

  • sed 's/\s\+$/ filename
  • sed 's/[[:space:]]\+$// filename

for test purposes, I used:

 $ echo "  abc       " | sed 's/\s\+$/-xx/'
abc-xx
 $ echo -e "  abc   \t\t    " | sed 's/\s\+$/-xx/'
abc-xx

Replacing all trailing whitespace with "-xx".

@Viktor wishes to avoid a temporay file, personally I would only use the -i => in-place with a back-up suffix. At least until I know the command works.

Sorry, I just found the existing responses a little oblique. sed is straightforward tool. It is easier to approach it in a straightforward way 90% of the time. Or perhaps I missed something, happy to corrected there.

Comments

0

Just call

$ sed -i 's/[[:space:]]*$//' /path/to/file

3 Comments

Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation?
@JeremyCaney, Dear Jeremy, there are indeed a lot of answers, with the most popular answer being just plain wrong, as some people have already noticed, while others are plain outdated with a lot of syntax which is not needed anymore. Instead of writing this long explanation, I have just posted the simplest in my opinion answer. I am in this community for more than 10 years and usually the most interesting answer is being voted up, without extra help from people like you. Something has changed since then?
I'm trying to help you out, John. When there are a lot of answers to a question, it's difficult for readers to sort through them and understand the differences. That makes it harder for "the more interesting answer" to get the attention it deserves. That's why you end up with a "popular answer being just plain wrong", as it has momentum, and people first sort by votes, or at least the new trending value. Adding an explanation to differentiate your answer and explain why it is preferred helps the community find the best answer in a crowded list.
-1

To only strip whitespaces (in my case spaces and tabs) from lines with at least one non-whitespace character (this way empty indented lines are not touched):

sed -i -r 's/([^ \t]+)[ \t]+$/\1/' "$file"

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.