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?
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'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With