3

Which is generally the best practice in bash?

hotstrings=()

if [ ${#hotstrings[@]} != 0 ]; then return; fi

vs

if [ ${#hotstrings[@]} -gt 0 ]; then return; fi

vs

if [ "${hotstrings[@]}" != '' ]; then return; fi

vs

Something else?

Emily
  • 2,129
  • 3
  • 18
  • 43
  • You have many metrics that might determine "best". Are you after speed, efficiency, readability, or compatibility? I'm sure I've missed some options out. – SEoF Jan 25 '22 at 09:25
  • 1
    Compatibility first, speed next, readability last. – Emily Jan 25 '22 at 09:36
  • 1
    `Compatibility first` Bash arrays are compatible with Bash. `speed next` write all possible combinations and measure the speed of execution of them. Firstly `[[` is faster then `[`. And `-z` may be faster then `!=`. And `((` may be faster then `-gt`. But overall, Perl or Python or Awk _will_ be faster then Bash, so if you want speed just don't use bash. – KamilCuk Jan 25 '22 at 11:02
  • 1
    @KamilCuk I'm sure others have done this already, and come up with best practices for the majority of computers. It would be silly to repeat what's already been done. – Emily Jan 25 '22 at 11:04
  • 1
    If you're after compatibility, i.e. needs to run on other shells, avoid using `[[...]]` in your if statement, and therefore stick with `-gt` and similar comparitors. If it will only ever run in Bash (or bash derived shells), then yes, follow KamilCuk's advice. – SEoF Jan 25 '22 at 12:37
  • Newbie q: What's the dif between [[ ]] and [ ] across systems? – Emily Jan 25 '22 at 13:59
  • Bash FAQ is a great resource. For your `[[ ]]` question, see http://mywiki.wooledge.org/BashFAQ/031. – Nic3500 Jan 25 '22 at 17:52
  • Does this answer your question? [How to check if a variable is set in Bash?](https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash) – Nic3500 Jan 25 '22 at 21:56
  • @SEoF Arrays are not compatible with other shells anyway, so the compatibility argument is moot, as already pointed out in previous comments. – tripleee Jan 26 '22 at 08:10
  • Array's defined in that way, sure, but other collections are compatible. – SEoF Jan 26 '22 at 09:20

2 Answers2

1

The comparison against an empty string looks brittle, and needlessly interpolates a potentially large array into a string just to compare it against the empty string.

I'm not sure whether numeric or string comparison against zero is more efficient in general, and in isolation, you would be hard pressed to perceive any difference even if there is one. My hunch is that strings are more efficient, and arithmetic requires some additional work by Bash.

The following quick test seems to bear this out:

bash$ array=({1..10000})
bash$ echo "${#array[@]}"
10000
bash$ time for i in {1..10000}; do [ "${#array[@]}" -gt 0 ]; done

real    0m0.109s
user    0m0.097s
sys 0m0.007s
bash$ time for i in {1..10000}; do [ "${#array[@]}" != 0 ]; done

real    0m0.100s
user    0m0.092s
sys 0m0.007s

For an apples-to-apples comparison, you should properly measure -ne (numeric non-equivalence) instead of -gt, but it doesn't seem to make any real difference. If anything, it seems slightly slower, somewhat to my surprise.

So with this rather ad-hoc measurement I would hesitantly recommend the string comparison != 0 over the arithmetic one. However, switching to [[ over [ makes a much bigger difference, and is more robust to boot.

bash$ time for i in {1..10000}; do [[ "${#array[@]}" != 0 ]]; done

real    0m0.065s
user    0m0.064s
sys 0m0.001s

(For the record, I did check that -gt 0 is slightly slower in this case too.)

Anyway, the test might obviously come out quite differently if your typical values are drastically different from what I guessed would be useful for a quick test, or if your Bash version is different from mine. (This was Bash 3.2.57(1)-release which is what MacOS still ships out of the box.) If this is really important, test with your own representative data.

All of these constructs are compatible back to Bash v2 at least; obviously, if you are not using Bash at all, your question is not well-defined (Zsh and ksh have similar array syntax, but you would hardly want to try to maintain compatibility across them).

Rock-bottom baseline:

bash$ time for i in {1..10000}; do true; done

real    0m0.062s
user    0m0.047s
sys 0m0.006s
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • Given `zsh` is the default terminal for Mac now, you probably don't want to suggest against maintaining compatibility. – SEoF Jan 26 '22 at 09:21
  • 1
    Huh? If you write a Bash script you want it to be compatible with Bash, period. If Zsh can emulate that, good for them; but the target is then still Bash compatibility. – tripleee Jan 26 '22 at 09:26
0

I'd use this:

[[ ${arr[@]}  ]] || echo empty

Update, or this:

((${#arr[*]}<1)) && echo empty

Testing:

$ arr=()
$ [[ ${arr[@]}  ]] || echo empty
empty
$ ((${#arr[*]}<1)) && echo empty
empty

$ arr=('')
$ [[ ${arr[@]}  ]] || echo empty # fail (
empty
$ ((${#arr[*]}<1)) && echo empty # fine )
Ivan
  • 6,188
  • 1
  • 16
  • 23
  • Fails for `arr=('')` (though that could be fixed with [correct quoting](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable)) and again needlessly interpolates the whole array for a check which could be done without that. – tripleee Jan 26 '22 at 13:15
  • For the record, the second one is competitive with my recommendation, though I think just a tiny tad slower (but really within the error margin). Why are you using * instead of @ though? – tripleee Jan 26 '22 at 13:34
  • To highlight the differences between these methods. – Ivan Jan 26 '22 at 13:44