31

1. Summary

I don't understand, why do I need E010 bashate rule.


2. Details

I use bashate for linting .sh files. E010 rule:

do not on the same line as for

for bashate:

  • Correct:

    #!/bin/bash
    for f in bash/*.sh; do
        sashacommand "$f"
    done
    
  • Error:

    #!/bin/bash
    for f in bash/*.sh
        do sashacommand "$f"
    done
    

Is any valid arguments, why I need for and do in the same line?


3. Not useful

I can't find an answer to my question in:

  1. Google
  2. Articles about best coding practices (example)
  3. bashate documentation. I find only:

    A set of rules that help keep things consistent in control blocks. These are ignored on long lines that have a continuation, because unrolling that is kind of “interesting”

8
  • 3
    The indented do is definitely a bit unusual, but for ... \ndo\n<indent>body... is extremely common and I'd even estimate more so than for ... ; do. Does it also complain about that? Commented May 13, 2018 at 7:40
  • 20
    bashate is a style checker. Styles are inherently subjective. Don't use it if you don't like the artibrary style it imposes. You should instead consider a syntax/bug checker like shellcheck.
    – meuh
    Commented May 13, 2018 at 7:41
  • 13
    There is never a syntactic obligation to have for and do on the same line in the shell.
    – meuh
    Commented May 13, 2018 at 7:52
  • 1
    @Саша Черных It's not the indentation by itself that's unusual, it's the indenting of do. It's like indenting the opening brace of an if and adding code on that same line in a language like C. Another example, if python allowed it, would be putting the : of an if indented on the next line and adding code on that same line. It's going to make it differ with additional lines in the body.
    – JoL
    Commented May 14, 2018 at 1:26
  • 1
    You don't need to. Linting is inherently opinionated. Most linter let you configure them to your liking.
    – Polygnome
    Commented May 14, 2018 at 9:03

5 Answers 5

62

Syntactically, the following two code snippets are correct and equivalent:

for f in bash/*.sh; do
    sashacommand "$f"
done
for f in bash/*.sh
    do sashacommand "$f"
done

The latter one could possibly be said to be harder to read as do slightly obfuscates the command in the body of the loop. If the loop contains multiple commands and the next command is on a new line, the obfuscation would be further highlighted:

for f in *
    do cmd1
    cmd2
done

... but to flag it as an "error" is IMHO someone's personal opinion rather than an objective truth.

I would say that if you want to prepend the command in the loop with do, then feel free to do so if that makes the code consistent and readable in the eyes of whoever is reading the code.

In general, almost any ; may be replaced by a newline. Both ; and newline are command terminators. do is a keyword that means "here follows what needs to be done (in this for loop)".

for f in *; do ...; done

is the same as

for f in *
do
   ...
done

and as

for f in *; do
   ...
done

and

for f in *
   do ...
done

The reason to use one over another is readability and local/personal style conventions.


Personal opinion:

In loop headers that are very long, I think that it may make sense to put do on a new line, as in

for i in animals people houses thoughts basketballs bees
do
    ...
done

or

for i in        \
    animals     \
    people      \
    houses      \
    thoughts    \
    basketballs \
    bees
do
    ...
done

The same goes for the then in an if statement.

But again, this comes down to one's personal style preferences, or to whatever coding style one's team/project is using.

10
  • You say that “In loop headers that are very long, it may make sense to put do on a new line”. Could you provide a reason for that? Given that the header must be followed by do, I don’t see a reason why putting it on the next line would help readability. Commented May 13, 2018 at 18:06
  • 3
    @KonradRudolph My terminal is 80 characters wide. If the list wraps, I will make it a set of continuated lines (as in my last example), if it almost wraps, I will put the do (or then) on a line on its own (as in the second to last example). This all has to de with personal preferences. I don't like overly long lines, partly because my terminal is "only" 80 characters wide, and partly because I think it looks untidy. I also find it hard to parse very long lines for errors.
    – Kusalananda
    Commented May 13, 2018 at 18:12
  • 1
    It seems unnecessary to keep pointing out that this is opinion. bashate is a style guide, so it's inherently based on opinion.
    – Barmar
    Commented May 14, 2018 at 6:05
  • 4
    @Barmar, do note that the question here uses phrasing like "need to" and "rule", and the bashate readme calls having the do on a separate line an "error". Those are rather strict phrases, so it does seem worth it to point out that, quite on the contrary, the so-called "rule" is actually just a subjective opinion.
    – ilkkachu
    Commented May 14, 2018 at 9:11
  • 2
    @mtraceur Right, I'm not questioning personal preferences. But be aware that the readability column limit you quote doesn't apply to code. It only applies to prose. Eye movement is fundamentally different for code reading, so the evidence from those studies is not applicable here. And as for using historical reasons that no longer apply as rationale, well, we've also used past 8.3 filenames. Commented May 14, 2018 at 16:16
29

Note what it says on the top of the page:

bashate: A pep8 equivalent for bash scripts

PEP8 is the Style Guide for Python Code. While Python folks often seem to take their style issues very seriously[citation needed], it's just that, a guide. We could come up with upsides for both putting the do in the same line as the for, and for putting it on a line of its own.

It's the same discussion as where to put the { in C code when starting an if or while block (or such).


A few lines below in the documentation, there's another interesting rule:

E020: Function declaration not in format ^function name {$

I assume the point here is that the author of that style guide thinks that using the function keyword is necessary for the reader to recognise a function declaration. However, function foo is a non-standard way of defining a function: the standard way is foo(). The only difference between the two (in Bash) is that the other is harder to port to a standard shell, like /bin/sh in Debian or Ubuntu.

So, I would suggest taking any and all such style guides with a grain of salt or three, and deciding for yourself what the best style is. A good style-checker allows disabling some checks (bashate has bashate -i E010,E011 to ignore those two checks.) An excellent style-checker would allow you to modify the tests, e.g. to check for the opposite of E020, here.

15

TL;DR: You don't need to. It's just some dude's opinion.

Like many others, I've been writing for loops like this for decades:

for F in arg1 arg2 arg*
do
    somecommand "$F"
done

This layout highlights the fact that do ... done surround the loop body, like curly braces in C-like languages, and allows newlines to separate the loop construct's components.

Of course there are religious wars about where to place the curly braces too, so you might say the jury is still out on this one. IMHO, this is clearly how this was designed to work; Bourne was fond of matched delimiters, cf. if ... fi, case ... esac, and the need for a semicolon in the shorter version reveals that you're making it shorter than originally designed. Nothing wrong with that; technology advances and a lot of things that once seemed like a good idea are now frowned upon, like goto. But until the entire shell-scripting community adopts the preferences of the bashate author(s), you don't have to do a thing.

5
  • 5
    The fi that corresponds to if, and esac etc. comes from Algol 68. The only reason a for loop 's do is not ended by od is that od is the name of an existing standard utility. See en.wikipedia.org/wiki/ALGOL_68C#Algol_68C_and_Unix
    – Kusalananda
    Commented May 13, 2018 at 14:53
  • 1
    Right, and keyword-delimited blocks were also used in other languages of the Algol family, including Pascal (which uses begin ... end etc.).
    – alexis
    Commented May 13, 2018 at 19:04
  • 2
    Hadn't heard the story about od, that's funny... (Although, why not just end for-loops with rof, then?)
    – alexis
    Commented May 13, 2018 at 19:05
  • 2
    @alexis Well, like Kusalananda said, it comes from Algol 68, that's the key point. Stephen Bourne, the creator of the original Bourne shell, was heavily influenced by that language, and that's why he stuck to that syntax. Even when he was writing in C. See the source code for bourne shell: minnie.tuhs.org//cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/mac.h Oh, and by the way, BEGIN and END are defined there as well. Commented May 13, 2018 at 19:28
  • 1
    This whole formatting style also comes from Algol 68, by the way. Its FOR and DO would also be on separate lines, by convention, except when the whole loop would easily fit on a single line. Commented May 15, 2018 at 11:03
4

Several good answers here. Always take style guides with a grain of salt, and IMHO and experience, be ever-so-slightly-to-moderately concerned about any community that is rife with excessive dogma when it comes to style. It's almost like they believe that when they FINALLY get that last I dotted, and that last T crossed, then the software will work. Sometimes it takes more than perfect style to remove bugs...

When I started learning shell, most scripts and tutes out there used the in-line semicolon format

for i in "$@"; do
    stuff
done

but because I was inexperienced, I had a bit of a hard time spotting the ; and do - both essential for confirming syntactical correctness. So I preferred the do on the next line all by itself. I no longer do that (pun intended)

Further, the 'while' command is an excellent candidate for a nice little trick:

while prep1; prep2; condition; do
    stuff
done

but when the prep commands are a bit bulky, and/or the condition is complex, it is better formatted as

while
    prep1
    prep2
    condition
do
    stuff
done

and then appending the do as "; do" is positively fugly.

You can even get more intense than that:

while
    if con1; then
      cond2
    elif cond3; then
      cond4
    elif cond5; then
      cond6
    else
      cond7
    fi
do
    stuff
done

because every statement is an expression. Notice how both styles are used opportunistically. Keep it clean and it's quite readable. Compact or contort it in the name of style or "saving space" and it becomes a horrible mess.

So! Given the rather baroque stylings of shell scripts in general, anything aimed at increasing clarity and easy visual access to significant symbols is always a good thing.

The form where 'do' is written inline with the command is sweet when you have a little command - I don't find the 'do' visually hidden, nor is the inner command really obscured:

for i in whatever
  do stuff
done

I find that quite pleasant to look at, and it has analogues with while, if and case:

while condition
  do stuff
end

if condition
  then stuff1
  else stuff2
fi

case $blah in
  a) stuff1;;
  b) stuff2;;
  c) stuff3;;
  *) stuffInfinity;;
esac

Clarity and general tidiness is all that Codd* asks of you.

*Edgar F. Codd, father of relational database theory.

1
  • 1
    Never saw a while with an if condition before. Cool!
    – Joe
    Commented May 23, 2018 at 8:07
3

I am surprised no one has mentioned this valid and portable syntax yet:

set alfa bravo charlie
for q do
  echo "$q"
done

Notice carefully that for and do are on same line, without semicolon. Result:

alfa
bravo
charlie
2
  • I approve of this answer for plugging a mention of this obscure (but critical for clean and portable shell code in some cases) syntax, however it's worth explicitly explaining to people that this clobbers the positional parameters, and also, I feel compelled to mention that the only "truly portable (tm)" form is the one where for q and do are on separate lines (the form in this example was unsupported a couple decades ago on the original Almquish shell (ash): in-ulm.de/~mascheck/various/bourne_args/#endnote )
    – mtraceur
    Commented May 14, 2018 at 16:19
  • While the example in this answer works for me, applying it to the example in OP's question does not work. Commented May 14, 2018 at 16:22

You must log in to answer this question.

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