1394

I have a shell script with this code:

var=`hg st -R "$path"`
if [ -n "$var" ]; then
    echo $var
fi

But the conditional code always executes, because hg st always prints at least one newline character.

  • Is there a simple way to strip whitespace from $var (like trim() in PHP)?

or

  • Is there a standard way of dealing with this issue?

I could use sed or AWK, but I'd like to think there is a more elegant solution to this problem.

8
  • 4
    Related, if you wanted to trim space on an integer and just get the integer, wrap with $(( $var )), and can even do that when inside double quotes. This became important when I used the date statement and with filenames. Commented Dec 10, 2012 at 14:42
  • "Is there a standard way of dealing with this issue?" Yes, use [[ instead of [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1 Commented Apr 26, 2016 at 16:08
  • If it helps, at least where am testing it on Ubuntu 16.04. Using the following matches trim in every way: echo " This is a string of char " | xargs. If you however have a single quote in the text you can do the following: echo " This i's a string of char " | xargs -0. Note that I mention latest of xargs (4.6.0) Commented Jul 19, 2016 at 16:48
  • The condition isn't true because of a newline as backticks swallow the last newline. This will print nothing test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, this however will test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi - so there must be more than just a newline at the end. Commented Jun 19, 2017 at 17:02
  • A="123 4 5 6 "; B=echo $A | sed -r 's/( )+//g'; Commented Jul 17, 2018 at 11:25

54 Answers 54

1641

A simple answer is:

echo "   lol  " | xargs

Xargs will do the trimming for you. It's one command/program, no parameters, returns the trimmed string, easy as that!

Note: this doesn't remove all internal spaces so "foo bar" stays the same; it does NOT become "foobar". However, multiple spaces will be condensed to single spaces, so "foo bar" will become "foo bar". In addition it doesn't remove end of lines characters.

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

30 Comments

Nice. This works really well. I have decided to pipe it to xargs echo just to be verbose about what i'm doing, but xargs on its own will use echo by default.
Nice trick, but be carefull, you can use it for one-line string but −by xargs design− it will not just do triming with multi-line piped content. sed is your friend then.
The only problem with xargs is that it will introduce a newline, if you want to keep the newline off I would recommend sed 's/ *$//' as an alternative. You can see the xargs newline like this: echo -n "hey thiss " | xargs | hexdump you will notice 0a73 the a is the newline. If you do the same with sed: echo -n "hey thiss " | sed 's/ *$//' | hexdump you will see 0073, no newline.
Careful; this will break hard if the string to xargs contains surplus spaces in between. Like " this is one argument ". xargs will divide into four.
This is bad. 1. It will turn a<space><space>b into a<space>b. 2. Even more: it will turn a"b"c'd'e into abcde. 3. Even more: it will fail on a"b, etc.
|
1220

Let's define a variable containing leading, trailing, and intermediate whitespace:

FOO=' test test test '
echo -e "FOO='${FOO}'"
# > FOO=' test test test '
echo -e "length(FOO)==${#FOO}"
# > length(FOO)==16

How to remove all whitespace (denoted by [:space:] in tr):

FOO=' test test test '
FOO_NO_WHITESPACE="$(echo -e "${FOO}" | tr -d '[:space:]')"
echo -e "FOO_NO_WHITESPACE='${FOO_NO_WHITESPACE}'"
# > FOO_NO_WHITESPACE='testtesttest'
echo -e "length(FOO_NO_WHITESPACE)==${#FOO_NO_WHITESPACE}"
# > length(FOO_NO_WHITESPACE)==12

How to remove leading whitespace only:

FOO=' test test test '
FOO_NO_LEAD_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//')"
echo -e "FOO_NO_LEAD_SPACE='${FOO_NO_LEAD_SPACE}'"
# > FOO_NO_LEAD_SPACE='test test test '
echo -e "length(FOO_NO_LEAD_SPACE)==${#FOO_NO_LEAD_SPACE}"
# > length(FOO_NO_LEAD_SPACE)==15

How to remove trailing whitespace only:

FOO=' test test test '
FOO_NO_TRAIL_SPACE="$(echo -e "${FOO}" | sed -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_TRAIL_SPACE='${FOO_NO_TRAIL_SPACE}'"
# > FOO_NO_TRAIL_SPACE=' test test test'
echo -e "length(FOO_NO_TRAIL_SPACE)==${#FOO_NO_TRAIL_SPACE}"
# > length(FOO_NO_TRAIL_SPACE)==15

How to remove both leading and trailing spaces--chain the seds:

FOO=' test test test '
FOO_NO_EXTERNAL_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_EXTERNAL_SPACE='${FOO_NO_EXTERNAL_SPACE}'"
# > FOO_NO_EXTERNAL_SPACE='test test test'
echo -e "length(FOO_NO_EXTERNAL_SPACE)==${#FOO_NO_EXTERNAL_SPACE}"
# > length(FOO_NO_EXTERNAL_SPACE)==14

Alternatively, if your bash supports it, you can replace echo -e "${FOO}" | sed ... with sed ... <<<${FOO}, like so (for trailing whitespace):

FOO_NO_TRAIL_SPACE="$(sed -e 's/[[:space:]]*$//' <<<${FOO})"

15 Comments

To generalize the solution to handle all forms of whitespace, replace the space character in the tr and sed commands with [[:space:]]. Note that the sed approach will only work on single-line input. For approaches that do work with multi-line input and also use bash's built-in features, see the answers by @bashfu and @GuruM. A generalized, inline version of @Nicholas Sushkin's solution would look like this: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
If you do that often, appending alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'" to your ~/.profile allows you to use echo $SOMEVAR | trim and cat somefile | trim.
The line echo -e "${FOO}" | wc -m counts one more character because echo adds it. Try echo -ne "${FOO}" | wc -m and get the correct count.
An inconvenient of this method is that it uses sed, whose behavior depends on the system. In particular, sed from Solaris 10 doesn't support [[:space:]].
Note that tr -d "[:space:]" removes both horizontal and vertical whitespace characters (=newlines). To remove just horizontal whitespace characters simply use tr -d " ".
|
550

A solution that uses Bash built-ins called wildcards:

var="    abc    "
# remove leading whitespace characters
var="${var#"${var%%[![:space:]]*}"}"
# remove trailing whitespace characters
var="${var%"${var##*[![:space:]]}"}"   
printf '%s' "===$var==="

Here's the same wrapped in a function:

trim() {
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"
    printf '%s' "$var"
}

You pass the string to be trimmed in quoted form, e.g.:

trim "   abc   "

This solution works with POSIX-compliant shells.

Reference

18 Comments

Clever! This is my favorite solution as it uses built-in bash functionality. Thanks for posting! @San, it's two nested string trims. E.g., s=" 1 2 3 "; echo \""${s%1 2 3 }"\" would trim everything from the end, returning the leading " ". Subbing 1 2 3 with [![:space:]]* tells it to "find the first non-space character, then clobber it and everything after". Using %% instead of % makes the trim-from-end operation greedy. This is nested in a non-greedy trim-from-start, so in effect, you trim " " from the start. Then, swap %, #, and * for the end spaces. Bam!
Much better solution than using sed, tr etc., since it's much faster, avoiding any fork(). On Cygwin difference in speed is orders of magnitude.
@San At first I was stumped because I thought these were regular expressions. They are not. Rather, this is Pattern Matching syntax (gnu.org/software/bash/manual/html_node/Pattern-Matching.html, wiki.bash-hackers.org/syntax/pattern) used in Substring Removal (tldp.org/LDP/abs/html/string-manipulation.html). So ${var%%[![:space:]]*} says "remove from var its longest substring that starts with a non-space character". That means you are left only with the leading space(s), which you subsequently remove with ${var#... The following line (trailing) is the opposite.
This is overwhelmingly the ideal solution. Forking one or more external processes (e.g., awk, sed, tr, xargs) merely to trim whitespace from a single string is fundamentally insane – particularly when most shells (including bash) already provide native string munging facilities out-of-the-box.
Wouldn't it be easier to just var=${var##+([[:space:]])} and var=${var%%+([[:space:]])}? The bash pattern +(...) means one or more of whatever is in the parens. That way you don't have to do the weird double-expansion.
|
132

In order to remove all the spaces from the beginning and the end of a string (including end of line characters):

echo $variable | xargs echo -n

This will remove duplicate spaces also:

echo "  this string has a lot       of spaces " | xargs echo -n

Produces: 'this string has a lot of spaces'

6 Comments

Basically the xargs removes all the delimiters from the string. By default it uses the space as delimiter (this could be changed by the -d option).
Why do you need echo -n at all? echo " my string " | xargs has the same output.
echo -n removes the end of line as well
Does not do what the question asks. For example, all spaces in the input (other than leading/trailing) will be converted to a single space. When you try to use encoding hacks like this you are relying on a number of factors you can't be sure about. Instead, use tools that are designed for processing data as data. I don't know why so many developers are so attracted to encoding bug minefields.
Removes backslashes.
|
113

Bash has a feature called parameter expansion, which, among other things, allows string replacement based on so-called patterns (patterns resemble regular expressions, but there are fundamental differences and limitations). [flussence's original line: Bash has regular expressions, but they're well-hidden:]

The following demonstrates how to remove all white space (even from the interior) from a variable value.

$ var='abc def'
$ echo "$var"
abc def
# Note: flussence's original expression was "${var/ /}", which only replaced the *first* space char., wherever it appeared.
$ echo -n "${var//[[:space:]]/}"
abcdef

13 Comments

Or rather, it works for spaces in the middle of a var, but not when I attempt to anchor it at the end.
They're regex, just a strange dialect.
${var/ /} removes the first space character. ${var// /} removes all space characters. There's no way to trim just leading and trailing whitespace with only this construct.
Bash patterns are not regular expressions. Bash supports both Bash pattern syntax and genuine regular expressions (e.g., via the builtin =~ operator introduced with Bash 3.0.0). The two have nothing to do with one another.
Bash “patterns” are regular expressions, only with a different syntax than “POSIX (extended) regular expressions”. You are right in saying that they are incompatible, and that Bash expects them in different places (the former in parameter substitutions, the latter after the =~ operator). As for their expressiveness: with “extended pattern matching operators” (shell option extglob), the only features that POSIX regexps have and Bash patterns lack, are ^ and $ (compare man bash § “Pattern Matching” with man 7 regex).
|
81

Strip one leading and one trailing space

trim()
{
    local trimmed="$1"

    # Strip leading space.
    trimmed="${trimmed## }"
    # Strip trailing space.
    trimmed="${trimmed%% }"

    echo "$trimmed"
}

For example:

test1="$(trim " one leading")"
test2="$(trim "one trailing ")"
test3="$(trim " one leading and one trailing ")"
echo "'$test1', '$test2', '$test3'"

Output:

'one leading', 'one trailing', 'one leading and one trailing'

Strip all leading and trailing spaces

trim()
{
    local trimmed="$1"

    # Strip leading spaces.
    while [[ $trimmed == ' '* ]]; do
       trimmed="${trimmed## }"
    done
    # Strip trailing spaces.
    while [[ $trimmed == *' ' ]]; do
        trimmed="${trimmed%% }"
    done

    echo "$trimmed"
}

For example:

test4="$(trim "  two leading")"
test5="$(trim "two trailing  ")"
test6="$(trim "  two leading and two trailing  ")"
echo "'$test4', '$test5', '$test6'"

Output:

'two leading', 'two trailing', 'two leading and two trailing'

5 Comments

This will trim only 1 space character. So the echo results in 'hello world ', 'foo bar', 'both sides '
@Joe I added a better option.
This helped me, but the question specifically asked about a trailing newlin which this does not remove.
@andrewlorien The original question was wrong about the trailing newline. Its real problem was some other trailing character. (The shell's `` or $() notation removes all trailing newlines)
Just in case, to replace multiple characters in one word an asterisk should suffice: trimmed="${trimmed%% *}";, trimmed="${trimmed##* }";.
56

You can trim simply with echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'

5 Comments

This collapses multiple contiguous spaces into one.
Did you try it when foo contains a wildcard? e.g., foo=" I * have a wild card"... surprise! Moreover this collapses several contiguous spaces into one.
This is an excellent solution if you: 1. want no spaces on the ends 2. want only one space between each word 3. are working with a controlled input with no wildcards. It essentially turns a badly formatted list into a good one.
Good reminder of the wildcards @gniourf_gniourf +1. Still an excelente solution, Vamp. +1 to you too.
Despite the problem with wildcharts and space in the middle, it is simple and does not need an external tool.
53

From Bash Guide section on globbing

To use an extglob in a parameter expansion

 #Turn on extended globbing  
shopt -s extglob  
 #Trim leading and trailing whitespace from a variable  
x=${x##+([[:space:]])}; x=${x%%+([[:space:]])}  
 #Turn off extended globbing  
shopt -u extglob  

Here's the same functionality wrapped in a function (NOTE: Need to quote input string passed to function):

trim() {
    # Determine if 'extglob' is currently on.
    local extglobWasOff=1
    shopt extglob >/dev/null && extglobWasOff=0 
    (( extglobWasOff )) && shopt -s extglob # Turn 'extglob' on, if currently turned off.
    # Trim leading and trailing whitespace
    local var=$1
    var=${var##+([[:space:]])}
    var=${var%%+([[:space:]])}
    (( extglobWasOff )) && shopt -u extglob # If 'extglob' was off before, turn it back off.
    echo -n "$var"  # Output trimmed string.
}

Usage:

string="   abc def ghi  ";
#need to quote input-string to preserve internal white-space if any
trimmed=$(trim "$string");  
echo "$trimmed";

If we alter the function to execute in a subshell, we don't have to worry about examining the current shell option for extglob, we can just set it without affecting the current shell. This simplifies the function tremendously. I also update the positional parameters "in place" so I don't even need a local variable

trim() {
    shopt -s extglob
    set -- "${1##+([[:space:]])}"
    printf "%s" "${1%%+([[:space:]])}" 
}

so:

$ s=$'\t\n \r\tfoo  '
$ shopt -u extglob
$ shopt extglob
extglob         off
$ printf ">%q<\n" "$s" "$(trim "$s")"
>$'\t\n \r\tfoo  '<
>foo<
$ shopt extglob
extglob         off

6 Comments

as you've observed trim() removes leading and trailing whitespace only.
As mkelement has already noted you need to pass the function parameter as a quoted string i.e. $(trim "$string") instead of $(trim $string). I've updated the code to show the correct usage. Thanks.
As much as I appreciate knowing about the shell options, I don't think the end result is more elegant than simply doing 2 pattern substitutions
Note that (with a recent enough version of Bash?), you can simplify the mechanism for restoring the option extglob, by using shopt -p: simply write local restore="$(shopt -p extglob)" ; shopt -s extglob at the beginning of your function, and eval "$restore" at the end (except, granted, eval is evil…).
Great solution! One potential improvement: it looks like [[:space:]] could be replaced with, well, a space: ${var##+( )} and ${var%%+( )} work as well and they are easier to read.
|
34

I've always done it with sed

  var=`hg st -R "$path" | sed -e 's/  *$//'`

If there is a more elegant solution, I hope somebody posts it.

8 Comments

could you explain the syntax for sed?
The regular expression matches all trailing whitespace and replaces it with nothing.
How about leading whitespaces?
This strips all trailing whitespace sed -e 's/\s*$//'. Explanation: 's' means search, the '\s' means all whitespace, the '*' means zero or many, the '$' means till the end of the line and '//' means substitute all matches with an empty string.
In 's/ *$//', why are there 2 spaces before the asterisk, instead of a single space? Is that a typo?
|
29

With Bash's extended pattern matching features enabled (shopt -s extglob), you can use this:

{trimmed##*( )}

to remove an arbitrary amount of leading spaces.

6 Comments

Terrific! I think this is the most lightweight and elegant solution.
See @GuruM's post below for a similar, but more generic solution that (a) deals with all forms of white space and (b) also handles trailing white space.
@mkelement +1 for taking the trouble to rewrite my code snippet as a function. Thanks
Works with OpenBSD's default /bin/ksh as well. /bin/sh -o posix works too but I'm suspicious.
Not a bash wizard here; what's trimmed? Is it a built-in thing or the variable that is being trimmed?
|
28
# Trim whitespace from both ends of specified parameter

trim () {
    read -rd '' $1 <<<"${!1}"
}

# Unit test for trim()

test_trim () {
    local foo="$1"
    trim foo
    test "$foo" = "$2"
}

test_trim hey hey &&
test_trim '  hey' hey &&
test_trim 'ho  ' ho &&
test_trim 'hey ho' 'hey ho' &&
test_trim '  hey  ho  ' 'hey  ho' &&
test_trim $'\n\n\t hey\n\t ho \t\n' $'hey\n\t ho' &&
test_trim $'\n' '' &&
test_trim '\n' '\n' &&
echo passed

6 Comments

Amazing! Simple and effective! Clearly my favorite solution. Thank you!
very ingenious, as it is also extremely an "one liner" helper read -rd '' str < <(echo "$str") thx!
The trim() function's parameter is a variable name: see the call to trim() inside test_trim(). Within trim() as called from test_trim(), $1 expands to foo and ${!1} expands to $foo (that is, to the current contents of variable foo). Search the bash manual for 'variable indirection'.
How about this little modification, to support trimming multiple vars in one call? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
@AquariusPower there's no need to use echo in a subshell for the one-liner version, just read -rd '' str <<<"$str" will do.
|
26

There are a lot of answers, but I still believe my just-written script is worth being mentioned because:

  • it was successfully tested in the shells bash/dash/busybox shell
  • it is extremely small
  • it doesn't depend on external commands and doesn't need to fork (->fast and low resource usage)
  • it works as expected:
    • it strips all spaces and tabs from beginning and end, but not more
    • important: it doesn't remove anything from the middle of the string (many other answers do), even newlines will remain
    • special: the "$*" joins multiple arguments using one space. if you want to trim & output only the first argument, use "$1" instead
    • if doesn't have any problems with matching file name patterns etc

The script:

trim() {
  local s2 s="$*"
  until s2="${s#[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  until s2="${s%[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  echo "$s"
}

Usage:

mystring="   here     is
    something    "
mystring=$(trim "$mystring")
echo ">$mystring<"

Output:

>here     is
    something<

7 Comments

Bah in C this would be way simpler to implement!
Sure. Unfortunately, this is not C and sometimes you want to avoid calling external tools
To make the code both more readable and copy-past compatible, you could change the brackets to escaped characters: [\ \t]
@leondepeon did you try this? I tried when i wrote it and tried again, and your suggestion doesn't work in any of bash, dash, busybox
Among all the answers, this is the only one that that I wanted and it works like python function strip()
|
24

You can delete newlines with tr:

var=`hg st -R "$path" | tr -d '\n'`
if [ -n $var ]; then
    echo $var
done

2 Comments

I don't want to remove '\n' from the middle of the string, only from the beginning or end.
The original question was wrong when it assumed that the problem was a trailing newline; the var=`hg...` would already have removed the newline if that was the case. (The actual problem was some other output.)
17

This is what I did and worked out perfect and so simple:

the_string="        test"
the_string=`echo $the_string`
echo "$the_string"

Output:

test

3 Comments

I tried two other solution from this question/answer page and only this one worked as expected, keep-it-simple-silly strikes again!!
You could even use process command substitution but I guess that won't work for Bourne Shell
will not work if the_string contains wildcards, e.g. "the_string=" foo * bar "
14

If you have shopt -s extglob enabled, then the following is a neat solution.

This worked for me:

text="   trim my edges    "

trimmed=$text
trimmed=${trimmed##+( )} #Remove longest matching series of spaces from the front
trimmed=${trimmed%%+( )} #Remove longest matching series of spaces from the back

echo "<$trimmed>" #Adding angle braces just to make it easier to confirm that all spaces are removed

#Result
<trim my edges>

To put that on fewer lines for the same result:

text="    trim my edges    "
trimmed=${${text##+( )}%%+( )}

9 Comments

Didn't work for me. The first one printed an untrimmed string. The second threw bad substitution. Can you explain what's going on here?
@musicin3d : this is a site I use frequently that spells out how variable manipulation works in bash search for ${var##Pattern} for more details. Also, this site explains bash patterns. So the ## means remove the given pattern from the front and %% means remove the given pattern from the back. The +( ) portion is the pattern and it means "one or more occurrence of a space"
Funny, it worked in the prompt, but not after transposing to the bash script file.
weird. Is it the same bash version in both instances?
@DrBeco #!/bin/sh is not necessarily bash ;)
|
13

You can use old-school tr. For example, this returns the number of modified files in a git repository, whitespaces stripped.

MYVAR=`git ls-files -m|wc -l|tr -d ' '`

2 Comments

This doesn't trim whitespace from the front and back - it removes all whitespace from the string.
You can use tr -s " " to squeeze all spaces into one space but that may still leave a leading and trailing space, but you may have ways of dealing with that more easily. I'm not sure how this works with newlines. Probably not what the OP wants.
13
# Strip leading and trailing white space (new line inclusive).
trim(){
    [[ "$1" =~ [^[:space:]](.*[^[:space:]])? ]]
    printf "%s" "$BASH_REMATCH"
}

OR

# Strip leading white space (new line inclusive).
ltrim(){
    [[ "$1" =~ [^[:space:]].* ]]
    printf "%s" "$BASH_REMATCH"
}

# Strip trailing white space (new line inclusive).
rtrim(){
    [[ "$1" =~ .*[^[:space:]] ]]
    printf "%s" "$BASH_REMATCH"
}

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1")")"
}

OR

# Strip leading and trailing specified characters.  ex: str=$(trim "$str" $'\n a')
trim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^["$trim_chrs"]*(.*[^"$trim_chrs"])["$trim_chrs"]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

OR

# Strip leading specified characters.  ex: str=$(ltrim "$str" $'\n a')
ltrim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^["$trim_chrs"]*(.*[^"$trim_chrs"]) ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# Strip trailing specified characters.  ex: str=$(rtrim "$str" $'\n a')
rtrim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^(.*[^"$trim_chrs"])["$trim_chrs"]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# Strip leading and trailing specified characters.  ex: str=$(trim "$str" $'\n a')
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1" "$2")" "$2")"
}

OR

Building upon moskit's expr soulution...

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "`expr "$1" : "^[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*$"`"
}

OR

# Strip leading white space (new line inclusive).
ltrim(){
    printf "%s" "`expr "$1" : "^[[:space:]]*\(.*[^[:space:]]\)"`"
}

# Strip trailing white space (new line inclusive).
rtrim(){
    printf "%s" "`expr "$1" : "^\(.*[^[:space:]]\)[[:space:]]*$"`"
}

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1")")"
}

Comments

13

There are a few different options purely in BASH:

line=${line##+([[:space:]])}    # strip leading whitespace;  no quote expansion!
line=${line%%+([[:space:]])}   # strip trailing whitespace; no quote expansion!
line=${line//[[:space:]]/}   # strip all whitespace
line=${line//[[:space:]]/}   # strip all whitespace

line=${line//[[:blank:]]/}   # strip all blank space

The former two require extglob be set/enabled a priori:

shopt -s extglob  # bash only

NOTE: variable expansion inside quotation marks breaks the top two examples!

The pattern matching behaviour of POSIX bracket expressions are detailed here. If you are using a more modern/hackable shell such as Fish, there are built-in functions for string trimming.

Comments

12

Use AWK:

echo $var | awk '{gsub(/^ +| +$/,"")}1'

2 Comments

Sweet that seems to work (ex:) $stripped_version=echo $var | awk '{gsub(/^ +| +$/,"")}1'``
except awk isn't doing anything: echo'ing an unquoted variable has already stripped out whitespace
10

This will remove all the whitespaces from your String,

 VAR2="${VAR2//[[:space:]]/}"

/ replaces the first occurrence and // all occurrences of whitespaces in the string. I.e. all white spaces get replaced by – nothing

Comments

10

A simple answer is:

sed 's/^\s*\|\s*$//g'

An example:

$ before=$( echo -e " \t a  b \t ")
$ echo "(${before})"
(    a  b    )

$ after=$( echo "${before}"  |  sed 's/^\s*\|\s*$//g' )
$ echo "(${after})"
(a  b)

1 Comment

OP was hoping to get something "more elegant" than sed or awk, but other answers arguably demonstrate there isn't... Thus this standard sed approach is probably the right answer. More sed and awk answers on Unix SE. Also, if 1 consistent space is suitable for your solution then squeezing all spaces into 1 may be nicer: ... | tr -s " " | ...
8

I would simply use sed:

function trim
{
    echo "$1" | sed -n '1h;1!H;${;g;s/^[ \t]*//g;s/[ \t]*$//g;p;}'
}

a) Example of usage on single-line string

string='    wordA wordB  wordC   wordD    '
trimmed=$( trim "$string" )

echo "GIVEN STRING: |$string|"
echo "TRIMMED STRING: |$trimmed|"

Output:

GIVEN STRING: |    wordA wordB  wordC   wordD    |
TRIMMED STRING: |wordA wordB  wordC   wordD|

b) Example of usage on multi-line string

string='    wordA
   >wordB<
wordC    '
trimmed=$( trim "$string" )

echo -e "GIVEN STRING: |$string|\n"
echo "TRIMMED STRING: |$trimmed|"

Output:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

c) Final note:
If you don't like to use a function, for single-line string you can simply use a "easier to remember" command like:

echo "$string" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Example:

echo "   wordA wordB wordC   " | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Output:

wordA wordB wordC

Using the above on multi-line strings will work as well, but please note that it will cut any trailing/leading internal multiple space as well, as GuruM noticed in the comments

string='    wordAA
    >four spaces before<
 >one space before<    '
echo "$string" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Output:

wordAA
>four spaces before<
>one space before<

So if you do mind to keep those spaces, please use the function at the beginning of my answer!

d) EXPLANATION of the sed syntax "find and replace" on multi-line strings used inside the function trim:

sed -n '
# If the first line, copy the pattern to the hold buffer
1h
# If not the first line, then append the pattern to the hold buffer
1!H
# If the last line then ...
$ {
    # Copy from the hold to the pattern buffer
    g
    # Do the search and replace
    s/^[ \t]*//g
    s/[ \t]*$//g
    # print
    p
}'

6 Comments

Note: As suggested by @mkelement it will not work for multi-line string though it should work for single-line strings.
You are wrong: it does work on multi-line strings too. Just test it out!:)
+1 for the usage - made it easy for me to test out the code. However the code still won't work for multi-line strings. If you look carefully at the output, you'll notice that any leading/trailing internal spaces are also getting removed e.g. the space in front of " multi-line" is replaced by "multi-line". Just try increasing the number of leading/trailing spaces on each line.
Now I see what you mean! Thank you for the head up, I edited my answer.
@"Luca Borrione" - welcome :-) Would you explain the sed syntax you're using in trim()? It might also help any user of your code to tweak it to other uses. Also it might even help find edge-cases for the regular-expression.
|
6

I've seen scripts just use variable assignment to do the job:

$ xyz=`echo -e 'foo \n bar'`
$ echo $xyz
foo bar

Whitespace is automatically coalesced and trimmed. One has to be careful of shell metacharacters (potential injection risk).

I would also recommend always double-quoting variable substitutions in shell conditionals:

if [ -n "$var" ]; then

since something like a -o or other content in the variable could amend your test arguments.

2 Comments

It is the unquoted use of $xyz with echo that does the whitespace coalescing, not the variable assignment. To store the trimmed value in the variable in your example, you'd have to use xyz=$(echo -n $xyz). Also, this approach is subject to potentially unwanted pathname expansion (globbing).
this is jut wrong, the value in the xyz variable is NOT trimmed.
6

Here's a trim() function that trims and normalizes whitespace

#!/bin/bash
function trim {
    echo $*
}

echo "'$(trim "  one   two    three  ")'"
# 'one two three'

And another variant that uses regular expressions.

#!/bin/bash
function trim {
    local trimmed="$@"
    if [[ "$trimmed" =~ " *([^ ].*[^ ]) *" ]]
    then 
        trimmed=${BASH_REMATCH[1]}
    fi
    echo "$trimmed"
}

echo "'$(trim "  one   two    three  ")'"
# 'one   two    three'

3 Comments

The first approach is tricky in that it not only normalizes interior whitespace (replaces all interior spans of whitespace with a single space each) , but is also subject to globbing (pathname expansion) so that, for instance, a * character in the input string would expand to all files and folders in the current working folder. Finally, if $IFS is set to a non-default value, trimming may not work (though that is easily remedied by adding local IFS=$' \t\n'). Trimming is limited to the following forms of whitespace: spaces, \t and \n characters.
The second, regular expression-based approach is great and side effect-free, but in its present form is problematic: (a) on bash v3.2+, matching will by default NOT work, because the regular expression must be UNquoted in order to work and (b) the regular expression itself doesn't handle the case where the input string is a single, non-space character surrounded by spaces. To fix these problems, replace the if line with: if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Finally, the approach only deals with spaces, not other forms of whitespace (see my next comment).
The function that utilizes regular expresssions only deals with spaces and not other forms of whitespace, but it's easy to generalize: Replace the if line with: [[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
6

This does not have the problem with unwanted globbing, also, interior white-space is unmodified (assuming that $IFS is set to the default, which is ' \t\n').

It reads up to the first newline (and doesn't include it) or the end of string, whichever comes first, and strips away any mix of leading and trailing space and \t characters. If you want to preserve multiple lines (and also strip leading and trailing newlines), use read -r -d '' var << eof instead; note, however, that if your input happens to contain \neof, it will be cut off just before. (Other forms of white space, namely \r, \f, and \v, are not stripped, even if you add them to $IFS.)

read -r var << eof
$var
eof

Comments

6

To remove spaces and tabs from left to first word, enter:

echo "     This is a test" | sed "s/^[ \t]*//"

cyberciti.biz/tips/delete-leading-spaces-from-front-of-each-word.html

Comments

6
var='   a b c   '
trimmed=$(echo $var)

1 Comment

That won't work if there are more than one space in between any two words. Try: echo $(echo "1 2 3") (with two spaces between 1, 2, and 3).
6

Assignments ignore leading and trailing whitespace and as such can be used to trim:

$ var=`echo '   hello'`; echo $var
hello

2 Comments

That's not true. It's "echo" that removes whitespace, not the assignment. In your example, do echo "$var" to see the value with spaces.
@NicholasSushkin One could do var=$(echo $var) but I do not recommend it. Other solutions presented here are preferred.
5

This is the simplest method I've seen. It only uses Bash, it's only a few lines, the regexp is simple, and it matches all forms of whitespace:

if [[ "$test" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]
then 
    test=${BASH_REMATCH[1]}
fi

Here is a sample script to test it with:

test=$(echo -e "\n \t Spaces and tabs and newlines be gone! \t  \n ")

echo "Let's see if this works:"
echo
echo "----------"
echo -e "Testing:${test} :Tested"  # Ugh!
echo "----------"
echo
echo "Ugh!  Let's fix that..."

if [[ "$test" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]
then 
    test=${BASH_REMATCH[1]}
fi

echo
echo "----------"
echo -e "Testing:${test}:Tested"  # "Testing:Spaces and tabs and newlines be gone!"
echo "----------"
echo
echo "Ah, much better."

3 Comments

Surely preferable to, for example (ye gods!), shelling out to Python. Except I think it's simpler and more general to correctly handle string that contains only spaces.Slightly simplified expression would be: ^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Does not work for single characters. You force match for "not space, anything, not space" in the middle. Thus at least two chars must exist. Therefore the input " f " breaks here. But @RonBurk has provided a decent solution, which removes "zero or more whitespace from start greedily", then matches "anything, greedily" which will go to the end of the entire string, then it will backtrack until it finds the last non-whitespace character, then it will look for zero or more whitespace characters until the end ($), but if this did not match, it backtracks and allows empty/whitespace-only str.
Another nice thing about RonBurk's solution is that the ? which turns the "non-whitespace match" into OPTIONAL also optimizes the entire regex, allowing it to complete in just 9 steps instead of THOUSANDS of steps if a string consists of only whitespace. For example, a 140 character long string consisting ONLY of SPACES needs 10000 steps to check for matches if ? is removed, but only 9 steps if ? is used. So not did he solve the problem. He did it BEAUTIFULLY. His regex matches every time regardless of input length (even empty), and "${BASH_REMATCH[1]}" contains the clean text.
5

Removing spaces to one space:

(text) | fmt -su

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.