9

For instance, the following command does not work:

if [[-e xyz]]; then echo File exists;fi

ksh gives the following error

[[-e: command not found

Is that because "[[-" is ambiguous?

Sergiy Kolodyazhnyy
  • 103,293
  • 19
  • 273
  • 492
AcBap
  • 93
  • 4
  • 2
    `[[` is a *keyword* - presumably the shell's parse tree requires that keywords must be delineated by whitespace – steeldriver Jun 23 '19 at 00:11
  • Another way to look at it is probably that you could write a function [[-, how would the shell know to parse "do the e flag on [[" instead of "do the function [[- on e". – pbhj Jun 23 '19 at 21:16

5 Answers5

15

The simplest explanation would be, because in the manual it appears as [[ expression ]] so there has to be space between [[ and expression and closing ]]. But of course we can attempt to look at it more in depth. Shells have somewhat complex grammar and rely heavily on the concept of "word splitting". In particular, ksh(1) manual states:

The shell begins parsing its input by breaking it into words. Words, which are sequences of characters, are delimited by unquoted white-space characters (space, tab and newline) or meta-characters (<, >, |, ;, &, ( and )). Aside from delimiting words, spaces and tabs are ignored, while newlines usually delimit commands.

So as stated in the manual, sequence of characters [[-e is considered a shell word. ksh would look for such command in the list of built-ins and special operators ( like for or while ), then look for an external command - and voilà - none found, hence there's an error message about command not found.

In this case [[ are not special meta-characters either. If they were, the -e part in [[-e would be considered a shell word, not an argument to [[ itself. However, [[ is described as compound command, and the manual says the following:

Compound commands are created using the following reserved words - these words are only recognized if they are unquoted and if they are used as the first word of a command (i.e., they can't be preceded by parameter assignments or redirections):

So in order for [[ to be recognized as a compound command, it has to be a first word of a command or command list, which by previous definition of a "word" implies it has to be separated by spaces,tabs,or newlines from other words/arguments.


You've asked in the comments: "Why can't the parser stop as soon as it finds "[[" pattern, and treats that as the start of a conditional command?" Short answer is probably because 1) influence of [ syntax since it was originally an external command, and for POSIX standard compliance exists as external command even today, and 2) because the shell parser is built so. Shell parser can recognize other non-space delimited special characters: echo $((2+2)) and (echo foobar) work perfectly fine. Maybe in future when ksh development resumes or there appears to be a fork or clone (like mksh or pdksh) someone will implement space-less syntax in [[-e.

See also:

Sergiy Kolodyazhnyy
  • 103,293
  • 19
  • 273
  • 492
  • 3
    I think the ksh manual quote really is on the spot. It's similar to any other programming language: In C, `char` is a keyword but you *still* cannot write `charsomeletter = 'A';` and expect the parser to stop after seeing `char`. – Peter - Reinstate Monica Jun 23 '19 at 14:44
  • I think the main difference between your example of "char" and the symbol "[[" in the question is that, an alphanumeric identifier, in general, needs space delimiter to separate from others; special symbols as tokens, on the other hand, don't need space delimiters. – AcBap Jun 24 '19 at 01:49
  • Exactly.  I’m not sure the comparison to C is helpful.  In C one can say `i--<=++j`, and the compiler has no problem parsing that as ``i -- <= ++ j``. – Scott - Слава Україні Jun 24 '19 at 02:05
  • @Scott I think the C comparison is helpful (and that it's sort of secondary that C identifiers only allow alphanumerics and `_`). Ksh has `(` as an *operator*, and `(echo hello)` parses as `( echo hello )`. It has `if`, `{`, `[[`, and other *keywords*, and `ifx`, `{x`, and `[[x` are each one token--like how `charsomeletter` is one C token. In contrast, an unquoted `(x` in ksh is two tokens--like how `i--` is two tokens in C. What it conceptually even *means* to be an operator differs between Bourne-style shells (like ksh) and C. But Peter A. Schneider pointed out a real *lexical* similarity. – Eliah Kagan Jun 24 '19 at 05:13
2

What is important to note is that [ is a command, and the shell keyword [[ in bash and ksh is based on [.

[[ is used in a similar way to [. Sometimes you can even replace [ ] with [[ ]] with no change in behavior. With [, like any command, a space is mandatory between the command and its arguments. The same applies to [[.

We used to have only /usr/bin/[. Now most shells have [ built in for efficiency—but the syntax is the same. In shells that provide [[, it functions as a more versatile alternative to [.

Here is the description for [ in bash:

$ help [
[: [ arg... ]
    Evaluate conditional expression.

    This is a synonym for the "test" builtin, but the last argument must
    be a literal `]', to match the opening `['.

So [ is equivalent to test (aside from expecting a ] argument at the very end). help test will give even more detail on it. You can compare this to help [[.

There is also a man page for the external command [ (man \[).

In the case of

if [[-e
  • Neither [ nor [[ is a command, there. The full word [[-e is.
  • The if [[-e makes it a test for true/false. So does a command [[-e exist?

Is that because "[[-" is ambiguous?

Yes. Or no. [[-e is what it is: nothing that the shell understand so it assumes it is its own command. ;-)

Eliah Kagan
  • 116,445
  • 54
  • 318
  • 493
Rinzwind
  • 293,910
  • 41
  • 570
  • 710
  • "% help [[" says that "[[" is a conditional command. Why can't the parser stop as soon as it finds "[[" pattern, and treats that as the start of a conditional command? – AcBap Jun 23 '19 at 01:21
  • @Myloti `[[-e` is a potentially valid (if weird-looking) command name. – Gordon Davisson Jun 23 '19 at 06:01
2

The space is a deliminator and is required. As you can see from shellcheck:

$ shellcheck gmail-browse-msgs-algorithm.sh

In gmail-browse-msgs-algorithm.sh line 847:
        [[$Today != "${DaysArr[ i + DAY_DELETED_ON_NDX ]}" ]] && continue
          ^-- SC1035: You need a space after the [[ and before the ]].

(Both ksh and bash support [[ and don't work without the space. Shellcheck gives exactly that output with similar ksh and bash scripts containing that buggy line.)

Why deliminators are needed has to do with tokens and lexicons.

Eliah Kagan
  • 116,445
  • 54
  • 318
  • 493
WinEunuuchs2Unix
  • 99,709
  • 34
  • 237
  • 401
  • Please note, OP asked about `ksh` shell in the question. Bash borrows `[[` operator from `ksh` and in this question their behavior is identical, but I would be cautious of assumptions as there's some significant differences between `ksh` and `bash` behavior and internals. That is just because shellcheck works on bash script, it might not necessarily be appropriate tool for ksh scripts. Just something to keep in mind while approaching different shells – Sergiy Kolodyazhnyy Jun 24 '19 at 02:10
  • @SergiyKolodyazhnyy I was being opportunistic using shellcheck to highlight the `[[` built-in rules. I was in no way inferring that `ksh` was a shell that `shellcheck` could find errors in. I hope others appreciate `ksh` and `bash` are two different interpreters. Thank you for mentioning that. Also worth noting `[[` is a bash built in whilst `[` is an external command. – WinEunuuchs2Unix Jun 24 '19 at 02:52
  • Actually `[` is also a built-in in bash :) http://manpages.ubuntu.com/manpages/bionic/man7/bash-builtins.7.html But you're not far off - `[` or `test` is required to be an external command. In the days of original Bourne shell `[` was in fact an external command, and still exists nowadays as `/usr/bin/[` because POSIX requires it to be an external command https://pubs.opengroup.org/onlinepubs/009695399/utilities/test.html Very few are required by POSIX to be built-in https://pubs.opengroup.org/onlinepubs/009695399/idx/sbi.html Builtin `[` is for efficiency – Sergiy Kolodyazhnyy Jun 24 '19 at 03:08
  • 2
    Shellcheck knows `ksh`; use a hashbang or `-s ksh`. Btw, ksh and bash have `[[` "built in," but it's a *keyword*, unlike `[`, which is a *shell builtin*. (ksh: `whence -v [ [[`; bash: `type [ [[`) Builtins and external commands are syntactically alike. One way `[[` differs from `[` is that `[[` suppresses some expansions in its arguments, which it couldn't as a builtin--much as `{` couldn't do grouping were it a builtin. This may be why `{x` (or `[[x`) being one token feels odd. I think it's right to say builtins and keywords are *lexically* but not *syntactically* similar. @SergiyKolodyazhnyy – Eliah Kagan Jun 24 '19 at 05:36
  • @EliahKagan Nice to know shellcheck recognizes `ksh` as well. And it's a good point - we could say lexically `[[` and `{` are similar to keywords. However what always catches my eye is that they are "compound commands", so right there in the manual we already can see the would be treated as commands, hence why it makes sense that space would be necessary. Which is why as you say syntactically they're not similar to keyword - the shell has to see that as command that has to have the closing brace or double bracket. But yes, all good points. Thank you ! – Sergiy Kolodyazhnyy Jun 24 '19 at 06:58
  • @SergiyKolodyazhnyy To be clear, I don't mean to say `[[` and `{` are merely similar to keywords. `[[` and `{` really are keywords. (If you haven't, try `whence -v [[` in ksh and `type [[` in bash.) The *`[[` compound command* in ksh is a command of the form `[[ expression ]]`, but `[[` is itself a keyword, as is `{`. I hope I haven't inadvertently suggested `[[` and `{` are syntactically dissimilar to keywords, since that's literally what they are, as fully as `if` is a keyword. I'm saying *builtins* are syntactically dissimilar yet lexically similar to keywords. `[[` and `{` aren't builtins. – Eliah Kagan Jun 24 '19 at 07:21
  • @EliahKagan No, I understood that well :) – Sergiy Kolodyazhnyy Jun 24 '19 at 07:26
  • @SergiyKolodyazhnyy Ah, good. Btw, I'm unsure if `[[ expression ]]` being a compound command really makes the spaces make sense, because `(list)` usually doesn't need spaces. (I *think* any ksh command that's not a simple command is considered a compound command. ksh(1) says "A command is either a simple-command or one of the following" and shows forms including `(list)`. [Bash](https://www.gnu.org/software/bash/manual/bash.html#Command-Grouping) and [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) directly call it a compound command.) But maybe `(list)` is the less intuitive case. – Eliah Kagan Jun 24 '19 at 07:51
  • @EliahKagan I think `(list)` case falls under the case of meta-characters delineating words. There's a quote in my answer from ksh manual about that. Commands are first word, compound command is still a command - just has to be closed. [bash manual](https://www.gnu.org/software/bash/manual/html_node/Compound-Commands.html) pretty much says that:"Each construct begins with a reserved word or control operator and is terminated by a corresponding reserved word or operator." I mean, at least based on these quotes, to me space makes sense. Commands are words, but not all words are commands :) – Sergiy Kolodyazhnyy Jun 24 '19 at 08:29
  • 1
    @EliahKagan Actually, I've posted a question on U&L to get a precise answer on what POSIX thinks about this situation https://unix.stackexchange.com/questions/526574/posix-shell-grammar-why-brace-group-needs-first-space-but-subshell-doesnt Shell language is complex enough so hopefully someone can explain it in more formal terms – Sergiy Kolodyazhnyy Jun 24 '19 at 09:11
  • @SergiyKolodyazhnyy I do fully agree with you on why `(list)` is okay. Lexically it makes sense. `(` and `)` are metacharacters and delimit words. I also have no real problem with how `[[` and `{` are keywords and not control operators--and thus must precede a space, tab, newline, or other metacharacter, to have special meaning. I'm used to it. *That* this is the rule, though, strikes me as a historical accident, rather than having any particularly good rationale besides compatibility. I'm just not sure the status of `[[ expression ]]` as a compound *command* helps any of this make more sense. – Eliah Kagan Jun 24 '19 at 09:15
1

[[ can be an external command! That is, it may be a program and not some syntax supported by your shell directly. Supporting a space-less syntax would be possible for ksh but it would fail on systems with external [[ so for compatibility reasons it's better to keep the required space.

BusyBox is providing [[ as an external command, for example.

DarkDust
  • 111
  • 2
0

Since [[-e can participate in shell expansion (it can be used like

echo [[-e]*

in order to list all files starting with a letter between [ and e inclusively), it would be a complete mess if [ and ] were special characters not participating in the normal whitespace-governed word splitting.