I have the following script which counts characters in user input:

    echo -n "Type text: ";
    read mystring;
    echo -n $mystring | wc -m;

Without the "-n" in the last line, the character count would be wrong because it would also include the newline character put there by echo (so the count for e.g. "abc" would be 4 instead of 3.)

For the sake of practice I now want to do this correction in a more complicated way. The general idea is like this:

     var=$($mystring | wc -m);
     echo -n "Type text: ";
     read mystring;
     echo $(( $var - 1 ));

So the character count of user input becomes $var and then I subtract 1 from $var. How do I make it work?

  • read string <<<"abcd"; printf 'String is %d chars long\n' "${#string}" – Valentin Bajrami Apr 24 '17 at 19:46

Your script does not work for several reasons:

  • You start by initializing var to be equal to the output of running the command | wc -m because mystring is at this point null.
  • Even if it were not null, it would attempt to run its contents as a command, and send that output into wc.

You have to A> do things in the right order, and ii.> do the correct things:

read -p "Type something > " mystring
var="$( wc -m <<< "$foo" )"
echo $(($var-1))
  • thank you, that solved it! I had already tried putting the var line after read, but as I didn't construct it correctly, of course it didn't work. I forgot to put the whole thing in quotes, add echo, and put $mystring in quotes also! – wsurfer0 Apr 24 '17 at 20:10
  • 1
    That won't work properly if $mystring is -nene for instance. You can't use echo for arbitrary data. Or if $IFS contains digits arithmetic expansions should also be quoted. – Stéphane Chazelas Apr 24 '17 at 20:18
  • It also won't work properly if what the user enters starts or ends with blanks or contains backslashes. – Stéphane Chazelas Apr 24 '17 at 20:20
  • Revised to use <<< which seems to play nice with strings starting with hyphens (wc -m <<< "-nene"). – DopeGhoti Apr 28 '17 at 15:41

If you want to count the number of characters in what the user entered up to but not including the newline character, then it should be:

#! /bin/sh -
printf 'Type text: '
IFS= read -r userInput
length=$(printf %s "$userInput" | wc -m)
# or:

If you want to include the newline character that the user possibly entered, then:

#! /bin/sh -
printf 'Type text: '
IFS= read -r userInput && userInput="$userInput
length=$(printf %s "$userInput" | wc -m)
# or:

read will typically return true if a full line was input (the newline character is present) which is why we append one if read was successful.

Note that in most shell implementations (zsh being the exception), it won't work properly if the user enters a NUL (aka ^@) character.

To work around that, you could do:

printf 'Type text: '
length=$(line | wc -m)

instead. Or:

length=$(line | tr -d '\n' | wc -m)
# or
length=$(($(line | wc -m) - 1)) # as line always includes a newline on
                                # output even if one was not provided on
                                # input.

if you don't want to count the newline.

The behaviour will vary as well if the user manages to enter bytes that don't form part of valid characters. You'll also find some sh implementations whose ${#var} doesn't work properly with multi-byte characters (would return the length in bytes instead of characters).

  • Thank you for your answer, I will have to study it a bit to understand. For now I just wanted to understand how to do the var construction, which was the main cause that it didn't work. – wsurfer0 Apr 24 '17 at 20:26
expr " $mystring" : '.*' - 1

will return the length of the contents of the shell variable mystring

  • See also expr + "$mystring" : '.*' with GNU expr. – Stéphane Chazelas Apr 25 '17 at 6:20
  • Strictly speaking that counts the number of characters at the start of $mystring. For instance in a UTF-8 locale (on a GNU system at least) expr $'foo\200bar' : '.*' would return 3 (length of foo, \200 is not a character). While printf 'foo\200bar' | wc -m would return 6 (and GNU expr length $'foo\200bar' returns 7) – Stéphane Chazelas Apr 25 '17 at 7:15

In bash you'd use

#!/usr/bin/env bash

read -p 'Type text: ' userInput
printf 'Your input was %d chars long\n' "${#userInput}"

The string count can be retrieved by ${#var}. Using wc is in this case unnecessary

  • thanks, but I purposely wanted to use wc in order to understand the construction of the var line – wsurfer0 Apr 24 '17 at 20:17
  • Note that read without IFS= and without -r doesn't store the input exactly as typed into $userInput. – Stéphane Chazelas Apr 25 '17 at 6:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.