2

Someone pointed out a missing semicolon in a simple shell script and I'm curious why it's not throwing a syntax error.

Full script:

interval () {
    INTERVAL="$*"
    WHEN_TO_RUN=0
    while read LINE; do
        if (( $(epoch 0S) >= $WHEN_TO_RUN )) then
            echo $LINE
            WHEN_TO_RUN="$(epoch $INTERVAL)"
        fi
    done
}

line in question:

if (( $(epoch 0S) >= $WHEN_TO_RUN )) then

I'm also confused as to why the parens make that line work, when I'd normally expect:

if [ $(epoch 0S) -ge $WHEN_TO_RUN ]; then

on OSX if relevant

codeforester
  • 39,467
  • 16
  • 112
  • 140
  • 2
    `((` is syntax, whereas `[` is a regular command (with a builtin implementation for performance, but semantics equivalent to an external tool); they're treated differently by the parser. – Charles Duffy Aug 27 '18 at 15:00
  • Please *do* edit the title to make it more explicit about the specific syntax in question, even if you don't like my particular proposed edit. Right now, nobody can tell without clicking through which context it is you're curious about. – Charles Duffy Aug 27 '18 at 15:01
  • 1
    ...this isn't specific to `if` statements in general, it's specific to `(( ))`. – Charles Duffy Aug 27 '18 at 15:02
  • Thanks, do you happen to have a reference as to how `(( ))` differs from `[ ]`? The issue I have with the title is that you're including knowledge of the solution in the title :) If I knew to ask "why isn't a semi-colon needed with `(( ))`?" I probably wouldn't need to ask the question. –  Aug 27 '18 at 15:05
  • Are you familiar with the POSIX-specified `$(( ... ))` syntax? It's easier to understand `(( ... ))` coming from there than from `[ ... ]`. That said, I'm sure we have duplicates for the more general question of what `(( ... ))` is, whereas the specific question about a command separator not being needed after it is, as far as I know, novel on this site. – Charles Duffy Aug 27 '18 at 15:10
  • I'm aware of it, but I almost never write shell scripts. Trying to get a bit more comfortable with it instead of just reaching for python all the time! –  Aug 27 '18 at 15:12
  • 1
    ...see https://stackoverflow.com/questions/31255699/double-parenthesis-with-and-without-dollar as one closely-related preexisting SO question (for the more general topic, not the command-separator-syntax one). – Charles Duffy Aug 27 '18 at 15:12
  • BTW, as an aside -- while it doesn't matter so much here (where all your arguments are expected to be numeric), consider `interval=( "$@" )` to save your argument list into an array, and then using `epoch "${interval[@]}"` to expand that array onto a command's argument list (of course, in this particular case you could also just use `epoch "$@"`). Even better would be to start your function with `local when_to_run line; local -a interval`, marking your local variables as local (and getting them out of all-caps namespace reserved for variables meaningful to the shell and POSIX-specified tools). – Charles Duffy Aug 27 '18 at 15:24
  • 1
    ...re: all-caps namespace, see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html, keeping in mind that shell and environment variables share a single namespace: *The name space of environment variable names containing lowercase letters is reserved for applications. Applications can define any environment variables with names from this name space without modifying the behavior of the standard utilities.* – Charles Duffy Aug 27 '18 at 15:25
  • 1
    ...but yes, shell (unfortunately) has the same design misfeature that JavaScript does, re: variables being global unless explicitly declared otherwise. (Incidentally, the origin of the ksh `function funcname {` syntax is that when first implemented in ksh it made variables local-by-default when declared with `typeset` in functions so defined, whereas with functions declared in the normal POSIX style `typeset` declared global variables; however, in bash, `function` has no benefits whatsoever over the POSIX-compliant syntax). – Charles Duffy Aug 27 '18 at 15:28

1 Answers1

2

(( )) is an "arithmetic command" extension taken from ksh, permitting C-style math syntax to be used in shell scripts (POSIX specifies $((...)) as an expansion syntax; ((...)) as a command syntax is the element that is an extension). As such, its goal is compatibility with ksh's implementation -- and is, unambiguously, a standalone command, terminating with )). Unlike [ -- an regular command with no special parse-time treatment[1] -- it is syntactically significant (the parser has to behave differently inside of ((, and thus needs to be aware of it), so the parser knows when an arithmetic command has ended.

Prior to bash 2.05, a command separator was necessary after it, even in conditional context. However, this was an incompatibility with the upstream ksh implementation, and so bash was modified to explicitly not require a separator in this context.


[1] - [ does have a built-in implementation in bash, but it gets involved only at command dispatch, not at parse time.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441