3

if is followed by then in bash but I don't understand why then cannot be used in the same line like if [...] then it has to be used in the next line. Does that remove some ambiguity from the code? or bash is designed like that? what is the underlying reason for it?

I tried to write if and then in the same line but it gave the error below:

./test: line 6: syntax error near unexpected token \`fi'
./test: line 6: \`fi'

the code is:

#!/bin/bash
if [ $1 -gt 0 ] then
echo "$1 is positive" 
fi
Manik
  • 573
  • 1
  • 9
  • 28
  • 3
    BTW, this isn't a *bash* language decision -- it's part of the [POSIX shell command language](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html) specification, so other POSIX-compliant shells implement the same syntax. – Charles Duffy Sep 07 '19 at 14:35

4 Answers4

11

It has to be preceded by a separator of some description, not necessarily on the next line(a). In other words, to achieve what you want, you can simply use:

if [[ $1 -gt 0 ]] ; then
    echo "$1 is positive"
fi

As an aside, for one-liners like that, I tend to prefer:

[[ $1 -gt 0 ]] && echo "$1 is positive"

But that's simply because I prefer to see as much code on screen as possible. It's really just a style thing which you can freely ignore.


(a) The reason for this can be found in the Bash manpage (my emphasis):

RESERVED WORDS: Reserved words are words that have a special meaning to the shell. The following words are recognized as reserved when unquoted and either the first word of a simple command (see SHELL GRAMMAR below) or the third word of a case or for command:

! case coproc do done elif else esac fi for function if in select then until while { } time [[ ]]

Note that, though that section states it's the "first word of a simple command", the manpage seems to contradict itself in the referenced SHELL GRAMMAR section:

A simple command is a sequence of optional variable assignments followed by blank-separated words and redirections, and terminated by a control operator. The first word specifies the command to be executed, and is passed as argument zero.

So, whether you consider it part of the next command or a separator of some sort is arguable. What is not arguable is that it needs a separator of some sort (newline or semicolon, for example) before the then keyword.

The manpage doesn't go into why it was designed that way but it's probably to make the parsing of commands a little simpler.

Community
  • 1
  • 1
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • I didn't downvote, but the keyword `then` isn't *in* a simple command; it *separates* the command list forming the condition form the command list forming the body. If `then` appears on the same line, the semicolon is necessary to terminate the command list (in place of the newline that is no longer there). (Although I see where the man page is coming from, and it accurately describes how the parser performs the disambiguation, I'm not sure it's the clearest way to explain it.) – chepner Sep 07 '19 at 13:54
  • (For example, you can write `if someCommand; someOtherCommand; then ...`; `then`, if seen in command position, acts as sort of a "super terminator" distinct from an ordinary list terminator.) – chepner Sep 07 '19 at 13:58
  • @chepner, I think I see your point, and it appears the manpage is not fully consistent. The bit I quoted seems to clearly state that the `then` is part of the simple command but she `SHELL GRAMMAR` section it references implies not. Will update the answer accordingly. Thanks. – paxdiablo Sep 08 '19 at 01:12
7

Here's another way to explain the need for a line break or semicolon before then: the thing that goes between if and then is a command (or sequence of commands); if the then just came directly after the command without a delimiter, it'd be ambiguous whether it should be treated as a shell keyword or just an argument to the command.

For instance, this is a perfectly valid command:

echo This prints a phrase ending with then

...which prints "This prints a phrase ending with then". Now, consider this one:

if echo This prints a phrase ending with then

should that print "This prints a phrase ending with then" and look for a then keyword later on, or should it just print "This prints a phrase ending with" and treat the then as a keyword?

In order to settle this ambiguity, shell syntax says it should treat "then" as an argument to echo, and in order to get it treated as a keyword you need a command delimiter (line break or semicolon) to mark the end of the command.

Now, you might think that your if condition [ $1 -gt 0 ], already has a perfectly good delimiter, namely the ]. But in shell syntax, that's really just an argument to the [ command (yes, that's a command). Try this command:

[ 1 -gt 0 ] then

...and you'll probably get an error like "-bash: [: missing ']'", because the [ command checked its last argument to make sure it was "]", found that it was "then" instead, and panicked.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
2

Perhaps it helps to understand why this is so by way of a few examples. The argument to if is a sequence of commands; so you can say e.g.

if read -r -p "What is your name?" name
    [ "$name" -eq "tripleee" ]
then
    echo "I kneel before thee"
fi

or even a complex compound like

while read -r -p "Favorite number?" number
    case $number in
        42) true; break;;
        *) false;;
    esac
do
    echo "Review your preferences, then try again"
 done

This extremely powerful but potentially confusing feature of the shell is probably one of its most misunderstood constructs. The ability to pass a sequence of commands to the flow control statements can make for very elegant scripts, but is often missed entirely (see e.g. Why is testing "$?" to see if a command succeeded or not, an anti-pattern?)

tripleee
  • 175,061
  • 34
  • 275
  • 318
-2

If it helps, you can use semi-colons

if [ $1 -gt 0 ]; then
echo "$1 is positive" 
fi

# or even

if [ $1 -gt 0 ]; then echo "$1 is positive"; fi

As for why, it helps me to think of if, then, else, and fi as bash commands, and just like all other commands, they need to be at the start of a line (or after a semi-colon).

rampion
  • 87,131
  • 49
  • 199
  • 315
  • It does *not* help to think of them as commands, since you *cannot* write something like `if [ $1 -gt 0]; then; echo "$1 is positive"`. `if` et al. are *keywords* that together form a single compound command. – chepner Sep 07 '19 at 13:55
  • it doesn't help *you* – rampion Sep 07 '19 at 13:56
  • 1
    boo, hiss re: describing outright misapprehensions as "helpful", even when they can demonstrably lead to errors. A "command" is a specific thing with a formal definition in the POSIX sh syntax spec. – Charles Duffy Sep 07 '19 at 14:37