2

I'm trying to create a simple function on macOS Sierra that counts the characters in a string. This works fine (added to my bashrc file):

function cchar() {
    str=$1
    len=${#str}
    echo "string is $len char long"
}

$ cchar "foo"
string is 3 char long

I'm trying to expand it with a -a option, so I added this to my function (and commented the rest out for testing):

while getopts "a:" opt; do
    case $opt in
        a)
            echo "-a param: $OPTARG" >&2
            ;;
    esac
done

After some testing while writing this, I've noticed everytime I run cchar -a "test", I have to run it without options (cchar) otherwise the next time I run it with the -a option, it doesn't recognise the option.

$ cchar

$ cchar -a "foo"
-a param: foo

$ cchar -a "foo"

$ cchar

$ cchar -a "foo"
-a param: foo
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Trav Easton
  • 401
  • 2
  • 6
  • 17
  • My end goal is to have the `-a` option only return the string count as an int, for use in other functions. I came upon this issue while trying to do that. – Trav Easton Feb 20 '17 at 04:35
  • I tried `unset opt` after the `while` loop in case the var is remembered between after the function ends, didn't fix it. Also, different redirections had no positive effect (`&2>1`, `>&1`, [blank]) – Trav Easton Feb 20 '17 at 04:52
  • Redirections are not command-line options. Why would they affect anything? – rici Feb 20 '17 at 04:56
  • In the 4th line of my second code block. Since that wasn't echoing, I thought there could be a problem there. – Trav Easton Feb 20 '17 at 05:06

2 Answers2

7

You have to reset the variable OPTIND, which keeps track of the current positional argument number. It should suffice to make that variable local to your function.

rici
  • 234,347
  • 28
  • 237
  • 341
  • That solved my problem, thank you. – Trav Easton Feb 20 '17 at 05:09
  • See [getopts](https://www.gnu.org/software/bash/manual/bash.html#index-getopts) in the Bash manual. It says, in part: _Each time it is invoked, getopts places the next option in the shell variable `name`, initializing `name` if it does not exist, and the index of the next argument to be processed into the variable OPTIND. OPTIND is initialized to 1 each time the shell or a shell script is invoked. … The shell does not reset OPTIND automatically; it must be manually reset between multiple calls to getopts within the same shell invocation if a new set of parameters is to be used._ – Jonathan Leffler Feb 20 '17 at 05:16
  • 1
    Yes, `getopts` is designed for parsing script options, not function arguments. But it will work just as well with local variables (although `local OPTIND=1` is probably better, just in case.) – rici Feb 20 '17 at 05:22
  • What would be a better way to handle function arguments? – Trav Easton Feb 20 '17 at 05:32
  • @trav: getopts is just fine. I use it a lot. I was trying to explain the failure to initialize OPTIND. – rici Feb 20 '17 at 05:34
  • Plus one for the local variable override. – xdhmoore Jul 17 '17 at 21:32
0

The final code I ended up using:

# Count characters in a string
function cchar() {
    local OPTIND

    while getopts "a:" opt; do
        case $opt in
            a)
                echo ${#OPTARG}
                return
                ;;
        esac
    done

    echo "string is ${#1} characters long"
}

$ cchar "foo bar"
string is 7 characters long

$ cchar -a "foo bar"
7

Note: I had to use return in lieu of exit, when sourced from .bashrc, exit will close the current shell.

Trav Easton
  • 401
  • 2
  • 6
  • 17
  • 1
    Note that you could simply use: `echo "string is ${#1} char long"` in the long form (and `echo "${#OPTARG}" >&2` in the short form). Is there a good reason to write to standard error in the short form? It would be more usual to write to standard output — you can then redirect the output to standard error when you invoke the function if that's what you need. – Jonathan Leffler Feb 20 '17 at 05:21
  • You're right, thanks. I was writing to stderr because [the accepted answer in this question](http://stackoverflow.com/questions/14447406/bash-shell-script-check-for-a-flag-and-grab-its-value) used it. – Trav Easton Feb 20 '17 at 05:59