32

I don't necessarily want the answer but if someone could point me to some literature or examples. I would like to figure it out.

When I run the script I receive an error:

Syntax error near unexpected token fi

I have deduced that my problem is in my if statement by making my if statements comments and adding echo "$NAME" which displays the names in the /etc/.

When I make changes, remove the # from if and fi and add # to wc -c "$NAME", I receive the syntax error I listed above. I have added ; between ] then. I have also moved then to the next line with no resolution.

#!/bin/bash
for NAME in /etc/*
do

     if [ -r "$NAME" -af "$NAME" ] then
          wc -c "$NAME"
     fi
done
8
  • 16
    Whenever you have a shell syntax error, a good first step is to cut and paste your code into shellcheck.net and correct the errors that it identifies. If you have trouble understanding its messages, then come here and ask.
    – John1024
    Commented Nov 30, 2017 at 21:27
  • 2
    What's that -af supposed to do?
    – ilkkachu
    Commented Nov 30, 2017 at 21:55
  • Thank You for that site @John1024 it was most useful, I have bookmarked it for future reference. Commented Nov 30, 2017 at 22:02
  • 1
    @ChristinaAGuzman In such a case -a is redundant because the condition is already included within -f. --- Anyway multiple conditions within [ ] (this command is also available as test) have to be joined using logical operators like -a (and) or -o (or) but as it was suggested in the reply below it is better to use multiple [ ] (test) commands and join them using shell operators like && or ||. Commented Dec 1, 2017 at 10:32
  • 2
    Christina, as pointed out @John1024 , shellcheck.net shows very clearly some syntax or even deeper mistakes (as lint does for C code). I also recommend a must read: mywiki.wooledge.org/BashPitfalls (read it at least once!) and mywiki.wooledge.org/BashFAQ and mywiki.wooledge.org/BashGuide are good to read as well. Commented Dec 1, 2017 at 17:45

3 Answers 3

55

Keywords like if, then, else, fi, for, case and so on need to be in a place where the shell expects a command name. Otherwise they are treated as ordinary words. For example,

echo if

just prints if, it doesn't start a conditional instruction.

Thus, in the line

if [ -r "$NAME" -af "$NAME" ] then

the word then is an argument of the command [ (which it would complain about if it ever got to run). The shell keeps looking for the then, and finds a fi in command position. Since there's an if that's still looking for its then, the fi is unexpected, there's a syntax error.

You need to put a command terminator before then so that it's recognized as a keyword. The most common command terminator is a line break, but before then, it's common to use a semicolon (which has exactly the same meaning as a line break).

if [ -r "$NAME" -af "$NAME" ]; then

or

if [ -r "$NAME" -af "$NAME" ]
then

Once you fix that you'll get another error from the command [ because it doesn't understand -af. You presumably meant

if [ -r "$NAME" -a -f "$NAME" ]; then

Although the test commands look like options, you can't bundle them like this. They're operators of the [ command and they need to each be a separate word (as do [ and ]).

By the way, although [ -r "$NAME" -a -f "$NAME" ] works, I recommend writing either

[ -r "$NAME" ] && [ -f "$NAME" ]

or

[[ -r $NAME && -f $NAME ]]

It's best to keep [ … ] conditionals simple because the [ command can't distinguish operators from operand easily. If $NAME looks like an operator and appears in a position where the operator is valid, it could be parsed as an operator. This won't happen in the simple cases seen in this answer, but more complex cases can be risky. Writing this with separate calls to [ and using the shell's logical operators avoids this problem.

The second syntax uses the [[ … ]] conditional construct which exists in bash (and ksh and zsh, but not plain sh). This construct is special syntax, whereas [ is parsed like any other command, thus you can use things like && inside and you don't need to quote variables except in arguments to some string operators (=, ==, !=, =~) (see When is double-quoting necessary? for details).

1
  • Note that if [[ 1 ]] then [[ 2 ]] fi works in ksh, pdksh and zsh. if(:)then(:)fi works in all shells. Commented Dec 2, 2017 at 11:45
13

See what's changed as follows

if [ -r "$NAME" -a -f "$NAME" ]; then
#               ^^^^^          ^
     wc -c "$NAME"
fi

If you want to remove all commands in if block, you at least need to add a colon in it, like

if [ -r "$NAME" -a -f "$NAME" ]; then
    :
fi

or one line version

if [ -r "$NAME" -a -f "$NAME" ]; then :; fi

2

Others already pointed out, but if you're looking for official reference then RTM

if list; then list; [ elif list; then list; ] ... [ else list;] fi

The if list is executed. If its exit status is zero, the then list is executed. Otherwise, each elif list is executed in turn, and if its exit status is zero, the corresponding thenlist is executed and the command completes. Otherwise, the else list is executed, if present. The exit status is the exit status of the last command executed, or zero if no condition tested true.

You are missing the ;

And the syntax for list is described in man test

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .