1309

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.

codeforester
  • 39,467
  • 16
  • 112
  • 140
too much php
  • 88,666
  • 34
  • 128
  • 138
  • 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. – Volomike Dec 10 '12 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` – user.friendly Apr 26 '16 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) – Luis Alvarado Jul 19 '16 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. – Mecki Jun 19 '17 at 17:02
  • A="123 4 5 6 "; B=`echo $A | sed -r 's/( )+//g'`; – Konstantin Burlachenko Jul 17 '18 at 11:25
  • This question would benefit from one of the answers being excepted. – Jan Groth Jun 02 '21 at 00:15
  • As @mecki alluded to, if the code exactly as given does not work, that's because output _isn't_ solely a newline; perhaps there's a carriage return or other unprintable character? (All the other answers instructing you to use "trim" are entirely missing the point.) – Martin Kealey Jul 30 '22 at 04:03

52 Answers52

1521

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.

rkachach
  • 16,517
  • 6
  • 42
  • 66
makevoid
  • 3,276
  • 2
  • 33
  • 29
  • 45
    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. – Will Feb 18 '13 at 18:59
  • On Windows this is a very good solution. I usually use gnu utils binaries without bash (eg. cygwin, unxutils, custom builds, mingw, git for windows, gnuwin32...) and `sed` and `awk` support is not very consistent across these. – n611x007 Oct 14 '13 at 15:29
  • 1
    Also, both `sed`, `awk` and `bash` are tools so old that they use a syntax that can feel "arcane". People who are not often using them, wanting to do a simple string manipulation, would easily feel overwhelmed by `'` or `"` or \ or `/` or `//` or `^` or `$`, asking themselves *just which one was it? Can't believe there is no more straightforward solution to this simple thing...* There is. – n611x007 Oct 14 '13 at 15:44
  • An other reason for using `xargs` is why introduce dependency? xargs is a binary that can be used with other shells (I even managed to use it with `cmd.exe`). Since it's quite straightforward it doesn't depend on any learning curve as a tool for being immediately useful either. – n611x007 Oct 14 '13 at 15:53
  • Using `grep .` can further prevent the output of empty lines. – Asclepius Nov 27 '13 at 19:31
  • 34
    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. – Jocelyn delalande Nov 28 '13 at 09:57
  • 36
    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. –  Jan 07 '15 at 04:30
  • 15
    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. – bos Jan 24 '15 at 23:12
  • 114
    This is bad. 1. It will turn `ab` into `ab`. 2. Even more: it will turn `a"b"c'd'e` into `abcde`. 3. Even more: it will fail on `a"b`, etc. – Sasha Feb 11 '15 at 23:20
  • 1
    @makevoid Why did you remove the (very important) warning from this answer? This doesn't change the behaviour of `xargs`... – Daniel Alder Feb 24 '15 at 12:27
  • 1
    @DanielAlder added the note, I removed that because it was too long, notes have to be short - a too long answer can be considered wrong sometimes because of TL;DR :) – makevoid Mar 25 '15 at 13:40
  • 5
    I've been using xargs to trim simple strings until I discovered it fails on strings with a single quote or multiple internal spaces (as was warned above). I can't recommend this method. – Quinn Comendant May 07 '15 at 22:40
  • 2
    This is a very quick solution when trimming simple single-word strings. I used this to strip sqlite's -line output, where the column name has useless padding script-wise. – F-3000 Apr 12 '16 at 04:00
  • 2
    I understand the downsides of this, but it's great for when you know the expected output is going to be just one value, e.g. from `wc -l`. I use this for removing leading spaces (which seem to be included by the macOS version of `wc`): `ls .. | wc -l | xargs` – nwinkler Jun 27 '17 at 06:30
  • 1
    @AlexanderMills thanks for the sincerity.... maybe try with this? `A=$(echo " lol " | xargs); echo $A` – makevoid Jul 14 '17 at 16:37
  • @makevoid, as for "added the note", [the note you've added](/revisions/12973694/6) is very different from [the previous warning](/revisions/12973694/4). The previous warning said that this approach **changes inner spaces** (I'd better say: _ruins them_). While your note just says that the inner spaces **wouldn't be fully removed** (almost the opposite thing). – Sasha Jul 25 '19 at 06:24
  • To continuously trim all stdin lines, run it through `xargs -l`. – Victor Sergienko Aug 23 '19 at 22:59
  • this is bad, it removes backslash from string – POMATu Oct 01 '19 at 13:03
  • 2
    you are a hero. much easier then `perl/sed/bashism` –  Nov 07 '19 at 10:23
  • 2
    this is misuse. `xargs` is made for another purpose and might change trimming behaviour anytime. it's bad advice to use this for trimming. As @Sasha already pointed out: it already has some weird behaviours you would not expect when trimming strings! – Kaii Feb 21 '20 at 20:57
  • `xargs <<< " test teste grdgdft"` – Kévin Berthommier Dec 07 '20 at 15:13
  • This answer doesn't remove \r or CR from the end of a variable. – black.swordsman Sep 11 '21 at 20:11
  • 1
    This removes surrounding quotes from the content. – Soren Bjornstad Jul 30 '22 at 14:53
1187

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})"
squirl
  • 1,636
  • 1
  • 16
  • 30
MattyV
  • 835
  • 1
  • 6
  • 5
  • 69
    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]}")` – mklement0 Jun 10 '12 at 14:05
  • 10
    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`. – instanceof me Mar 19 '13 at 15:59
  • I wrote a `sed` solution that only uses a single expression rather than two: `sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'`. It trims leading and trailing whitespace, and captures any whitespace-separated sequences of non-whitespace characters in the middle. Enjoy! – Victor Zamanian Feb 06 '14 at 17:41
  • @VictorZamanian Your solution does not work if the input contains only whitespace. The two-pattern sed solutions given by MattyV and instanceof me work fine with whitespace only input. – Torben Apr 09 '14 at 07:38
  • @Torben Fair point. I suppose the single expression could be made conditional, with `|`, so as to keep it as one single expression, rather than several. – Victor Zamanian Apr 09 '14 at 13:59
  • 1
    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. –  Jul 19 '15 at 05:58
  • 1
    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:]]`. – vinc17 Apr 26 '16 at 11:33
  • I was very confused by the -e option of the echo command. I'm working on a Mac and it's not recognised. Or at least not documented in the man page. `FOO=' test test test ' FOO_NO_WHITESPACE=$(echo "${FOO}" | tr -d '[:space:]')` works fine for me. – Mig82 Feb 13 '17 at 09:26
  • 2
    Note that `tr -d "[:space:]"` removes both horizontal and *vertical* whitespace characters (=newlines). To remove just horizontal whitespace characters simply use `tr -d " "`. – scai Nov 13 '18 at 07:48
  • Bumped you to 1000 since it had been sitting at 999 for some time :-D – Harlin Feb 29 '20 at 16:08
  • bash is evil lulz for requiring so long an answer to "how to trim whitespace" isn't there some coreutils program that can do this? xargs is one I guess but not explicitly made for that. –  Mar 18 '20 at 20:09
  • 1
    It takes lots of examples because trimming a whitespace is about as easy as trimming your own hair. – David Scott Kirby Mar 11 '21 at 22:36
  • Except in this world it's easier to trim the back, you can barely see the top, and your clippers behave wildly differently depending on where you use them. – David Scott Kirby Mar 11 '21 at 22:44
  • Try `sed -e 's/^[[:space:]]*//;s/[[:space:]]*$//'` and eliminate the repeated edit (`-e`) flags. ... Better yet, use extended flags (`-E`) and try a single expression `sed -E 's/^[ ]*|[ ]*$//;'`! – ingyhere Sep 07 '22 at 16:23
  • **️** borrowing some lines to make 1 that removes trailing ',' chars, I often had this problem but now it works flawlessly, thanks for this flawless answ! `echo ', a , b , c,' | sed -e 's/,*$//' ##, a , b , c` – William Martens Oct 16 '22 at 11:11
501

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

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
bashfu
  • 249
  • 1
  • 3
  • 2
  • I tried your solution but using local var="$@" compresses __multiple internal-spaces__ into __a single space__. So a string with multiple-space like " ab c d " would translate to single-spaced "a b c d". The only way as suggested by @mkelement is to quote the input variable directly i.e. trim "$string" in which case $@ expands to a single parameter anyway. – GuruM Sep 18 '12 at 14:52
  • 26
    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! – Mark G. Nov 12 '14 at 17:04
  • 2
    I haven't found any unwanted side effects, and the main code works with other POSIX-like shells too. However, under Solaris 10, it doesn't work with `/bin/sh` (only with `/usr/xpg4/bin/sh`, but this is not what will be used with usual sh scripts). – vinc17 Apr 26 '16 at 11:57
  • Well, let's say that this is also OK for Solaris 10 even without bash, where one can put `{ a=a; : ${a%b}; } 2> /dev/null || exec /usr/xpg4/bin/sh -- "$0" ${1+"$@"}` at the beginning of the script to use a compatible shell. – vinc17 Apr 26 '16 at 12:12
  • 14
    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. – Gene Pavlovsky Jul 03 '16 at 09:33
  • 13
    @San At first I was stumped because I thought these were regular expressions. They are not. Rather, this is Pattern Matching syntax (https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html, http://wiki.bash-hackers.org/syntax/pattern) used in Substring Removal (http://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. – Ohad Schneider Jan 25 '17 at 18:50
  • 28
    **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. – Cecil Curry Aug 14 '18 at 20:44
  • This was an excellent solution. Just what I was looking for. To use the built-ins. Now I just need the codebook to decipher all of it. Is this solution based on years of experience? I didn't know how BASH could do this, but I had a feeling it should be able to do it. – Ken Ingram Apr 02 '19 at 13:10
  • 1
    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. – Isaac Freeman Aug 01 '19 at 14:25
  • 1
    Oh, and I guess the solution in my last comment requires `shopt -s extglob`. – Isaac Freeman Aug 01 '19 at 14:38
  • @CecilCurry Getting the result of `trim` into a variable would still require command substitution and a subshell, e.g. `var=$(trim " abc ")`, so in a shell script the advantage over piping is minimal, right? – miguelmorin Dec 19 '19 at 12:39
  • downvote because this doesn't give any explanation. it doesn't work for me and I just don't know why (need \r\n removed that was added by recent winepath) – JPT Apr 14 '20 at 14:58
  • @akhan, Yes, quotes are NOT needed in variable assignment in BASH: https://stackoverflow.com/questions/3958681/quoting-vs-not-quoting-the-variable-on-the-rhs-of-a-variable-assignment https://unix.stackexchange.com/questions/97560/are-quotes-needed-for-local-variable-assignment – Alek Apr 23 '21 at 09:11
  • Great. Thanks for the function - added to my local library of valuabe functions. – Zane Jul 04 '21 at 19:07
  • 1
    You can add support for piped input, stdin, etc. with a single line addition. After the line `local var="$*"`, add a new line `(( $# )) || read -re var`. Now this function can be called as in `echo " hello " | trim` to produce just `hello`. – ardnew Nov 19 '21 at 21:12
  • @ardnew why `-e`? – shadowtalker Aug 31 '23 at 17:57
  • @shadowtalker Probably just habit! Since input is _not_ coming from a keyboard in this case, I'm not sure `-e` (Readline support on my system) provides any benefit and might actually be slower than native parsing. – ardnew Aug 31 '23 at 21:02
118

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'
rkachach
  • 16,517
  • 6
  • 42
  • 66
  • 8
    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). – rkachach Mar 08 '17 at 15:57
  • 1
    Why do you need `echo -n` at all? `echo " my string " | xargs` has the same output. – bfontaine Mar 20 '19 at 09:26
  • 7
    echo -n removes the end of line as well – rkachach Mar 20 '19 at 09:35
  • 1
    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. – AndrewF Aug 30 '22 at 19:52
112

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
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 2
    Or rather, it works for spaces in the middle of a var, but not when I attempt to anchor it at the end. – Paul Tomblin Dec 15 '08 at 21:56
  • Does this help any? From the manpage: "${parameter/pattern/string} [...] If pattern begins with %, it must match at the end of the expanded value of parameter." –  Dec 15 '08 at 22:28
  • @Ant, so they're not really regular expressions, but something similar? – Paul Tomblin Dec 15 '08 at 22:40
  • 3
    They're regex, just a strange dialect. –  Mar 05 '09 at 12:36
  • to replace all matches echo ${var// } – Peter Lindqvist Nov 27 '09 at 10:01
  • 25
    `${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. – Gilles 'SO- stop being evil' Jun 03 '12 at 19:47
  • @PaulTomblin: the bash manual calls them _patterns_ and while they share some features with regular expressions, they're sufficiently different to warrant _not_ calling them regular expressions so as to avoid confusion. – mklement0 Jun 04 '12 at 13:51
  • I think that was the point I was making - that flussence was calling them regular expressions, but they're not. – Paul Tomblin Jun 04 '12 at 13:52
  • **Bash patterns are not regular expressions.** Bash supports both Bash pattern syntax *and* [genuine regular expressions](https://www.networkworld.com/article/2693361/operating-systems/unix-tip-using-bash-s-regular-expressions.html) (e.g., via the builtin `=~` operator introduced with Bash 3.0.0). The two have nothing to do with one another. – Cecil Curry Aug 14 '18 at 20:53
  • 2
    Bash “patterns” _are_ [regular expressions](https://en.wikipedia.org/wiki/Regular_expression), 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`). – Maëlan Mar 24 '19 at 22:44
  • FWIW, `var="${var//[[:space:]]/}"` works fine in re-assigning the trimmed contents back to the original variable. – JL Peyret Sep 01 '20 at 18:01
  • thanks works great for inline use of `for $i do { some command expecting $i arg with spaces > path/output-catpure-$i.log };` liked that i could use `path/ouput-capture-$(echo -n "${i//[[:space:]]/-}").log` to get with a dash replacing spaces instead of removing them. – myusrn Aug 29 '22 at 19:50
  • `$ echo -n "${var//[[:space:]]/}"` is what worked for cleaning up the stored vars in my case. There were no visible spaces, must have been some kind of hidden return or other character that matched a space... – Peter Kionga-Kamau Oct 27 '22 at 06:38
77

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'
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Brian Cain
  • 57
  • 2
  • 2
  • 9
    This will trim only 1 space character. So the echo results in `'hello world ', 'foo bar', 'both sides '` – Joe Jul 14 '14 at 15:33
  • 1
    @Joe I added a better option. – wjandrea Dec 09 '17 at 02:09
  • 1
    This helped me, but the question specifically asked about a trailing newlin which this does not remove. – andrew lorien May 12 '21 at 08:53
  • 1
    @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) – Martin Kealey Jul 30 '22 at 06:15
52

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
Volte
  • 1,905
  • 18
  • 25
GuruM
  • 865
  • 11
  • 20
  • 2
    as you've observed trim() removes leading and trailing whitespace only. – GuruM Sep 12 '12 at 13:03
  • 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. – GuruM Sep 18 '12 at 09:57
  • 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 – sehe Nov 01 '16 at 12:33
  • 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…). – Maëlan Mar 24 '19 at 22:56
  • 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. – Dima Korobskiy May 25 '20 at 14:41
  • While using builtin functionality seems nice, extglob is *very* slow in my experience. – Michał Górny Jul 02 '20 at 06:58
49

You can trim simply with echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
VAmp
  • 1
  • 2
  • 2
  • This collapses multiple contiguous spaces into one. – Evgeni Sergeev Dec 16 '13 at 06:13
  • 10
    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. – gniourf_gniourf Apr 28 '14 at 08:47
  • 6
    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. – musicin3d Nov 19 '15 at 15:25
  • Good reminder of the wildcards @gniourf_gniourf +1. Still an excelente solution, Vamp. +1 to you too. – DrBeco Mar 21 '16 at 01:05
  • Despite the problem with wildcharts and space in the middle, it is simple and does not need an external tool. – Peter Steier Jun 21 '23 at 07:03
35

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.

Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • could you explain the syntax for `sed`? – farid99 Jul 08 '15 at 20:21
  • 2
    The regular expression matches all trailing whitespace and replaces it with nothing. – Paul Tomblin Jul 08 '15 at 20:23
  • 6
    How about leading whitespaces? –  Feb 10 '16 at 06:28
  • 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. – Craig Apr 12 '16 at 12:21
  • In 's/ *$//', why are there 2 spaces before the asterisk, instead of a single space? Is that a typo? – Brent212 Mar 19 '20 at 18:01
  • @Brent212 because you don't want to match unless there is at least one space. `'s/ +$//'` might work as well. – Paul Tomblin Mar 20 '20 at 16:30
  • @PaulTomblin sure, I get that we only want to match one or more spaces, but wouldn't 's/ *$//' do the trick, instead of 's/ *$//' ? The later seems like it'd only match two or more spaces. – Brent212 Mar 21 '20 at 17:20
  • You’re thinking of glob, not regex. * means 0 or more of the previous thing, so s/ *$// will match on zero spaces. – Paul Tomblin Mar 22 '20 at 06:58
28

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

{trimmed##*( )}

to remove an arbitrary amount of leading spaces.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mooshu
  • 141
  • 2
  • 3
24

You can delete newlines with tr:

var=`hg st -R "$path" | tr -d '\n'`
if [ -n $var ]; then
    echo $var
done
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • 15
    I don't want to remove '\n' from the middle of the string, only from the beginning or end. – too much php Dec 15 '08 at 21:48
  • 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.) – Martin Kealey Jul 30 '22 at 06:19
24
# 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
flabdablet
  • 3,565
  • 3
  • 22
  • 15
  • 2
    Amazing! Simple and effective! Clearly my favorite solution. Thank you! – xebeche Aug 02 '12 at 11:21
  • 1
    very ingenious, as it is also extremely an "one liner" helper `read -rd '' str < <(echo "$str")` thx! – Aquarius Power Apr 02 '15 at 20:53
  • 1
    @CraigMcQueen it is the variable value, as `read` will store at variable by its name $1 a trimmed version of its value ${!1} – Aquarius Power Apr 02 '15 at 20:54
  • 2
    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'. – flabdablet Apr 09 '15 at 12:54
  • 2
    How about this little modification, to support trimming multiple vars in one call? ```trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }``` – Gene Pavlovsky Jul 03 '16 at 09:37
  • 4
    @AquariusPower there's no need to use echo in a subshell for the one-liner version, just `read -rd '' str <<<"$str"` will do. – flabdablet Jul 03 '16 at 19:08
22

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<
Daniel Alder
  • 5,031
  • 2
  • 45
  • 55
  • Bah in C this would be way simpler to implement! – Nils Oct 14 '16 at 10:47
  • 1
    Sure. Unfortunately, this is not C and sometimes you want to avoid calling external tools – Daniel Alder Oct 14 '16 at 11:02
  • To make the code both more readable and copy-past compatible, you could change the brackets to escaped characters: `[\ \t]` – Leon S. Jan 21 '17 at 23:06
  • @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 – Daniel Alder May 26 '20 at 10:19
  • @DanielAlder I did, but as it is already 3 years ago, I can't find the code where I used it. Now however, I'd probably use `[[:space:]]` like in one of the other answers: https://stackoverflow.com/a/3352015/3968618 – Leon S. May 26 '20 at 13:10
  • I've tried your solution, but it fails with non-breaking space at the end (https://en.wikipedia.org/wiki/Non-breaking_space). Do you have any idea how to improve your solution to U+00A0 character? – mc2 Mar 30 '21 at 09:08
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
Moses Davidowitz
  • 982
  • 11
  • 28
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##+( )}%%+( )}
gMale
  • 17,147
  • 17
  • 91
  • 116
  • 1
    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 Nov 19 '15 at 15:23
  • 1
    @musicin3d : this is a site I use frequently that spells out how [variable manipulation works in bash](http://tldp.org/LDP/abs/html/parameter-substitution.html) search for `${var##Pattern}` for more details. Also, this site [explains bash patterns](https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html). 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" – gMale Nov 21 '15 at 18:13
  • Funny, it worked in the prompt, but not after transposing to the bash script file. – DrBeco Mar 21 '16 at 01:03
  • weird. Is it the same bash version in both instances? – gMale Mar 21 '16 at 19:11
  • @DrBeco `#!/bin/sh` is not necessarily bash ;) – Daniel Alder Nov 23 '20 at 08:57
  • Hi @DanielAlder, you replied to a comment from 2016, and I have no idea what is this about. But I agree with you. Have a good one. – DrBeco Nov 23 '20 at 19:11
  • @DrBeco It's really easy to remember what this is about, just check the title of the original question and the answer these comments are under. Btw, it didn't work for you because your bash hell did not have the option `shopt -s extglob`. I updated the answer. – Ярослав Рахматуллин May 11 '21 at 08:39
  • That formulation for fewer lines is wrong. You can't do `${${f}}` in _bash_. – bobbogo Mar 15 '22 at 11:33
  • The second part is flat wrong. You cannot combine multiple substitutions like `${${...}...}`; the only thing you can have following `${` is an optional `!` and then the name of a variable. (It is also illogical, since the intuitive interpretation of `${${var}}` is to expand the variable `var` to get the name of another variable, which in turn is expanded to get the text that you want. – Martin Kealey Jul 30 '22 at 06:25
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")")"
}
Larry
  • 95
  • 8
NOYB
  • 625
  • 8
  • 14
12

Use AWK:

echo $var | awk '{gsub(/^ +| +$/,"")}1'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
11

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 ' '`
pojo
  • 5,892
  • 9
  • 35
  • 47
  • 3
    This doesn't trim whitespace from the front and back - it removes all whitespace from the string. – Nick Dec 06 '13 at 15:28
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

Alpesh Gediya
  • 3,706
  • 1
  • 25
  • 38
9

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.

Alan Carlyle
  • 948
  • 1
  • 6
  • 11
Adam Erickson
  • 6,027
  • 2
  • 46
  • 33
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
}'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Luca Borrione
  • 16,324
  • 8
  • 52
  • 66
  • Note: As suggested by @mkelement it will not work for multi-line string though it should work for single-line strings. – GuruM Sep 18 '12 at 10:18
  • 1
    You are wrong: it does work on multi-line strings too. Just test it out!:) – Luca Borrione Sep 18 '12 at 12:36
  • +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. – GuruM Sep 18 '12 at 14:16
  • Now I see what you mean! Thank you for the head up, I edited my answer. – Luca Borrione Sep 18 '12 at 15:04
  • @"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. – GuruM Sep 19 '12 at 06:22
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/16860/discussion-between-gurum-and-luca-borrione) – GuruM Sep 19 '12 at 06:26
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

Zombo
  • 1
  • 62
  • 391
  • 407
6
var='   a b c   '
trimmed=$(echo $var)
ultr
  • 1
  • 1
  • 1
  • 1
    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). – joshlf Aug 14 '13 at 22:26
6

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."
  • 1
    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:]]*$` – Ron Burk Apr 10 '17 at 21:58
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.

MykennaC
  • 641
  • 7
  • 18
  • 4
    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). – mklement0 Jun 04 '12 at 04:20
  • this is jut wrong, the value in the `xyz` variable is NOT trimmed. – caesarsol Jun 10 '15 at 14:24
6

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

$ var=`echo '   hello'`; echo $var
hello
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
evanx
  • 41
  • 1
  • 1
  • 9
    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. – Nicholas Sushkin Feb 09 '12 at 17:41
  • 2
    @NicholasSushkin One could do `var=$(echo $var)` but I do not recommend it. Other solutions presented here are preferred. – xebeche Aug 02 '12 at 11:08
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'
Nicholas Sushkin
  • 13,050
  • 3
  • 30
  • 20
  • 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. – mklement0 Jun 04 '12 at 21:53
  • 1
    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). – mklement0 Jun 04 '12 at 21:53
  • 2
    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:]]* ]]` – mklement0 Jun 04 '12 at 21:54
6

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)
Takakiri
  • 33
  • 2
  • 6
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
mklement0
  • 382,024
  • 64
  • 607
  • 775
Gregor
  • 1,205
  • 11
  • 20
5

Removing spaces to one space:

(text) | fmt -su
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
gardziol
  • 1
  • 1
  • 1
4

trim() removes whitespaces (and tabs, non-printable characters; I am considering just whitespaces for simplicity). My version of a solution:

var="$(hg st -R "$path")" # I often like to enclose shell output in double quotes
var="$(echo "${var}" | sed "s/\(^ *\| *\$\)//g")" # This is my suggestion
if [ -n "$var" ]; then
 echo "[${var}]"
fi

The 'sed' command trims only leading and trailing whitespaces, but it can be piped to the first command as well resulting in:

var="$(hg st -R "$path" | sed "s/\(^ *\| *\$\)//g")"
if [ -n "$var" ]; then
 echo "[${var}]"
fi
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Avenger
  • 21
  • 1
4

Python has a function strip() that works identically to PHP's trim(), so we can just do a little inline Python to make an easily understandable utility for this:

alias trim='python -c "import sys; sys.stdout.write(sys.stdin.read().strip())"'

This will trim leading and trailing whitespace (including newlines).

$ x=`echo -e "\n\t   \n" | trim`
$ if [ -z "$x" ]; then echo hi; fi
hi
johncs
  • 163
  • 2
  • 8
  • while that works, you may want to consider offering a solution that does not involve launching a full python interpreter just to trim a string. It's just wasteful. – pdwalker Apr 03 '15 at 12:00
4

Use:

trim() {
    local orig="$1"
    local trmd=""
    while true;
    do
        trmd="${orig#[[:space:]]}"
        trmd="${trmd%[[:space:]]}"
        test "$trmd" = "$orig" && break
        orig="$trmd"
    done
    printf -- '%s\n' "$trmd"
}
  • It works on all kinds of whitespace, including newline,
  • It does not need to modify shopt.
  • It preserves inside whitespace, including newline.

Unit test (for manual review):

#!/bin/bash

. trim.sh

enum() {
    echo "   a b c"
    echo "a b c   "
    echo "  a b c "
    echo " a b c  "
    echo " a  b c  "
    echo " a  b  c  "
    echo " a      b  c  "
    echo "     a      b  c  "
    echo "     a  b  c  "
    echo " a  b  c      "
    echo " a  b  c      "
    echo " a N b  c  "
    echo "N a N b  c  "
    echo " Na  b  c  "
    echo " a  b  c N "
    echo " a  b  c  N"
}

xcheck() {
    local testln result
    while IFS='' read testln;
    do
        testln=$(tr N '\n' <<<"$testln")
        echo ": ~~~~~~~~~~~~~~~~~~~~~~~~~ :" >&2
        result="$(trim "$testln")"
        echo "testln='$testln'" >&2
        echo "result='$result'" >&2
    done
}

enum | xcheck
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alois Mahdal
  • 10,763
  • 7
  • 51
  • 69
4

I had to test the result (numeric) from a command but it seemed the variable with the result was containing spaces and some non printable characters. Therefore even after a "trim" the comparison was erroneous. I solved it by extracting the numerical part from the variable:

numerical_var=$(echo ${var_with_result_from_command} | grep -o "[0-9]*")
Olivier Meurice
  • 554
  • 8
  • 17
4

I needed to trim whitespace from a script when the IFS variable was set to something else. Relying on Perl did the trick:

# trim() { echo $1; } # This doesn't seem to work, as it's affected by IFS

trim() { echo "$1" | perl -p -e 's/^\s+|\s+$//g'; }

strings="after --> , <-- before,  <-- both -->  "

OLD_IFS=$IFS
IFS=","
for str in ${strings}; do
  str=$(trim "${str}")
  echo "str= '${str}'"
done
IFS=$OLD_IFS
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
TrinitronX
  • 4,959
  • 3
  • 39
  • 66
  • 2
    You can easily avoid problems with non-default $IFS values by creating a local copy (which will go out of scope upon exiting the function): `trim() { local IFS=$' \t\n'; echo $1; }` – mklement0 Jun 02 '12 at 03:17
  • 1
    Related to @mklement0 coment: https://mywiki.wooledge.org/IFS. `The default value of IFS is space, tab, newline. (A three-character string.)`... – Artfaith Jun 15 '21 at 15:19
3

Use this simple Bash parameter expansion:

$ x=" a z     e r ty "
$ echo "START[${x// /}]END"
START[azerty]END
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • 2
    This approach (a) also removes _interior_ spaces and (b) only removes _spaces_, not other forms of white space. – mklement0 Jun 04 '12 at 14:49
  • @mklement0 Sometimes that's exactly what is needed, though! :-) – Ville Feb 19 '17 at 05:46
  • 1
    @Ville: Yes, and with the help of my behavior-clarifying comment future readers can decide if this solution is the right one for them. – mklement0 Feb 19 '17 at 06:08
3

This trims multiple spaces of the front and end

whatever=${whatever%% *}

whatever=${whatever#* }

gretelmk2
  • 35
  • 1
  • 8
    Your second command should presumably have `##` not just `#`. But in fact these don't work; the pattern you're giving matches a space followed by _any_ sequence of other characters, not a sequence of 0 or more spaces. That `*` is the shell globbing `*`, not the usual "0-or-more" regexp `*`. – dubiousjim Jan 27 '11 at 18:50
  • 1
    These are useful if you know there will be no spaces in the string. They work great for converting `" foo "` to `"foo"`. For `" hello world "`, not so much. – J.D. Feb 28 '15 at 16:29
  • `t=" "" a "" "; echo "=${t#* }="` output `= a =` leading multiple spaces are not stripped. `t=" "" a "" "; echo "=${t%% *}="` output `==` trailing spaces are stripped with string content. (Double quoting because of SE.) – catpnosis Mar 24 '18 at 16:08
3

I created the following functions. I am not sure how portable printf is, but the beauty of this solution is you can specify exactly what is "white space" by adding more character codes.

    iswhitespace()
    {
        n=`printf "%d\n" "'$1'"`
        if (( $n != "13" )) && (( $n != "10" )) && (( $n != "32" )) && (( $n != "92" )) && (( $n != "110" )) && (( $n != "114" )); then
            return 0
        fi
        return 1
    }

    trim()
    {
        i=0
        str="$1"
        while (( i < ${#1} ))
        do
            char=${1:$i:1}
            iswhitespace "$char"
            if [ "$?" -eq "0" ]; then
                str="${str:$i}"
                i=${#1}
            fi
            (( i += 1 ))
        done
        i=${#str}
        while (( i > "0" ))
        do
            (( i -= 1 ))
            char=${str:$i:1}
            iswhitespace "$char"
            if [ "$?" -eq "0" ]; then
                (( i += 1 ))
                str="${str:0:$i}"
                i=0
            fi
        done
        echo "$str"
    }

#Call it like so
mystring=`trim "$mystring"`
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
cmeub
  • 497
  • 2
  • 11
3

I found that I needed to add some code from a messy sdiff output in order to clean it up:

sdiff -s column1.txt column2.txt | grep -F '<' | cut -f1 -d"<" > c12diff.txt 
sed -n 1'p' c12diff.txt | sed 's/ *$//g' | tr -d '\n' | tr -d '\t'

This removes the trailing spaces and other invisible characters.

imp25
  • 2,327
  • 16
  • 23
2
var="  a b  "
echo "$(set -f; echo $var)"

>a b
davide
  • 2,082
  • 3
  • 21
  • 30
2

Use:

var=`expr "$var" : "^\ *\(.*[^ ]\)\ *$"`

It removes leading and trailing spaces and is the most basic solution, I believe. Not Bash built-in, but 'expr' is a part of coreutils, so at least no standalone utilities are needed like sed or AWK.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
moskit
  • 94
  • 2
2

Array assignment expands its parameter splitting on the Internal Field Separator (space/tab/newline by default).

words=($var)
var="${words[@]}"
David Farrell
  • 427
  • 6
  • 16
2

The simplest and cheapest way to do this is to take advantage of echo ignoring spaces. So, just use

dest=$(echo $source)

for instance:

> VAR="   Hello    World   "
> echo "x${VAR}x"
x   Hello    World   x
> TRIMD=$(echo $VAR)
> echo "x${TRIMD}x"
xHello Worldx

Note that this also collapses multiple whitespaces into a single one.

  • Really great cheap and simple solution. – Ahmed Korany May 24 '22 at 11:30
  • The lack of quoting is a bug; for example, if `var` contains a wildcard character, the shell will expand it. See also [When to wrap quotes around a shell variable?](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) – tripleee Dec 21 '22 at 09:31
2
#!/bin/bash

function trim
{
    typeset trimVar
    eval trimVar="\${$1}"
    read trimVar << EOTtrim
    $trimVar
EOTtrim
    eval $1=\$trimVar
}

# Note that the parameter to the function is the NAME of the variable to trim, 
# not the variable contents.  However, the contents are trimmed.


# Example of use:
while read aLine
do
    trim aline
    echo "[${aline}]"
done < info.txt



# File info.txt contents:
# ------------------------------
# ok  hello there    $
#    another  line   here     $
#and yet another   $
#  only at the front$
#$



# Output:
#[ok  hello there]
#[another  line   here]
#[and yet another]
#[only at the front]
#[]
Razor5900
  • 21
  • 1
1

Yet another solution with unit tests which trims $IFS from stdin, and works with any input separator (even $'\0'):

ltrim()
{
    # Left-trim $IFS from stdin as a single line
    # $1: Line separator (default NUL)
    local trimmed
    while IFS= read -r -d "${1-}" -u 9
    do
        if [ -n "${trimmed+defined}" ]
        then
            printf %s "$REPLY"
        else
            printf %s "${REPLY#"${REPLY%%[!$IFS]*}"}"
        fi
        printf "${1-\x00}"
        trimmed=true
    done 9<&0

    if [[ $REPLY ]]
    then
        # No delimiter at last line
        if [ -n "${trimmed+defined}" ]
        then
            printf %s "$REPLY"
        else
            printf %s "${REPLY#"${REPLY%%[!$IFS]*}"}"
        fi
    fi
}

rtrim()
{
    # Right-trim $IFS from stdin as a single line
    # $1: Line separator (default NUL)
    local previous last
    while IFS= read -r -d "${1-}" -u 9
    do
        if [ -n "${previous+defined}" ]
        then
            printf %s "$previous"
            printf "${1-\x00}"
        fi
        previous="$REPLY"
    done 9<&0

    if [[ $REPLY ]]
    then
        # No delimiter at last line
        last="$REPLY"
        printf %s "$previous"
        if [ -n "${previous+defined}" ]
        then
            printf "${1-\x00}"
        fi
    else
        last="$previous"
    fi

    right_whitespace="${last##*[!$IFS]}"
    printf %s "${last%$right_whitespace}"
}

trim()
{
    # Trim $IFS from individual lines
    # $1: Line separator (default NUL)
    ltrim ${1+"$@"} | rtrim ${1+"$@"}
}
l0b0
  • 55,365
  • 30
  • 138
  • 223
0

The "trim" function removes all horizontal whitespace:

ltrim () {
    if [[ $# -eq 0 ]]; then cat; else printf -- '%s\n' "$@"; fi | perl -pe 's/^\h+//g'
    return $?
}

rtrim () {
    if [[ $# -eq 0 ]]; then cat; else printf -- '%s\n' "$@"; fi | perl -pe 's/\h+$//g'
    return $?
}

trim () {
    ltrim "$@" | rtrim
    return $?
}
Mario Palumbo
  • 693
  • 8
  • 32
0

read already trims whitespace, so in bash you can do this:

$ read foo <<< "   foo  bar   two spaces follow   "
$ echo ".$foo."
.foo  bar   two spaces follow.

The POSIX compliant version is a bit longer

$ read foo << END
   foo  bar   two spaces follow   
END
$ echo ".$foo."
.foo  bar   two spaces follow.
Roger Keays
  • 3,117
  • 1
  • 31
  • 23
0

The simplest way for the single-line use cases I know of:

echo "  ABC  " | sed -e 's# \+\(.\+\) \+#\1#'

How it works:

  • -e enables advanced regex
  • I use # with sed as I don't like the "messy library" patterns like /\////\/\\\/\/
  • sed wants most regex control chars escaped, hence all the \
  • Otherwise it's just ^ +(.+) +$, i.e. spaces at the beginning, a group no.1, and spaces at the end.
  • All this is replaced with just "group no.1".

Therefore, ABC becomes ABC.

This should be supported on most recent systems with sed.


For tabs, that would be

echo "  ABC  " | sed -e 's#[\t ]\+\(.\+\)[\t ]\+#\1#'

For multi-line content, that already needs character classes like [:space:] as described in other answers, and may not be supported by all sed implementations.

Reference: Sed manual

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
  • `-e` does not enable *advanced regex*. Did you mean `-E` (with which you wouldn't need any of those backslashes)? – oguz ismail Aug 05 '22 at 05:31
0

Create an array instead of variable this will trim all space, tab and newline characters:

arr=( $(hg st -R "$path") )
if [[ -n "${arr[@]}" ]]; then
    printf -- '%s\n' "${arr[@]}"
fi
Ivan
  • 6,188
  • 1
  • 16
  • 23
-1
var = '  a b  '
# remove all white spaces
new=$(echo $var |  tr -d ' ')
# remove leading and trailing whitespaces
new=$(echo $var)

ab
a b
  • 1
    The lack of quoting is a bug; for example, if `var` contains a wildcard character, the shell will expand it. See also [When to wrap quotes around a shell variable?](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) – tripleee Dec 21 '22 at 09:29
  • 1
    Fifty answers, one with nearly1,500 votes. What is your answer adding that's new? Why do you prefer your approach over the other answers? – Jeremy Caney Dec 22 '22 at 00:40
-3

While it's not strictly Bash this will do what you want and more:

php -r '$x = trim("  hi there  "); echo $x;'

If you want to make it lowercase too, do:

php -r '$x = trim("  Hi There  "); $x = strtolower($x) ; echo $x;'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
David Newcomb
  • 10,639
  • 3
  • 49
  • 62
-4
#Execute this script with the string argument passed in double quotes !! 
#var2 gives the string without spaces.
#$1 is the string passed in double quotes
#!/bin/bash
var2=`echo $1 | sed 's/ \+//g'`
echo $var2
a.saurabh
  • 1,163
  • 10
  • 15