How to remove trailing whitespaces with sed? - sed

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

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.

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"

Thanks to codaddict for suggesting the -i option.
The following command solves the problem on Snow Leopard
sed -i '' -e's/[ \t]*$//' "$1"

It is best to also quote $1:
sed -i.bak 's/[[:blank:]]*$//' "$1"

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

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
}

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

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

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/

To remove trailing whitespace for all files in the current directory, I use
ls | xargs sed -i 's/[ \t]*$//'

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.

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"

Related

sed find and replace fails when string has a space in it

I have a sample.txt file with contents as:
test sometest
test2 againsometext
My shell script uses sed to find the line which has "test" and replaces the whole line with "test google.com::100-ws sample.com::SAMPLE". sed fails with "unterminated `s' command".
#!/bin/sh
TEST1=test
TEST2=google.com::100-ws sample.com::GOLD-WS
echo $TEST1
echo $TEST2
if [[ ! -e sample.txt ]]; then
touch sample.txt
echo $TEST1 $TEST2 >> sample.txt
else
grep -q $TEST1 sample.txt
if [ $? -eq 0 ];then
sed -i 's/^$TEST1 .*$/$TEST1 '$TEST2'/' sample.txt
else
echo $TEST1 $TEST2 >> sample.txt
fi
fi
I have this same script in other places where the replacement text does not have any spaces and it works fine.
Can someone suggest some ideas to try?
TIA
Assuming bash is available in your environment, it will be just
enough to say:
#!/bin/bash
test1="test"
test2="google.com::100-ws sample.com::GOLD-W"
sed -i "s/^$test1.*/$test1 $test2/" sample.txt
You need to quote strings which contain blank character(s) to prevent word-splitting.
It is not a good practice to use uppercase alphabets for normal variable names.
You need to use not single quotes but double quotes to enable variable expansion as
a command to sed.
That space between test and msg is important. Otherwise you'll obliterate every line that begins with test. Also you can backrefence \1 your captured group \(test \) https://regular-expressions.mobi/refcapture.html
#! /bin/bash
replace="google.com::100-ws sample.com::GOLD-W"
sed -i "s/^\(test \).*/\1$replace/" sample.txt

Shell scripting Sed option

I need a help in the shell scripting processing the file. The script should read each file in the path and replace the string in each row.
It should read each line and replace the 7th column with XXXX mentioned in the sample output. Any help in appreciated.
Input file data
"2013-04-30"|"X"|"0000628"|"15000231"|"1999-12-05"|"ST"|"2455525445552000"|"1111-11-11"|75.00|"XXE11111"|"224425"
"2013-04-30"|"Y"|"0000928"|"95000232"|"1999-12-05"|"VT"|"2455525445552000"|"1111-11-11"|95.00|"VVE11111"|"224425"
output file
"2013-04-30"|"X"|"0000628"|"15000231"|"1999-12-05"|"ST"|"24555XXXXXXXXXX"|"1111-11-11"|75.00|"XXE11111"|"224425"
"2013-04-30"|"Y"|"0000928"|"95000232"|"1999-12-05"|"VT"|"24555XXXXXXXXXX"|"1111-11-11"|95.00|"VVE11111"|"224425"
Script I used to run but it is not editing the input file
FILES=/home/auto/*.txt
for f in $FILES
do
echo "Processing $f file..."
cat $f | awk 'BEGIN {FS="|"; OFS="|"} {$7=substr($7, 1, 6)"XXXXXXXXXX\"";print}'
done
but I can't edit the exiting file in the directory. I need to use the sed -i option but it's not working.
I tried using the script in below server but I am getting the following error.
SunOS 5.10 Generic January 2005
echo "hello"
FILES=/export/home/*.txt
for f in $FILES
do
echo "Processing $f file..."
sed -i -r 's/"([^"]{6})[^"]*"/"\1XXXXXXXXXX"/6' "$f"
done
I get
sed: illegal option -- i
Using GNU sed with -i optoin
sed -i -r 's/"([^"]{5})[^"]*"/"\1XXXXXXXXXX"/5' file
"2013-04-30"|"X"|"0000628"|"15000231"|"1999-12-05"|"ST"|"24555XXXXXXXXXX"|"1111-11-11"|75.00|"XXE11111"|"224425"
"2013-04-30"|"Y"|"0000928"|"95000232"|"1999-12-05"|"VT"|"24555XXXXXXXXXX"|"1111-11-11"|95.00|"VVE11111"|"224425"
if your awk is gnu awk 4.1.0, there is in-place option, read man/info page.
otherwise, you could do:
awk '..code..' inputfile > tmpfile && mv tmpfile inputfile
note, the cat is not necessary, could (should) be removed.
A little ugly but you can try something like this with sed
sed -i 's/\(\([^|]*|\)\{6\}\)\(.\{6\}\).\{11\}\(.*\)/\1\3XXXXXXXXXXX\4/' file
So with your existing script, it will be -
FILES=/home/auto/*.txt
for f in $FILES
do
echo "Processing $f file..."
sed -i 's/\(\([^|]*|\)\{6\}\)\(.\{6\}\).\{11\}\(.*\)/\1\3XXXXXXXXXXX\4/' "$f"
done

How can sed replace "\ " (backslash + space)?

In a bash script, files with spaces show up as "File\ with\ spaces.txt" and I want to substitute those slashed-spaces with either _ or +.
How can I tell sed to do that? I had no success using;
$1=~/File\ with\ spaces.txt
ext=$1
web=$(echo "$ext" | sed 's/\ /+/')
I'm open to suggestions if there's a better way than through sed.
[EDIT]: Foo Bah's solution works well, but it substitutes only the first space because the text following it is treated as arguments rather than part of the $1. Any way around this?
sed 's/\\\\ /+/';
\\\\ evaluates to a \\ at the shell level, and then into a literal \ within sed.
Sed recognises \ as space just fine:
bee#i20 ~ $ echo file\ 123 | sed 's/\ /+/'
file+123
Your bash script syntax is all wrong, though.
Not sure what you were trying to do with the script, but here is an example of replacing spaces with +:
ext=~/File\ with\ spaces.txt
web=`echo "$ext" | sed 's/\ /+/g'`
echo $web
Upd:
Oh, and you need the g flag to replace all occurences of space, not only the first one. Fixed above.
you want to escape the slash:
web=$(echo "$ext" | sed 's/\\ /_/g')
single quotes are your friend
the following should be used with single quoted args for $1 and $2
#!/bin/bash
ESCAPE='\\'
if [ $# -ne 2 ];then
echo "$0 <TO_ESCAPE> <IN_STRING>"
echo args should be in single quotes!!
exit 1
fi
TO_ESCAPE="${1}"
IN_STRING="${2}"
if [ ${TO_ESCAPE} = '\' ];then
TO_ESCAPE='\\'
fi
echo "${IN_STRING}" | sed "s/${TO_ESCAPE}/${ESCAPE}${TO_ESCAPE}/g"

sed command in dry run

How it is possible to make a dry run with sed?
I have this command:
find ./ -type f | xargs sed -i 's/string1/string2/g'
But before I really substitute in all the files, i want to check what it WOULD substitute. Copying the whole directory structure to check is no option!
Remove the -i and pipe it to less to paginate though the results. Alternatively, you can redirect the whole thing to one large file by removing the -i and appending > dryrun.out
I should note that this script of yours will fail miserably with files that contain spaces in their name or other nefarious characters like newlines or whatnot. A better way to do it would be:
while IFS= read -r -d $'\0' file; do
sed -i 's/string1/string2/g' "$file"
done < <(find ./ -type f -print0)
I would prefer to use the p-option:
find ./ -type f | xargs sed 's/string1/string2/gp'
Could be combined with the --quiet parameter for less verbose output:
find ./ -type f | xargs sed --quiet 's/string1/string2/gp'
From man sed:
p:
Print the current pattern space.
--quiet:
suppress automatic printing of pattern space
I know this is a very old thread and the OP doesn't really need this answer, but I came here looking for a dry run mode myself, so thought of adding the below piece of advice for anyone coming here in future. What I wanted to do was to avoid stomping the backup file unless there is something really changing. If you blindly run sed using the -i option with backup suffix, existing backup file gets overwritten, even when there is nothing substituted.
The way I ended up doing is to pipe sed output to diff and see if anything changed and then rerun sed with in-place update option, something like this:
if ! sed -e 's/string1/string2/g' $fpath | diff -q $fpath - > /dev/null 2>&1; then
sed -i.bak -e 's/string1/string2/g' $fpath
fi
As per OP's question, if the requirement is to just see what would change, then instead of running the in-pace sed, you could do the diff again with some informative messages:
if ! sed -e 's/string1/string2/g' $fpath | diff -q $fpath - > /dev/null 2>&1; then
echo "File $fpath will change with the below diff:"
sed -e 's/string1/string2/g' $fpath | diff $fpath -
fi
You could also capture the output in a variable to avoid doing it twice:
diff=$(sed -e 's/string1/string2/g' $fpath | diff $fpath -)
if [[ $? -ne 0 ]]; then
echo "File $fpath will change with the below diff:"
echo "$diff"
fi

How can I append the name of a file to end of each line in that file?

I need to do the following for hundreds of files:
Append the name of the file (which may contain spaces) to the end of each line in the file.
It seems to me there should be some way to do this:
sed -e 's/$/FILENAME/' *
where FILENAME represents the name of the current file. Is there a sed variable representing the current filename? Or does anyone have a different solution using bash, awk, etc.?
I'm sure there are other ways to do it, I'd use perl:
perl -p -i -e 's/$/$ARGV/;' *
Some versions of sed support the "--in-place" argument so you can condense Tyler's solution to
for i in * ; do
sed -e "s/\$/$i/" --in-place "$i"
done
You could do it with a bash script
for i in *
do
sed -e "s/\$/$i/" "$i"
done
One-liner version:
for i in * ; do sed -e "s/\$/$i/" "$i" ; done
Edit: If you want to replace the contents of the file with the new, name-appended lines, do this:
TFILE=`mktemp`
for i in *
do
sed -e "s/\$/$i/" "$i" > $TFILE
cp -f $TFILE "$i"
done
rm -f $TFILE
awk '{print $0,FILENAME}' > tmpfile
In BASH, I'd do something to the effect of:
for f in *; do echo $f >> $f; done
More or less how Tyler suggested, just with some modifications to allow for spaces in the name. I was hoping for a one-liner though...
(
OLDIFS=$IFS
IFS=$'\n'
for f in *
do
IFS=OLDIFS
sed -e "s/\$/$f/" $f > tmpfile
mv tmpfile $f
IFS=$'\n'
done
)
This might work for you:
printf "%s\n" * | sed 's/.*/sed -i "s|$| &|" &/' | bash