1

I'm currently testing changes in my scripts for performance purposes. Specifically, I want to change if to case statements.

However, being new to shell programming I'm getting stuck on this type of statement:

if [ "$A" == "x" -a "$B" == "y" ] 
    then
        let a=a+1
fi 

I tried this but it didn't work:

case "$A$B" in
    "x""y") let a=a+1
;;
esac

What did I get wrong?

And is it possible to manage a "!=" in case statements? Such as:

if [ "$A" == "x" -a "$B" != "y" ] 
then
    let a=a+1
fi 
  • This optimisation may not be worth it if your shell has [ as a builtin (You can check with command -V [ which says "[ is a shell builtin". When optimising, specify which shell you want to target for. – meuh Mar 22 '17 at 14:27
  • @Kusalananda I edited my post :) – Matieu Mar 22 '17 at 14:36
  • @Matieu this is a shell script so it is slow and inefficient pretty much by definition. What makes you think switching from if to case will give a non insignificant improvement? Especially when you consider the time you will spend trying to figure out how to do it, then doing it, and then fixing the bugs that your changes introduced. – terdon Mar 22 '17 at 14:38
  • @terdon I did some research and found los-gatos.ca.us/davidbu/faster_sh.html or unix.stackexchange.com/questions/67057/… . Some worked and some did not but at the end after few hours I managed to reduce the script execution time by 15%, which is something significant when the script will be executed everyday. Moreover this is my first shell script and by definition it surely is bad code. – Matieu Mar 22 '17 at 14:47
  • 1
    @Matieu yes, precisely. The answer you linked to states: "The moral of the story is that if you really need more speed, you are better off with a (semi)compiled language like Perl or Python [. . .]" :) – terdon Mar 22 '17 at 14:50
2

Your case example works for me, but note that you are comparing the concatenation of A and B, so the pattern will match also if, say A is xy and B is empty. (I think the double-quotes in the middle are redundant.) case only takes a single word to compare against the patterns, so you can't compare several variables except by concatenating.

In the standard test, or [ command, the comparison operator is =. == works in some systems, but not all. Also, combining conditions using -a is problematic, at least with conditions like -n. (see this question). Better use if [ "$A" = x ] && [ "$B" = y ] ; then ....

As for inverting a case, you'd need to come up with a pattern that matches everything but the target string. extglob in Bash and others allows for matching anything except:

case $B in !(y) ) echo not y ;; esac

Without it, you'd need to do some more work in inverting the pattern, or use multiple patterns with the first ones matching the string and a later default match doing the actual action.

case $B in ??*) echo not y ;; [^y]) echo also not y ;; esac

case $B in y) ;; *) echo still not y ;; esac

(no guarantees, especially on the first one. Frankly, the whole idea of doing this seems horrible.)

Anyway, since you mentioned performance reasons, you might consider changing to Perl or Python instead of the shell, not as much because of simple comparisons, but because they'll allow you to do more without calling external programs (like grep, sed, cut, or jq). Also, the first rule of performance optimizations: measure the difference.

  • Thanks for your anwser I'm starting to understand how it really works. However I don't want the concatenation of the strings but more like if a equals to x AND b equals to y, x and y being preset strings and a & b strings set by the jq command (that parses json files) at the beginning of the script, then do stuff. – Matieu Mar 22 '17 at 14:33
  • @Matieu, edited, in short, I don't think you can get that. – ilkkachu Mar 22 '17 at 14:42
  • @ilkkachu case $B in ??*) echo not y ;; [^y]) echo also not y ;; esac will match even when $B is empty. case $B in '' | ??* | [!y] ) echo not y ;; esac – user218374 Mar 22 '17 at 16:11
0
#  $A = "x" && $B = "y"
case ${A:--}/${B:--} in 'x/y' ) let a=a+1;; esac

# $A = "x" && $B != "y"
case $A in 'x' ) case $B in '' | ??* | [!y] ) let a=a+1;; esac;; esac

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.