2

I've tried web search and search on that site but found only problems with usage of whitespace for strings in bash. However I'd like to understand general logic behind usage of whitespace in bash scripts so that I can remember correct usage better: where it is mandatory, where the opposite.

Couple of examples: mandatory [ $i == b ], the opposite var1="foo"

3
  • var1="foo" is variable assignment. Spaces are never used in variable assignments. [ var1="foo" ] is very different. That should just evaluate to true all the time. [ var1 = "foo" ] is an actual test. As for spaces in general, any thing that is a separate argument to a command should have spaces. Oct 14 '19 at 15:50
  • @ Sergiy Kolodyazhnyy, is [ ] a command? Oct 14 '19 at 15:57
  • 3
    Yes, [ is built in command, also known as test. So you can do things like test var1 = "foo" Originally it was separate command in /usr/bin/ but now most shells have it as built in. See serverfault.com/q/52034/363611 Oct 14 '19 at 16:00
4

However I'd like to understand general logic behind usage of whitespace in bash scripts

Whitespace is mandatory in separating arguments to commands, and in separating keywords (or reserved words, depending on the source). These are {, }, !, if, for, do, done, etc.

Whitespace is not mandatory around operators. These are at least the statement terminators ; and &; the conditional operators && and ||; all redirection operators >, << etc.; and the parenthesis ( and ). The optional file descriptor number in front a redirection operator must not be separated by whitespace, but otherwise extra whitespace around operators doesn't usually matter.

So, these are fine:

true;true
cat<<file
true&&false
tac|rev
f()(echo foo)

These aren't:

if !somecmd; then ...
f(){echo foo}

The first would look for a command !somecmd, instead of running somecmd and inverting the exit status. The second doesn't work because the keywords '{' and } must be separate words, and they're only recognized at the start of a command. It could be e.g. f(){ echo foo;}.

And this, if course doesn't work as an if statement, because if the if was picked from the start of a word, a program called ifconfig would be hard to use:

ifsomecmd;then ...

(Though that one is probably obvious, unless you're used to Fortran.)

So, how do you know that ( is an operator and { isn't? You learn it by rote. The difference might be due to historical reasons, as so many things are. See: Why does a brace command group need spaces after the opening brace in POSIX Shell Grammar?

The answers to that question have more discussion about operators and keywords, with references, so go read them.


None of the above really helps you with the difference between an assignment foo=bar and the test [ "$foo" = bar ], though.

The key here is that [ is like a regular command, it takes its operators and operands as distinct arguments and doesn't really go looking inside them. In the same way that ls -l /somedirectory needs whitespace to separate its arguments (-l and /somedirectory), so does [. The pair of brackets isn't part of the shell syntax (or grammar), and neither is the =, unlike the equivalent construct in most real programming languages.

As for the assignment, they're a bit tricky. Assignments don't need to be alone on a command line (they can be used with a command), and there can be more than one assignment on a line. Like so:

$ foobar=123 foofoo=456 env|grep foo
foofoo=456
foobar=123

The way it works (roughly), is that the shell splits the command to words (above, those would be foobar=123, foofoo=456 and env), and looks at the initial ones to see if they look like assignments. Those two do, but here, there's no assignment, just an ordinary argument to echo:

$ echo foo=bar
foo=bar

I suppose the shell grammar could be different with regard to this, but supporting things like this as having two assignments and a command with no arguments might seem weird (well, unless you're used to Lua):

foobar = 123 foofoo = 456 env
1
2

The key thing is that whitespace separates words, unless quoted, and words are what you have to think about:

Basically, the shell does the following:

  1. Reads its input [...]
  2. Breaks the input into words and operators, obeying the quoting rules described in Quoting. These tokens are separated by metacharacters. [...]
  3. Parses the tokens into simple and compound commands (see Shell Commands).

- 3.1.1 Shell Operation

And:

A simple command is the kind of command encountered most often. It’s just a sequence of words separated by blanks, terminated by one of the shell’s control operators[.] - 3.2.1 Simple Commands

And:

  1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.
  2. The words that are not variable assignments or redirections are expanded [...]. If any words remain after expansion, the first word is taken to be the name of the command and the remaining words are the arguments.

- 3.7.1 Simple Command Expansion

Note how it says "words [...] marked as variable assignments." So variable assignments have to be a single word, and so the following are just variable assignments:

  • var=value
  • var=" value"

But the following aren't:

  • var= value ✗ (two words: variable assignment var= - assigning the empty string - for command value)
  • var =value ✗ (two words: command var with argument =value)
  • var" =value" ✗ (one word: but variable names can't be quoted, so this is the command named var =value)
  • "var=value" ✗ (one word: but variable names can't be quoted, so this is the command named var=value)

Now for [ var = value ], [ is a command (the same command as test), that expects tests and operands to the tests as separate arguments. This way, you can do things like [ "$var" = "$value" ] or test "$var" = "$value", where, e.g., var="a = b" and value="b = c" can contain things that look like tests, but aren't, because each is a single argument.

Because arguments have to be separate words, whitespace is needed around =. And that's also why "$var" and "$value" had to be quoted, otherwise the shell would split them into separate words, and [ would fail.

3
  • but all this does not even define what kind of simple command this is, and if this very common termination needs WS: ls >out. Of course ls>out is not possible. Or is it?
    – user373503
    Oct 15 '19 at 17:43
  • @rastafile just tried and it is working fine: creating file with output of ls Oct 16 '19 at 11:47
  • @AlexeiMartianov you do not have to tell rastafile :) So you get what I mean?
    – user373503
    Oct 16 '19 at 11:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.