10

I have a function that depending on an argument where the functionality changes.
I know I can do:

function foo {  
  PARAM1=$1  
  PARAM2="$2"  
  VAR=$3  
  if[[ -z "$VAR" ]]; then  
   # code here  
  else  
   # other code here  
  fi  
}  

I was wondering if there is a more appropriate approach for bash. This would work but I wouldn't like to have something like

foo "x" "y" "blah"  
foo "x" "y" "true"  
foo "y" "y" "1"

all to be equivalent.

Is there a more Bash suitable approach?

5
  • 1
    What would be considered "false"? – Kusalananda Jul 17 '18 at 7:43
  • @Kusalananda:I define that. I am looking for something that is the most appropriate for a bash script. E.g. if passing "true" is suitable I am ok with that. – Jim Jul 17 '18 at 7:47
  • Related: stackoverflow.com/a/48568788/1997354 – LinuxSecurityFreak Jul 17 '18 at 7:50
  • The way you define a false condition also affects the way you test for a true condition. For example, if any string is true, then the string false is true. – Kusalananda Jul 17 '18 at 7:52
  • @Kusalananda: But my issue is not how to define a boolean but how to handle it as a function argument properly – Jim Jul 17 '18 at 7:54
7

You may supply a command line option to your function. Using command line options that takes no argumets is a common way of providing binary/boolean values ("on/off", "true/false", "enable/disable") to shell scripts, shell functions and utilities in general.

foo () {
    local flag=false
    OPTIND=1

    while getopts 't' opt; do
        case $opt in
            t) flag=true ;;
            *) echo 'Error in command line parsing' >&2
               exit 1
        esac
    done
    shift "$(( OPTIND - 1 ))"

    local param1="$1"
    local param2="$2"

    if "$flag"; then
        # do things for "foo -t blah blah"
    else
        # do things for "foo blah blah"
    fi
}

The option -t acts like a boolean flag for the user. Using it would set flag inside the function to true (changing it from its default value of false). The -t option would be used as the first argument to the function.

Calling the function would be done using

foo "some value" "some other value"

or

foo -t "some value" "some other value"

where the latter call would set the flag variable in the function to true.

4
  • Very helpful, but I think that won't work for Mac right? – Jim Jul 18 '18 at 6:52
  • @Jim I would say it would work on macOS too. Care to share why you think it wouldn't? – Kusalananda Jul 18 '18 at 7:01
  • You are right it works fine! To be sure this shift "$(( OPTIND - 1 ))" affects the next params? Does it matter the order -t is passed? (Before other args, after other args, in the middle etc) – Jim Jul 19 '18 at 13:22
  • @Jim The shift removes -t (all processed command line options) from the list of command line arguments. The options need to occur before any other non-options. – Kusalananda Jul 19 '18 at 13:25
3

In general

In general passing booleans in to functions, in any language, is unreadable. e.g. calculate_interest 5y 4% true. The reader is left wondering what true is.

Therefore use an enumeration: { per_month, per_year }. Now you can do calculate_interest 5y 4% per_year. This is more readable.

In bash

Bash is not statically typed, (or strongly typed, or have much of a type system at all), so you can pass in many different values. One will lead this way, the others will not. It is not desired to have many different values that lead down the same path.

Therefore add code to check that the input is one of the two acceptable values. Do this at the start of the function, you don't want to bail out ½ way through doing something.

What to do if you have to call a function that takes a Boolean

So some one did not follow my advice, and you have to call a function that takes a boolean. What can you do to make your code readable?

  • If the language (such as Python) allows named arguments e.g. calculate_interest 5y 4% per_year=True, then use them. It will help, but does not tell you the meaning of calculate_interest 5y 4% per_year=False.
  • If the language does not have named arguments, then the only options are to live with unreadable code (not an option), or to wrap the functions in literate functions.
4
  • well, Python has named arguments, and stuff like Popen("somecmd...", shell=True) is pretty ok on the readability side. :) – ilkkachu Jul 17 '18 at 8:58
  • 1
    I agree that sometimes named arguments can make it literate. Though in your example would use_shell=True, be more correct. “Use_shell” is a verb, “shell” is a noun. – ctrl-alt-delor Jul 17 '18 at 9:10
  • All these are very helpful points in your answer, but how would that apply to bash specifically? – Jim Jul 18 '18 at 6:53
  • @Jim see bash section: Pass in a word, and check it with a case, have a default (*), and error if input is invalid. I would also look at the answer that uses getopt. – ctrl-alt-delor Jul 18 '18 at 7:55
2

I recommend to follow a format like this:

foo() {
  # Limit scope of variables
  local 'opt1' 'opt2' 'opt3' 'operands'

  # Default values
  opt1='default1'
  opt2='default2'
  opt3='false'
  operands=()

  # Arguments handling
  while (( ${#} > 0 )); do
    case "${1}" in
      ( '--opt1='* ) opt1="${1#*=}" ;;           # Handles --opt1
      ( '--opt2='* ) opt2="${1#*=}" ;;           # Handles --opt2
      ( '--opt3='* ) opt3="${1#*=}" ;;           # Handles --opt3
      ( '--' ) operands+=( "${@:2}" ); break ;;  # End of options
      ( '-'?* ) ;;                               # Discard non-valid options
      ( * ) operands+=( "${1}" )                 # Handles operands
    esac
    shift
  done

  ...
}

That way the function will be more robust and readable:

$ foo
Options:
  opt1: [default1]
  opt2: [default2]
  opt3: [false]
$ foo --opt1='value1' --opt2='value2' --opt3='true' 'foo' 'bar' 'baz'
Options:
  opt1: [value1]
  opt2: [value2]
  opt3: [true]
Operands:
  1: [foo]
  2: [bar]
  3: [baz]

Pros:

  • Easy to read and understand.
  • Syntax is similar to any typical command-line utility.
  • It's easy to add more options without breaking compatibility.

Cons:

  • May be overkill for small and simple scripts.
  • Hard to write a portable and POSIX-compliant equivalent that also handles long-options and operands.

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.