4

I'm building a function that will calculate the gauge of wire required given amperage, distance(in feet), and allowable voltage drop.

I can calculate the "circular mils" given those values and with that get the AWG requirement. I started building a large if elif statement to compare the circular mils to it's respected gauge but I believe case is the right tool for this.

I haven't yet found any examples of case being used to compare numbers though so I'm wondering if it's even possible to do something like below:

what.gauge () {

    let cmils=11*2*$1*$2/$3
    let amils=17*2*$1*$2/$3


    case $cmils in

        320-403)
            cawg="25 AWG"
            ;;
        404-509)
            cawg="24 AWG"
            ;;
        510-641)
            cawg="23 AWG"
            ;;

        etc...

}
jesse_b
  • 37,005
  • 8
    "I believe case is the right tool for this" —honestly, the right tool for this is to use something other than Bash. Bash isn't really a programming language. It's extremely valuable for what it is, but numerical calculations should be done in other ways. And no, you can't use case for numerical comparisons in Bash. – Wildcard Aug 08 '17 at 02:05
  • Aha. For pratical examples, I would do something with files and directories. That's what Bash excels at. Or perhaps chaining together multiple commands in a tricky pipeline. – Wildcard Aug 08 '17 at 02:12
  • 1
    Ah, found what I was looking for: https://cmdchallenge.com Enjoy. :) – Wildcard Aug 08 '17 at 02:14
  • 1
    (For the exact use case you describe, using elif chains is simplest, but it's not a great illustration of what Bash is really for. Nor is it something that I would want to see in production code.) – Wildcard Aug 08 '17 at 02:15
  • There is a formula for calculating American Wire Gauge from circular mils; why not just apply that? – Blrfl Aug 08 '17 at 13:32

4 Answers4

10

POSIX Shell

Since you are looking for arithmetic tests and case doesn't do arithmetic, if-then seems like the natural approach:

if [ "$cmils" -lt 320 ]
then
        cawg="??"
elif [ "$cmils" -le 403 ]
then
        cawg="25 AWG"
elif [ "$cmils" -le 509 ]
then
        cawg="24 AWG"
elif [ "$cmils" -le 641 ]
then
        cawg="23 AWG"
fi

Bash Only

I prefer portable solutions but some people are enamored of bash's arithmetic syntax:

if ((cmils < 320))
then
        cawg="??"
elif ((cmils <= 403))
then
        cawg="25 AWG"
elif ((cmils <= 509))
then
        cawg="24 AWG"
elif ((cmils <= 641))
then
        cawg="23 AWG"
fi

This is both more powerful and more fragile than the POSIX syntax. To see why, try this code after setting cmils=cmils.

John1024
  • 74,655
  • 1
    Oh that's nice. I've seen that before but have never used parenthesis to compare like that. I'll have to read more into it. – jesse_b Aug 08 '17 at 02:31
  • 1
    You would start reading here: https://www.gnu.org/software/bash/manual/bashref.html#Conditional-Constructs -- scroll down for ((...)) – glenn jackman Aug 08 '17 at 10:00
  • Will this execute in subshell ((cmils < 320)) && echo "$cmils"? – Mamdouh Saeed May 18 '23 at 14:18
  • 1
    @MamdouhSaeed Yes, that works in either a shell or a subshell. To try it in a subshell, run: cmils=5; ( ((cmils < 320)) && echo "$cmils" ) – John1024 May 23 '23 at 23:36
5
case $cmils in

    3[2-9][0-9]|40[0-3])
        cawg="25 AWG"
        ;;
    40[4-9]|4[1-9][0-9]|50[0-9])
        cawg="24 AWG"
        ;;
    51[0-9]|6[0-3][0-9]|64[01])
        cawg="23 AWG"
        ;;
Hauke Laging
  • 90,279
  • Thanks! This will do it, but I think writing up all that regex would take longer than the elifs...I guess I should be careful what I ask for lol. Thanks again though. – jesse_b Aug 08 '17 at 02:29
  • 15
    This is technically the answer to the question that was asked. I would not want to be the person to maintain this code. – glenn jackman Aug 08 '17 at 02:36
  • 3
    @glennjackman It is not even fun to write it. If for whatever reason something like that has to be used then you would write a function for generating the regexes and just use variables in the case selectors. – Hauke Laging Aug 08 '17 at 07:30
  • 2
    International Obfuscated Bash Contest anyone? :) – pipe Aug 08 '17 at 12:31
5

As others have said, case doesn't support comparison operators, just glob pattern matching.

However, you can make a a set of if/elif/fi statements look more like a case statement by formatting it differently. e.g., based on John1024's answer:

if   [ "$cmils" -lt 320 ]; then cawg='??'
elif [ "$cmils" -le 403 ]; then cawg='25 AWG'
elif [ "$cmils" -le 509 ]; then cawg='24 AWG'
elif [ "$cmils" -le 641 ]; then cawg='23 AWG'
fi

or even:

[ "$cmils" -ge 320 ] && [ "$cmills" -le 403 ] && cawg='25 AWG'
[ "$cmils" -ge 404 ] && [ "$cmills" -le 509 ] && cawg='24 AWG'
[ "$cmils" -ge 510 ] && [ "$cmills" -le 641 ] && cawg='23 AWG'

NOTE: unlike anything using elif, this variant has the disadvantage of running at least the first test of every one of those lines. Using elif will skip all remaining tests after any test evaluates to true. You could put something like this in a function and add && return after setting cawg.

I personally find either of these to be far more readable (without all the extra linefeeds and alternating indentation cluttering things up), but opinions vary greatly on this particular coding-style/indentation issue :)

With everything lined up on the same (or very close) columns, they're easier to Yank, Paste, Edit too. Which is good when using the one true editor.

cas
  • 78,579
1

You can map a value into a range, and then use the index number of that range in a case statement:

cmil_limits=(320 404 510 642)
index=0
for limit in "${cmil_limits[@]}"
do
        if [ "$cmils" -lt "$limit" ]
        then
                break
        fi
        ((index++))
done
case "$index" in
   0)                   # < 320
        cawg="??"
        ;;
   1)                   # 320-403
        cawg="25 AWG"
        ;;
   2)                   # 404-509
        cawg="24 AWG"
        ;;
   3)                   # 510-641
        cawg="23 AWG"
        ;;
   4)                   # > 641
        cawg="??"
        ;;
esac

If $cmils is less than 320, we break out of the for loop on its first iteration, with index=0.  If $cmils is &nlt; 320 (i.e., is ≥ 320), we increment index (→1) and go on to the next iteration.  Then, if $cmils is < 404 (i.e., ≤ 403, assuming that it is a whole number), we break out of the loop with index=1.  And so on.  If $cmils is &nlt; 642, it is ≥ 642 and hence > 641, so we run to the end of the for loop and get index=4.

This has the advantages that it keeps the cutoff values together, all on the same line, and you don’t have to maintain redundant numbers (e.g., your current code, and that of one other answer, lists both 403 and 404, and both 509 and 510 — which is redundant, and more work to maintain if the numbers ever change.  I don't know whether this is real-world concern.)


cmil_limits is an array.  bash, ksh, and some other shells support arrays, but some others do not.  If you need to do something like this in a shell that doesn’t support arrays, you can just put the list directly into the for statement:

for limit in 320 404 510 642
do
    ︙

or use the shell’s parameter list as an array:

set -- 320 404 510 642
for limit in "$@"
do
    ︙

Some shells let you abbreviate the above:

set -- 320 404 510 642
for limit
do
    ︙

((…)) arithmetic is also a bashism (as is the let statement).  If you need to do something like this in a shell that doesn’t support ((…)) arithmetic, you can replace the

        ((index++))

statement (to increment index) with

        index=$(expr "$index" + 1)

Note that the spaces before and after the + are required.