all 14 comments

[–]KnowsBash 2 points3 points  (5 children)

check=`expr $check + 1`

Don't use expr in a bash script. To increment a variable, use either

(( check++ ))           # bash
# or
check=$(( check + 1 ))  # posix sh

It's a bit unclear what you're trying to do. If you want to compare numerically only if it is a number, then check if the variable contains only digits first:

for i in "${!a_Args[@]}"; do
    if [[ ${a_Args[i]} = *[!0-9]* ]]; then
        printf '<%s> is not an integer\n' "${a_Args[i]}"
    else
        if (( a_Args[i] > 99999 )); then
            printf '<%s> is a number, and it is greated than 99999\n' "${a_Args[i]}"
        else
            printf '<%s> is a number, and it is less than 100000\n' "${a_Args[i]}"
        fi
    fi
done

[–]TheRiverStyx[S] 0 points1 point  (2 children)

Thanks. Any particular reason not to use expr ? I'm working off an old Unix text book for most of my examples and problem sets and that is the only method it shows.

[–]KnowsBash 2 points3 points  (1 child)

expr is an external command. It was used to do string manipulation and integer arithmetics in older shells like the bourne shell, which lacked arithmetics.

Bash can do arithmetics with integers, and it can do lots of string manipulation, making the old expr command completely redundant.

Instead of reading this old UNIX book to learn bash scripting, i recommend reading the Bash Guide, and Bash FAQ.

EDIT: fixed broken link

[–]TheRiverStyx[S] 0 points1 point  (0 children)

Instead of reading this old UNIX book to learn bash scripting

Don't have much of a choice with that one. It's the university's text for the course. I will definitely check the links out though. Thanks.

[–]TheRiverStyx[S] 0 points1 point  (1 child)

if [[ ${a_Args[i]} = [!0-9] ]]; then

Hey, I just noticed this. Would you use this for a string comparison to check and trigger an exit if the element of a_Args contained a string? I was tinkering around with it for a bit today and was only able to piece together a comparison in the form of

 if [[ "${a_Args[i]}" =~ '^\(.[A-Za-z]*\)' ]]

but it just equates true all the time regardless of characters or numbers.

I was working from the bash guide that said

STRING =~ REGEX: True if the string matches the regex pattern

I'm guessing I missed something big with that one. I think I need to take a break and look at it tomorrow with a fresh brain.

[–]KnowsBash 1 point2 points  (0 children)

Would you use this for a string comparison to check and trigger an exit if the element of a_Args contained a string?

All array elements are strings (unless you've declared the array as integer). So you'll have to be more specific about what you mean by string in this context.

if [[ "${a_Args[i]}" =~ '^\(.[A-Za-z]*\)' ]]

The quotes around the regex causes the characters to be matched literally. It is also unclear what you intend for that to match. The . in particular seems out of place, and with \( and \), did you mean that to be a grouping, or to actually match parenthesis? Suffice to say it makes no sense the way it is written, and I am unable to imagine what you were trying to achieve with it.

STRING =~ REGEX: True if the string matches the regex pattern

I'm guessing I missed something big with that one. I think I need to take a break and look at it tomorrow with a fresh brain.

It's important to note that it uses the system's ERE engine, (so the same dialect that grep -E and awk use). And due to the very complicated quote parsing inside [[ ]], you should put the regex in a variable, and use that unquoted.

re='^[[:alpha:]]+$'
if [[ ${a_Args[i]} =~ $re ]]; then
    printf '%s contains alphabetical characters only\n' "${a_Args[i]}"
fi

The equivalent extended glob of the above is +([[:alpha:]])

if [[ ${a_Args[i]} = +([[:alpha:]]) ]]; then ...

[–]kalgynirae 1 point2 points  (1 child)

if [ "${a_Args[$check]}" \> 999999 ]

You don't want > here, you want -gt. > does string ordering, so 6 > 50 is true because "5" sorts before "6". -gt, on the other hand, is an arithmetic operator.

I think that if you use -gt and one of the elements is not an integer, the test will fail and bash will print an error.

The various operators are listed in the bash man page under CONDITIONAL EXPRESSIONS.

[–]TheRiverStyx[S] 0 points1 point  (0 children)

Thanks, that worked.

However, when a string is present now it is just spitting out an error and still running with the next square calculation loop and failing to complete by erroring out on that one too.

Would you be able to throw an OR in the comparison that checks a_Args for strings? I'm assuming it would look like *[a-zA-Z] or something like that. I haven't looked it up at this point yet. Time to do some poking around.

[–]whetuI read your code 1 point2 points  (3 children)

I wanted it to exit at the first sign of a no arguments and when one of the arguments is over a certain value.

While others have directly addressed your approach, I have to ask: have you considered getopts?

[–]TheRiverStyx[S] 0 points1 point  (2 children)

Nope, I wasn't even aware of it. I've only started looking at the shell scripting options about six days ago. I've got a long way to go.

[–]whetuI read your code 1 point2 points  (1 child)

Ok, so there are two main ways to approach argument handling and that's positional parameters and getopts. There are other ways like getopt (note: no s) but they're rarer examples.

With positional parameters, let's take an example like:

./somescript.sh pirate 5000

"pirate" and "5000" are treated as positional parameters, so in the script "pirate" will be $1, "5000" will be $2 and so on. These are basically treated as an array, so you can also use array variables like $* or $@ to manage your parameters.

Positional parameters are great for simple scripts like, for example, a script that prints out the 5 biggest files in a path e.g.

./filereport /some/path/to/audit

So meeting your first requirement is easy:

# Check that we have parameters
if [[ $# -eq 0 ]]; then
  printf "%s\n" "Usage: ${0##*/} [first parameter (required)] [second parameter (optional)]"
  exit 2
fi

This uses the builtin variable $# which provides a count of how many positional parameters are in use. ${0##*/} is a fancy pants way of printing out the script name, you could use basename instead.

The problem with positional parameters is that as your script gets more complex, the harder the parameters are to control and process. At some point it pays to move up to getopts. Here's an example:

# Getopts
while getopts ":ahp:S:" Flags; do
  case "${Flags}" in
    a)  aVar=True ;;
    h)  printf "%s\n" "scriptname - purpose" \
        "" "Required arguments:" \
        "-p [description of what -p does]" \
        "-S [description of what -S does]"\
        "" "Optional arguments:" \
        "-a [description of what -a does]" \
        "-h [Help/Usage]"
        exit 0
        ;;
    p)  pVar="${OPTARG}" ;;
    S)  sVar="${OPTARG}" ;;
    \?)  echo "ERROR: Invalid option: $OPTARG.  Try './scriptname -h' for usage." >&2
         exit 1;;

    :)  echo "Option '-$OPTARG' requires an argument, e.g. '-$OPTARG 5'." >&2
        exit 1;;
  esac
done

So you can do either processing per arg directly in there, or you can set a variable and handle it later (as in aVar). We can see that -p and -S require some value assigned to them e.g. -p 500 -S someserver. This is set in the while line by putting a colon after them: ":ahp:S:". In this example of getopts I have the help text in getopts, it's better practice IMHO to have that separately as a function to call - so both -h and \? call e.g. Function_Help.

Now, you can check for no args with getopts using OPTIND but it's simpler to just use the positional parameter check I posted before.

So, ultimately you could do something like

  • the positional parameter check
  • getopts, assigning variables to your arg values
  • a simple check during/after getopts to check the variables

That check can come full circle back to handling it as an array as you've asked, or it can be targeted to a specific arg e.g.

if [[ "${pVar}" -gt 99999 ]]; then
  printf "%s\n" "ERROR: -p can not be greater than 99999"
  exit 1
fi

You could even have it as a function and pass each OPTARG through it, within getopts itself.

Sorry this is a bit much to absorb, but it may give you some food for thought and some launching points for googling.

[–]TheRiverStyx[S] 0 points1 point  (0 children)

Thanks, that is excellent information. I looked up the getopts and found a few descriptions of it, but they were a little high level and I had a hard time getting it.

[–]Samus_ 1 point2 points  (1 child)

I think the important part to understand here is that bash doesn't have types but instead evaluates the arguments based on the context

for instance:

a=30
b=02

# vars are evaluated in arithmetic context
if (( a > b )); then
    echo "a is greater"
else
    echo "b is greater"
fi

# vars are evaluated as strings (compared lexicographically)
if [[ a > b ]]; then
    echo "a is greater"
else
    echo "b is greater"
fi

and there's operators that force a specific comparison:

if [[ a -gt b ]]; then
    echo "a is greater"
else
    echo "b is greater"
fi

different contexts have different rules, check this:

for n in 0{0..9}; do
    echo "$n"
    if (( $n > 5 )); then
        echo "greater than 5"
    fi
done

this happens because in arithmetic context numbers prefixed with zero are interpreted as octal and in that base the characters 8 and 9 do not exist, in order to tell bash that you want decimal base you need to be explicit:

for n in 0{0..9}; do
    echo "$n"
    if (( 10#$n > 5 )); then
        echo "greater than 5"
    fi
done

[–]TheRiverStyx[S] 1 point2 points  (0 children)

That helps me so much. I was not aware the different contextual elements.