You can call external utilities (see other answers), but they will make your script slower, and it's hard to get the plumbing right.
Zsh
In zsh, you can write ${#$(readlink -f /etc/fstab)}
to get the length of the command substitution. Note that this isn't the length of the command output, it's the length of the output without any trailing newline.
If you want the exact length of the output, output an extra non-newline character at the end, and subtract one.
$((${#$(readlink -f /etc/fstab; echo .)} - 1))
If what you want is the payload in the command's output, then you need to subtract two here, because the output of readlink -f
is the canonical path plus a newline.
$((${#$(readlink -f /etc/fstab; echo .)} - 2))
This differs from ${#$(readlink -f /etc/fstab)}
in the rare but possible case where the canonical path itself ends in a newline.
For this specific example, you don't need an external utility at all, because zsh has a built-in construct that's equivalent to readlink -f
, through the history modifier A
.
echo /etc/fstab(:A)
To get the length, use the history modifier in a parameter expansion:
${#${:-/etc/fstab}:A}
If you have the file name in a variable filename
, that would be ${#filename:A}
.
Bourne/POSIX-style shells
None of the pure Bourne/POSIX shells (Bourne, ash, mksh, ksh93, bash, yash …) have any similar extension that I know of. If you need to apply a parameter substitution to the output of a command substitution or to nest parameter substitutions, use successive stages.
You can stuff the processing into a function if you like.
command_output_length_sans_trailing_newlines () {
set -- "$("$@")"
echo "${#1}"
}
or
command_output_length () {
set -- "$("$@"; echo .)"
echo "$((${#1} - 1))"
}
but there's usually no benefit; except with ksh93, that causes an extra fork to be able to use the output of the function, so it makes your script slower, and
there's rarely any readability benefit.
Once again, the output of readlink -f
is the canonical path plus a newline; if you want the length of the canonical path, subtract 2 instead of 1 in command_output_length
. Using command_output_length_sans_trailing_newlines
gives the right result only when the canonical path itself doesn't end in a newline.
Bytes vs characters
${#…}
is supposed to be the length in characters, not in bytes, which makes a difference in multibyte locales. Reasonably up-to-date versions of ksh93, bash and zsh calculate the length in characters according to the value of LC_CTYPE
at the time the ${#…}
construct is expanded. Many other common shells don't really support multibyte locales: as of dash 0.5.7, mksh 46 and posh 0.12.3, ${#…}
returns the length in bytes. If you want the length in characters in a reliable way, use the wc
utility:
$(readlink -f /etc/fstab | wc -m)
As long as $LC_CTYPE
designates a valid locale, you can be confident that this will either error out (on an ancient or restricted platform that doesn't support multibyte locales) or return the correct length in characters. (For Unicode, “length in characters” means the number of code points — number of glyphs is yet another story, due to complications such as combining characters.)
If you want the length in bytes, set LC_CTYPE=C
temporarily, or use wc -c
instead of wc -m
.
Counting bytes or characters with wc
includes any trailing newlines from the command. If you want the length of the canonical path in bytes, it's
$(($(readlink -f /etc/fstab | wc -c) - 1))
To get it in characters, subtract 2.
readlink -f /etc/fstab
is 11 characters. Don't forget the newline. Otherwise you'd see/etc/fstabluser@cern:~$
when you ran it from a shell. – Phil Frost Oct 11 '14 at 13:32