Skip to content
shellhacks.dev
Bash

How to Get the Length of a String or Array in Bash

Get the length of a string or an array in Bash with ${#var} and ${#arr[@]}, count bytes versus characters, and avoid the common off-by-one with indexes.

5 min read
Summarize in AI
Illustration for "How to Get the Length of a String or Array in Bash": a shell script in an editor, a process monitor, a pipeline of commands.

Counting characters is one expansion, ${#var}, and counting array items is another that looks nearly the same. Mixing them up is why a loop runs over letters instead of words. This builds on Bash variables and assumes Bash 5.2 with a UTF-8 locale.

String length

Shell
word="shellhacks"
echo "${#word}"

greeting="héllo"
echo "${#greeting}"
Text
10
5

Under a UTF-8 locale Bash counts the accented letter as one character. With LC_ALL=C the same string reports 6, because the locale sees raw bytes. When the byte count is what you want, for a protocol field or a storage limit, set the locale on purpose rather than relying on whatever the caller exports.

Array length

Shell
fruits=(apple banana cherry)
echo "${#fruits[@]}"
echo "${#fruits[1]}"
Text
3
6

${#fruits[@]} is the number of elements. ${#fruits[1]} is the length of element 1, banana, which is a different question. A bare ${#fruits} means the length of element 0, so it prints 5, which is a quiet bug in scripts that forget the subscript. The array basics are in arrays in Bash.

Last index and sparse arrays

Shell
items=(a b c)
unset 'items[1]'
echo "count: ${#items[@]}"
echo "indexes: ${!items[@]}"
echo "last: ${items[-1]}"
Text
count: 2
indexes: 0 2
last: c

Removing an element leaves a hole, so the count is 2 but the highest index is 2, not 1. Code that computes the last index as ${#items[@]}-1 then reads an unset slot. Use a negative subscript for the last element (Bash 4.3 and later) or ${!items[@]} to list the real indexes.

Avoid the pipe

Shell
s="abc"
echo -n "$s" | wc -c
echo "$s" | wc -c
Text
3
4

The second form counts the newline echo adds. Staying inside the shell with ${#s} also saves a process, which matters in a loop. The Bash manual on parameter expansion documents the length forms, and the expansion tutorial covers the rest.

Characters, bytes and locales

The length expansion ${#var} counts characters in the current locale, not bytes. In a UTF-8 locale, the string héllo has length 5, and in the C locale the same bytes give length 6, since the accented letter takes two bytes. A script run by cron or in a container often has a different locale from your terminal, so a length calculation can change from one environment to another. If the script needs bytes, for example for a protocol field, set LC_ALL=C for that one expansion, and if it needs characters, set a UTF-8 locale explicitly and make sure it is installed.

Characters are still not what a reader sees as a letter. A combining accent after a base letter is two code points, and an emoji with a skin tone modifier is two or more. Bash counts code points, so a display width or a count of perceived letters needs a library that understands grapheme clusters.

Counting words, lines and fields

For text, wc gives lines with -l, words with -w, bytes with -c and characters with -m. Piping a variable into it with a here-string, wc -c <<< "$var", adds a trailing newline to the count, which gives a number one larger than the string length. Use printf '%s' "$var" | wc -c to avoid that. Command substitution strips trailing newlines from captured output, so ${#out} of a command's result can be shorter than the output really was. Keep that in mind when validating lengths of values read from files.

For the number of fields in a delimited line, split into an array with IFS=, read -ra parts <<< "$line" and take the array length. Quote carefully, and remember that consecutive delimiters create empty fields.

Length checks in validation

Length is common in validation: a password minimum, a maximum filename, a limited field. Test it with arithmetic, (( ${#name} > 255 )) && die, and check emptiness separately with -z. Limits imposed by systems are usually in bytes, such as 255 bytes for a file name on most Linux file systems, so a name with 100 characters of Japanese text may exceed it. Compute the byte length with the C locale before comparing with a byte limit. Consider also what happens with leading and trailing whitespace; trim it first if the rule is about the content, using parameter expansion or xargs for the trimming.

Do not use length checks as a security measure alone. Input of the right length can still contain control characters, newlines or shell metacharacters, so validate content with a pattern as well.

Performance and portability

The expansion is a shell builtin and does not start a process, so it is fast in loops. Calling wc or expr length in each iteration costs a fork per call, and for ten thousand items the difference is visible. Alternatives such as awk '{print length($0)}' are the right choice when processing a whole file line by line, as a single process handles all lines. In POSIX sh, ${#var} exists and works as in Bash, but arrays do not, so array length is unavailable in Dash and other minimal shells. If a script must run under both, avoid arrays or require Bash in the shebang and test with that shell. A short test with an empty string, an ASCII string, an accented string and a string with a newline gives confidence that the length logic behaves in the environments you target.

A practical habit is to centralise length checks in a small function that takes the value and a limit and prints a message naming the field that was too long. Repeating inline comparisons across a script produces inconsistent messages and occasionally the wrong operator. With a function, the rule appears once, it is easy to test and the error text is uniform. Pair it with a check that the value is not empty and that it contains only the characters you allow, and the validation is complete for most inputs.

Document the unit in the message. Saying "name too long (maximum 255 bytes)" prevents the confusion between characters and bytes that causes support questions with non-English names.

Where this does not apply

Characters are not always what a user sees: an emoji with a skin-tone modifier is two code points, so ${#var} reports 2. For display width in a terminal, use a tool that understands grapheme clusters such as wcswidth in Python. In POSIX sh there are no arrays, so only the string form works there.

A common use is validating input before it goes somewhere with a limit. A script that builds a name for a file system with a 255-byte name limit can check (( ${#name} > 255 )) and exit with a clear message instead of failing later inside mv. Remember to count bytes there, since the limit is in bytes: LC_ALL=C in front of the expansion does that. The Bash manual on arrays documents the subscript forms used above.

Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.