4

I have an if statement in a script.

It looks like this:

if [ "$a" != "0" -a "$b" != "100" ]; then
 #some commands here

If I'm not mistaken, the line above will work if both conditions are true.

Now, how can I test that either one or both conditions are true before executing some commands?

jasonwryan
  • 73,126
cube00
  • 51
  • Strange that you tagged this with ksh. By using [ and not [[, you are invoking the test utility. The Posix and GNU test have nothing directly to do with ksh. – Otheus Jun 22 '15 at 08:22
  • @Otheus, the [ utility is built in ksh (on some systems, /bin/test is even implemented as a ksh script). [[ doesn't invoke the test utility. There are subtle variations between every [/test implementations. Posix is a standard body. It doesn't provide with an implementation of [, just a specification which various implementations (ksh/bash/Bourne/zsh/yash/ash... builtin, GNU test...) try to follow though they generally also support extensions. – Stéphane Chazelas Jun 22 '15 at 10:19

4 Answers4

13

The standard (POSIX sh and utilities) canonical legible ways would be:

  • string comparison:

    if [ "$a" != 0 ] || [ "$b" != 100 ]; then...
    
  • decimal integer comparison (0100 is 100, whether leading blanks are ignored or not depend on the implementation though).

    if [ "$a" -ne 0 ] || [ "$b" -ne 100 ]; then...
    
  • integer comparison (0x64, 0144 are 100 (POSIX mode has to be enabled for some shells for octals). Depending on the shell 100.0, 1e2, 50+50, (RANDOM 0.003% of the time)... will be as well):

    if [ "$((a != 0 || b != 100))" -ne 0 ]; then...
    

    However, if the content of the variables cause that arithmetic expansion to fail with a syntax error, that will cause the shell to abort, so you may want to run that in a subshell to account for that.

    if ([ "$((a != 0 || b != 100))" -ne 0 ]); then
    

    You probably shouldn't use that form anyway if the content of the variables is not under your control as that would be an arbitrary command execution vulnerability in many shells (bash, ksh, zsh) (for instance with values of $a like x[$(reboot)]).

Which one you'll choose depends on what the content of the variables may be and what you want to allow them to be. If you know they contain decimal integer numbers in their canonical form, all 3 will be equivalent.

In any case, avoid the -a/-o test operators which are deprecated and unreliable in the general case (not here if you have control on the content of the variables though).

1
[ "$((a||b^100))" -eq 1 ] && some commands

A shell's math expansion will handle the boolean && AND || OR and ! NOT conditions by evaluating the expression to either 1 for true or 0 for false. It will handle the bitwise & AND | OR and ^ XOR operators as well, but obviously those won't necessarily get you a 0 or 1, though a bitwise expression can serve as a field for a boolean eval, as it does here. Interestingly, the shell will even do the twiddle thing ~ and << left and >> right SHIFTs.

And so if a is true OR b^100 is true, the expansion evals to 1, matches the comparison -eq [ test ] and the shell continues to evaluate the rest of && some commands.

It is usually easier to evaluate/compare integers in that way than to try to string together ...

...the -a and -o binary primaries and the ( and ) operators [which] have been marked obsolescent. (Many expressions using them are ambiguously defined by the grammar depending on the specific expressions being evaluated.)

(^direct quote from the test spec)

Even when that works, 4 is as far as you can take it before you're in officially unspecified territory.

In the past, I've often found utility in applying a little abstraction like:

math(){ return "$((!($1)))"; }

...which can be used like...

math 'a||b^100' && some commands

Though that wasn't really my idea: I actually picked that up from the POSIX spec's XRAT Rationale section:

...[i]t was concluded that the test command ([) was sufficient for the majority of relational arithmetic tests, and that tests involving complicated relational expressions within the shell are rare, yet could still be accommodated by testing the value of "$(())" itself. For example:

    # a complicated relational expression
    while [ "$(( (($x + $y)/($a * $b)) < ($foo*$bar) ))" -ne 0 ] 

...or better yet, the rare script that has many complex relational expressions could define a function like this...

    val() { return "$((!$1))"; }

(I added the "quotes ^above, though. What's wrong with those guys?)

mikeserv
  • 58,310
  • 2
    You know there's some value in your code being written to carry your intention. Often more than making your code shorter. For instance, by writing it [ "$((a||b^100))" -eq 1 ], you then need 2 or 3 lines of comments to explain what it's meant to do and maybe why you wrote it that way. Here you want to do something unless a=0 and b=100. That doesn't transpire from that code. – Stéphane Chazelas Jun 19 '15 at 12:10
  • @StéphaneChazelas - the explanation is just to put the reader in a boolean logic mindset - that's the way i believe you should think with maths like this. I was just about to ask you what was up with all of those != 0 - it took me a moment or two to understand what was going on because of the clutter. a||b^100 makes immediate sense to me. If you believe it should be different - well, better, anyway - then please edit it. By the way - interactive zsh can do some nasty stuff w/ those bangs when the math spans newlines/involves many parens. – mikeserv Jun 19 '15 at 12:16
  • @StéphaneChazelas - I changed this a little - or, I added to it anyway. Dunno if it matters, but math() is always sourced for me and might demonstrate a little better how I typically enter/eval those kinds of tests - maybe it's why I spring for the positive eq as opposed a negation at top as well. math() is my context. – mikeserv Jun 21 '15 at 06:53
1

Use arithmetic evaluation and parentheses to avoid ambiguity:

if (( ($a != 0)  || ($b != 100) )); then
# some commands

Or, if you want to deal with strings as well, use [[.

muru
  • 72,889
  • You want to omit those quoted (which don't make any sense anyway) if you want to be compatible with zsh. (( a != 0 || b != 100)) would do just the same as a ksh/zsh/bash only solution. Note that it is integer comparison for bash/ksh88/pdksh and number comparison for zsh/ksh93. Not string comparison. Depending on the shell it can be recognise octal or hexadecimal numbers (0100 is not 100 in bash/ksh93, it is in zsh or mksh). – Stéphane Chazelas Jun 19 '15 at 11:50
  • @StéphaneChazelas that was just leftover from editing OP's code. For all that it does, zsh doesn't support octal numbers? O.o – muru Jun 19 '15 at 11:53
  • 1
    zsh supports octals as 8#123; or as 0123 when in POSIX mode (octal_zeroes option). – Stéphane Chazelas Jun 19 '15 at 12:29
0

Simpler answer than the one's I've seen above.

if [ "$a" != "0" -o "$b" != "100" ]; then
Otheus
  • 6,138