1

I found this thread with two solutions for trimming whitespace: piping to xargs and defining a trim() function:

trim() {
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"   
    echo -n "$var"
}

I prefer the second because of one comment:

This is overwhelmingly the ideal solution. Forking one or more external processes 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.

I am getting, for example, the wifi SSID on macOS by piping to awk (when I get comfortable with regular expressions in bash, I won't fork an awk process), which includes a leading space:

$ /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | awk -F: '/ SSID/{print $2}'
 <some-ssid>

$ /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | awk -F: '/ SSID/{print $2}' | xargs
<some-ssid>

$ /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | awk -F: '/ SSID/{print $2}' | trim

$ wifi=$(/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | awk -F: '/ SSID/{print $2}')

$ trim "$wifi"
<some-ssid>

Why does piping to the trim function fail and giving it an argument work?

miguelmorin
  • 5,025
  • 4
  • 29
  • 64

1 Answers1

2

It is because your trim() function is expecting a positional argument list to process. The $* is the argument list passed to your function. For the case that you report as not working, you are connecting the read end of a pipe to the function inside which you need to fetch from the standard input file descriptor.

In such a case you need to read from standard input using read command and process the argument list, i.e. as

trim() {
    # convert the input received over pipe to a a single string 
    IFS= read -r var
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"   
    echo -n "$var"
}

for which you can now do

$ echo "    abc    " | trim
abc

or using a command substitution syntax to run the command that fetches the string, that you want to pass to trim() with your older definition.

trim "$(/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | awk -F: '/ SSID/{print $2}')"

In this case, the shell expands the $(..) by running the command inside and replaces it with output of the commands run. So now the function sees trim <args> which it interprets as a positional argument and runs the string replacement functions directly on it.

miguelmorin
  • 5,025
  • 4
  • 29
  • 64
Inian
  • 80,270
  • 14
  • 142
  • 161
  • 1
    Can even use Bash's built-in RegEx engine with: `[[ "$var" =~ ^[[:space:]]*(.*[^[:space:]]) ]]` and `printf '%s' "${BASH_REMATCH[1]}"` to stream the result to `stdout`. – Léa Gris Dec 19 '19 at 12:34
  • Agreed, but my answer is trying to explain the actual problem OP was facing and not conjure up an alternate way to solve their problem – Inian Dec 19 '19 at 12:36