The third caveat concerns parsing `export` and `let` as keywords rather than as builtins. Like the README says, this is to properly build the syntax tree without leaving opaque strings as expressions, but also to support `declare foo=(bar)` which wouldn't work if `declare` was treated like any other builtin simple command.
How else would you have a static parser handle these two builtins? They are in a bit of an awkward middle ground between builtin and keyword. My instinct is that giving them special treatment in the parser to allow tokens like `(`, while at the same time representing them in the syntax tree with opaque strings as expressions, would be pretty underwhelming to any users of the parser.
That said, we already have that problem with `let "foo=123"` for example, where our parser currently represents the expression as the quoted string without going any deeper. https://github.com/mvdan/sh/issues/754#issuecomment-96329574... considers doing a second parse stage in the shell interpreter to fix cases like these, though always doing a second parse could get expensive.
We _could_ leave all arithmetic expressions as input strings in the parser, and do all the actual parsing when they are evaluated. That would be more compatible with Bash and more consistent. But it would also be less useful to any parser users who don't run into any of these weird edge cases, which aren't common at all, I think.
In short, I have some ideas, but I'm not sure at all what's best :) Doing a good job for 99% of users feels better than aiming for 100% compatibility with bash syntax, particularly where bash syntax is a bit weird.
Also for static typing an analysis I would absolutely give up even more syntax that is ambiguously parsed.
https://github.com/koalaman/shellcheck
Crucially it shows where on the line the error is in case I've got some large piped one-liner which might have a problem.
shfmt foo.sh || true
worst case it just doesn't format it?This means anywhere golang is installed, including aarch64 Darwin and Windows you can:
go run mvdan.cc/sh/v3/cmd/gosh@latest
Or things like go run mvdan.cc/sh/v3/cmd/gosh@latest -c 'echo "cross platform shell"; go run github.com/mikefarah/yq/v3@latest r metadata.name <(kubectl get pod my-pod -o yaml)'
Pretty awesome stuff, I'm always discovering new ways to use it.Kudos to Daniel for building such a wonderful package.
After `devenv init`, update `devenv.nix` as follows:
{ pkgs, ... }:
{
pre-commit.hooks = {
shellcheck.enable = true;
shfmt.enable = true;
};
}The only thing that disturbed me personally is no space before semicolon. Without having read what spec says I think the semicolon in shell is more of a command separator than a terminator. So I had always formatted it symmetrically, with space before and after like e.g. && or a pipe. shfmt did not support that so I had to adapt. Still having a tool and skipping review discussions outweighs this minor matter of taste.
for i in $(seq 100)
do if expr $i % 5 > /dev/null
then if expr $i % 3 > /dev/null
then echo $i
else echo fizz
fi
else if expr $i % 3 > /dev/null
then echo buzz
else echo fizzbuzz
fi
fi
doneSupports bash, posix, mksh, bats.