Jose
Jose

Reputation: 1

# operator in bash to get the length of a string literal

I'm having trouble getting the length of a plain string in bash using # (is there a common name for this operator? The reference manual calls it a special parameter, but there are many of those). More specifically, I want to get the length of a path with : as a delimiter.

I've tried a few things, following the ${#parameter} syntax found in the manual, but they all return an error.

length=${#":/path/to/a/directory"} # "bad substitution"
length=${#':/path/to/a/directory'} # "bad substitution"
length=${#:/path/to/a/directory} # "syntax error: operand expected (error token is "/path/to/a/directory")"

# Removing the `:`
length=${#"/path/to/a/directory"} # "bad substitution"
length=${#'/path/to/a/directory'} # "bad substitution"
length=${#/path/to/a/directory} # `length` is set to 0

Clearly, using a variable works.

var=":/path/to/a/directory"
length=${#var} # `length` is correctly set to 21

It seems evident that bash does not support using # on a literal string, but could there be a workaround to using a variable?

Upvotes: 0

Views: 628

Answers (5)

James Brown
James Brown

Reputation: 37464

Using printf:

$ printf -v_ "%s%n" ":/path/to/a/directory" length
$ echo $length
21

-v_ "%s"to write the output to trash variable _

"%n" "string" length to write the length of the string to variable length.

Upvotes: 0

Ed Morton
Ed Morton

Reputation: 204548

${#...} is only for variables but with a literal string you can always do any of these (or various similar alternatives):

$ expr length ':/path/to/a/directory'
21

$ expr ':/path/to/a/directory' : '.*'
21

$ wc -c < <(printf '%s' ':/path/to/a/directory')
21

$ printf '%s' ':/path/to/a/directory' | wc -c
21

You can save the result of either command in a variable in the usual way, e.g.:

$ length=$( expr length ':/path/to/a/directory' )
$ echo "$length"
21

The only one above that's kinda cryptic IMO is expr ':/path/to/a/directory' : '.*' - that's using expr's : operator to compare the original string ':/path/to/a/directory' to the regex '.*', which of course matches the whole of that string, and then printing the number of characters that match, i.e. the number of characters in the original string.

Upvotes: 1

pjh
pjh

Reputation: 8209

You could define a function to do what you want. This Shellcheck-clean pure Bash code demonstrates one way to do it:

# Assign the length of parameter 2 to the variable named by parameter 1
function set_to_length
{
    # Check parameter 1 is a valid variable name and parameter 2 is defined
    [[ -z ${1-} ]]              && return 1
    [[ $1 == *[^[:alnum:]_]* ]] && return 1
    [[ $1 == [[:digit:]]* ]]    && return 1
    [[ -z ${2+_} ]]             && return 1

    printf -v "$1" '%s' "${#2}"
}
  • For example, running set_to_length var :/path/to/a/directory gives var the value 21.
  • There are many pitfalls associated with setting variables from names given in function parameters. This function avoids name clashes by using no additional local or global variables. It avoids bad variable names by explicitly checking that variable names are correctly formed. However, it will happily blow away your PATH, for instance, so it still needs to be used carefully.
  • I've tested it with Bash 3.2 (released in 2006). It won't work with older version of Bash because they didn't support printf -v.

Upvotes: 1

Barmar
Barmar

Reputation: 782488

${...} is only for expanding variables, and performing transformations on the variable value in the process. So it can't be used for other expressions.

However, you can get the string length using command substitution.

length=$(printf "%s" ":/path/to/a/directory" | wc -c)

printf is needed to avoid appending a newline to the string before piping to wc, as it would count that character.

Upvotes: 1

KamilCuk
KamilCuk

Reputation: 141881

could there be a workaround to using a variable?

No, there is no workaround. Any variable expansions, like ${#, work with variables, not strings.

is there a common name for this operator?

No, I do not think so. I call it "length operator" in my head. It would be really nice to have names for all those different expansions, they all are nameless.


 length=${#/path/to/a/directory} # `length` is set to 0

The $# variable is the special variable expanding to the number of arguments passed to a script. In the case of your shell, $# is 0, your shell was invoked with no parameters. The ${#/path/to/a/directory} is parsed as ${parameter/pattern/string} case, where the parts are parameter=#, pattern="path" and string="to/a/directory".

Upvotes: 2

Related Questions