2

I'm trying to better understand how to write bash scripts using positional parameters as arguments to the script. Specifically I want to understand how to make a script as user friendly as possible, capable of handling multiple arguments from any position. Much in that ls -lst is the same as ls -stl. I decided to attempt a password generator. I'm not very familiar with writing scripts that take arguments. After a while I just decided to do the whole thing using if statements.

Note: to give credit where credit is due, the rand_string function in the script below came directly from this answer.

#!/bin/bash

## grep -v '##' will remove all the comments if you're so inclined :)
usage(){
echo "usage: $0 [n][-c]||[-h]"  
}

## Character set used to create random strings 
chars=( {a..z} {A..Z} {0..9} \! \" \# \$  \% \& \( \) \* \+ \, \- \. \/ \: \; \< \= \> \? \@ \[ \] \^ \_ \` \{ \| \} \~ \\ )

## Create random string from character set
rand_string() { 
    local c=$1 ret=
    while((c--)); do
        ret+=${chars[$((RANDOM%${#chars[@]}))]}; done
    printf '%s\n' "$ret"
}

## get a random number between 14-50
length() {
    python -S -c "import random; print random.randrange(14,50)"
}

## regular expression to test for valid numbers
re='^[0-9]+$'

## if no options specified 
## create a random string of charcters
## of a random length between 14-50 characters
## display password on screen and exit
if [ ! "$1" ]; then
    set - "$(length)"
    password="$(rand_string "$1")"
    echo "$password"
    exit
fi

## if option 1 or option 2 is -h or -help
## display usage and exit
if [[ "$1" == -h || "$1" == -help || "$2" == -h || "$2" == -help ]]; then
    usage
    exit
fi

## if more than 2 options
## exit with error code 1
if [[ $# -gt 2 ]]; then
    echo "Invalid number of options specified"
    usage
    exit 1
fi
## if exactly 2 options
if [[ $# -eq 2 ]]; then
    ## test if option 1 is a number
    ## if option 1 is NOT a number 
    if [[ ! "$1" =~ $re ]]; then
        ## test if option 1 is -c or -copy
        if [[ "$1" == -c || "$1" == -copy ]]; then
            ## if option 1 is -c or -copy
            ## test if option 2 is a number
            ## if 2 is a number and 1 is -c or -copy 
            ## execute the command
            if [[ "$2" =~ $re ]]; then
                set - "$(length)"
                rand_string "$1" | pbcopy
                echo "Password copied to clipboard"
                exit
            ## if option 1 is -c or -copy
            ## but option 2 is NOT a number
            ## exit script with error code 1
            elif [[ ! "$2" =~ $re ]]; then
                echo "Unrecognized option \"$2\""
                usage
                exit 1
            fi
        else
            echo "Unrecognized option \"$1\""
            exit 1
        fi
    ## if option 1 is a number
    elif [[ "$1" =~ $re ]]; then
        ## and option 2 is -c or -copy
        ## execute the command
        if [[ "$2" == -c || "$2" == -copy ]]; then
            rand_string "$1" | pbcopy
            echo "Password copied to clipboard"
            exit
        ## if option 1 is a number 
        ## but option 2 is not -c or -copy
        ## exit script with error code 1
        else
            echo "Unrecognized option \"$2\""
            usage
            exit 1
        fi
    fi
## if exactly one option specified
elif [[ $# -eq 1 ]]; then
    ## if option is NOT a number
    ## check if option is -c or -copy
    if [[ ! "$1" =~ $re ]]; then
        ## if option is -c or -copy
        ## execute the command 
        if [[ "$1" == -c || "$1" == -copy ]]; then
            set - "$(length)"
            rand_string "$1" | pbcopy
            echo "Password copied to clipboard"
            exit
        ## if option is neither a number nor -c or -copy
        ## exit script with error code 1
        else
            echo "Unrecognized option \"$1\""
            usage
            exit 1
        fi
    ## if option is a number
    ## execute the command
    elif [[ "$1" =~ $re ]]; then
        rand_string "$1"
    fi
fi

This script takes two optional arguments:

  • any whole number e.g.. 10, 29, 54 to determine the length of string

  • -c or -copy to copy the string to the clipboard

  • -h or -help to display the usage function

Any of the above arguments can be called from the $1 or $2 position. Any of these arguments may also be omitted.

Assuming the script is called password any of the following are valid uses:

password

password 25

password -c

password 16 -copy

password -c 42

password -help

But the script itself is a giant mess of if statements. It's long, messy and hard to read.

So my question is: How could this be written to take the same arguments shown in valid uses without using so many if statements?

I'm not asking anyone to rewrite my script. I'm just looking for some guidance to gain better understanding of how to do this correctly.

Community
  • 1
  • 1
I0_ol
  • 1,054
  • 1
  • 14
  • 28
  • 2
    Did you try considering `getopts` for your requirement? it is tailor-made for this – Inian May 02 '17 at 06:44
  • Appreciate your detailed research, but still it is not _exactly_ clear what you want to do? Can you give a _minimal_ input and output needed for us to test on? – Inian May 02 '17 at 06:47
  • 1
    You think I should shorten the question? – I0_ol May 02 '17 at 06:54
  • Not exactly _shorten_ it, but make it easy for people to understand the code you are looking for. e.g. I want to do this, this way etc. – Inian May 02 '17 at 07:01
  • Or give us a list of all possible command-line inputs you want to achieve out of this – Inian May 02 '17 at 07:22
  • I think I have clarified what I'm trying to achieve. Let me know if anything is unclear. – I0_ol May 02 '17 at 07:46
  • Reading about `getopts` right now on the bash hackers wiki. Any other information resources are appreciated. – I0_ol May 02 '17 at 08:11
  • By the way, you should consider `--copy` rather than `-copy`. `-copy` implies options c, o, p, and y are set. – cdarke May 02 '17 at 08:30
  • @cdarke You mean with `getopts`? I just read it doesn't support long options. – I0_ol May 02 '17 at 08:41
  • Long options are non-standard, but commonly used. `getopts` only supports standard options. `-copy` would not work with `getopts`. – cdarke May 03 '17 at 06:33
  • Yes, I read more about it last night. This [answer](http://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options/7680682#7680682) shows how it can be done. I see what you mean about `-copy` not working as it considers each letter a separate argument. – I0_ol May 03 '17 at 06:49

1 Answers1

1

If you don't want to use getopts then the simple way is to just shift the arguments.

#!/bin/bash

usage(){
        echo "usage: $0 [n][-c]||[-h]" >&2
        exit 1
}

declare copy=
declare argument=

while [ "$1" ]; do
        case "$1" in
                -h|-help)
                        usage
                        ;;

                -c|-copy)
                        copy=1
                        ;;

                *)
                        [[ "$1" =~ ^[0-9]+$ ]] || usage
                        argument="$1"
                        ;;
        esac
        shift
done

[ "$argument" ] || argument=$(length)

if [ "$copy" ]; then
        rand_string "$argument" | pbcopy
else
        rand_string "$argument"
fi
ccarton
  • 3,556
  • 16
  • 17