Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

String concatenation without spaces in Bash

Tags:

string

bash

I'm trying to write a simple bash script to count the number of files in a directory, and then add a new file with name file<#files> to the end of the directory. My current attempt is:

name="out"
num=$(ls -l|wc -l)
echo foo > "${name}${num}"

However, this gives me a bunch of spaces, resulting in the filename out 12. Why do the spaces appear, and how do I concatenate these strings without creating spaces?

like image 709
Teofrostus Avatar asked Oct 20 '15 00:10

Teofrostus


1 Answers

On OS X (and BSD-like systems in general), wc -l left-space-pads the number to 8 characters, e.g., _______7 (_ representing a space here for technical reasons).

(If you use wc's output unquoted with echo - e.g., echo $(wc -l <<<'dummy'), you will not see the padding, because the shell will "eat" the leading spaces; if you double-quote the command substitution, you'll see them: echo "$(wc -l <<<'dummy')"

That said, you're better off using neither ls nor wc - for both reasons of robustness and performance; use globbing to capture all filenames in an array and use that array's element count instead:

name='out'
files=( * ) # collect all filenames in array

echo foo > "${name}${#files[@]}" # use array-element count

Note: By default, * will not include hidden items, just like the OP's ls command (because it doesn't include -A or -a).
Use shopt -s dotglob to include hidden items too.
Also, if there happen to be no matching items at all, Bash will return the pattern as-is, i.e., *; to have Bash return the empty string instead, use shopt -s nullglob.


Generally, if you do find yourself needing to trim leading and trailing whitespace from command output and/or a Bash variable:

  • To trim a value stored in a Bash variable, use read:

     # Single-line value:
     val='  a   b  '
     read -r val <<<"$val" # $val now contains 'a   b'
    
     # Multi-line value:
     # Note: Trims leading and trailing whitespace including newlines, but 
     # preserves *any* interior whitespace, including empty and all-whitespace lines.
     val=$'\n \t  one\n  \ntwo\n  \n'
     read -r -d '' val <<<"$val" # $val now contains $'one\n  \ntwo'
    
  • To trim command output as part of a pipeline or lines from stdin / a file:

    • If the value to trim has either no interior whitespace or you want to ensure / don't mind that interior whitespace is normalized to a single space each, pipe to xargs (this works, because xargs, after parsing the input into words, passes them to the echo utility by default - without shell involvement); the result is always a single output line; caveat: xargs removes embedded quotes and \ instances, unless you \-escape them:

      val=$(ls | wc -l | xargs) # $val now contains trimmed count
      
    • If preserving line-interior whitespace as-is matters, use sed; note that - unlike read -r d '' above - this is a LINE-based solution, so the number of input lines will be preserved, including leading and trailing empty or all-whitespace lines, with all-whitespace lines trimmed to empty ones (assumes GNU Sed or BSD Sed):

      echo $'\nfoo \n \n bar \n' | sed -E 's/^[[:blank:]]+|[[:blank:]]+$//g'
      # -> $'\nfoo\n\nbar'
      

  • Simplified scenario: Remove ALL whitespace from a value, using tr:

    • single-line / per-line:

      tr -d '[:blank:]' <<<$'  a   b     c \n foo  ' # -> $'abc\nfoo\n'
      
    • globally, including newlines:

      tr -d '[:space:]' <<<$'  a   b     c \n foo  ' # -> 'abcfoo'
      
like image 154
mklement0 Avatar answered Sep 29 '22 08:09

mklement0