Subcommand statement grouping? - bash

Today I tried this and was somewhat surprised to find that it didn't work:
$ nice -n 10 { ./configure && make ; }
-bash: syntax error near unexpected token `}'
Is there a way to use grouping in a "subcommand"?

Have you tried this ? =)
nice -n10 bash -c './configure && make'

You can't just pass shell syntax to the argv of a program and expect it to understand it. Specifically, the error you're seeing is because of the && and ;, which are "list operators" which separate commands. Bash is trying to evaluate the arguments to nice as:
nice '-n' '10' '{' './configure'
Bash then tries to evaluate the next command after the && (make), then the next command, which is }. Technically, braces are both "reserved words" and "control operators". Different shells treat bare braces a bit differently but that's an esoteric detail. The point is depending on the shell that will either be a parsing error (like here), or an error due to not being able to find a command named "}" (usually the former.
Exceptions to this rule exist only within the shell itself. For instance, the Bash coproc keyword works like this, enabling special parsing and evaluation of its arguments almost exactly as in your example.

Related

Does bash think this argument is a command? [duplicate]

I am writing a shell script where I am setting few variables, whose value is the output of commands.
The errors I get are:
$ $tag_name="proddeploy-$(date +"%Y%m%d_%H%M")"
-bash: =proddeploy-20141003_0500: command not found
now, I did read other similar questions and based on it, I tried various things:
spliting command into two calls
$ $deploy_date=date +"%Y%m%d_%H%M"
bash: =date: command not found
$ $tag_name="proddeploy-$deploy_date"
bash: proddeploy- command not found
tried using backticks
$ $tag_name=`proddeploy-$(date +"%Y%m%d_%H%M")`
bash: proddeploy-20141003_1734: command not found
bash: =: command not found
tried using $()
$ $tag_name=$(proddeploy-$(date +"%Y%m%d_%H%M"))
bash: proddeploy-20141003_1735: command not found
bash: =: command not found
But in every case the command output is getting executed. how do I make it to stop executing command output and just store as a variable? I need this to work on ZSH and BASH.
You define variables with var=string or var=$(command).
So you have to remove the leading $ and any other signs around =:
tag_name="proddeploy-$(date +"%Y%m%d_%H%M")"
deploy_date=$(date +"%Y%m%d_%H%M")
^^ ^
From Command substitution:
The second form `COMMAND` is more or less obsolete for Bash, since it
has some trouble with nesting ("inner" backticks need to be escaped)
and escaping characters. Use $(COMMAND), it's also POSIX!
Also, $() allows you to nest, which may be handy.
The accepted answer shows corrected code, but does not clarify that one of your problems is accessing a variable (using $) while assigning it, which is illegal.
For example:
$foo=4
should be
foo=4
See the difference? foo is being assigned, so you should not use $foo, which is not foo but the value of foo.

unix shell script unexpected error

when I run the following script
#!/bin/sh
[ `whoami` == root ] || echo "must be run as root"
I get the following error
./test.sh: 2: [: root: unexpected operator
How can I avoid that error?
While it might seem like the problem is not quoting the word root, your script does run without error on my machine, even without quoting it. So it seems your error depends on the shell implementation.
The problem is that sh is implemented by different shells in different environments. The posix sh command doesn't support == (only =), and I think that's the error you're experiencing. See e.g. this answer.
Try changing the first line to #!/bin/bash to see if this is the case on your machine.

How to syntax check portable POSIX shell scripts? [duplicate]

This question already has answers here:
How do I syntax check a Bash script without running it?
(10 answers)
Closed 6 years ago.
The following shell script executes well when provided /bin/true for the first argument, but may otherwise fail with a syntax error during execution!
#!/bin/sh
if $1 ; then exit; fi
/tmp/asdf <<< ASDF # Something with syntax error in POSIX
Surely some syntax errors (if not all?) can be avoided by static checking? How do I statically check whether a given Shell Command Language script is syntactically valid?
EDIT: Checking for syntax errors in Bash scripts has been answered in this question.
EDIT #2: Note that Bash fails to properly check whether the syntax adheres to POSIX even when executed with the +B and --posix flags in addition to -n.
All POSIX-compatible Shell Command Language shells support the set -n built-in which can be used to check the syntax of the script. Therefore it is possible to prepend
set -n
to your code to syntax check it. Note also that standard sh utility is also required to support a command-line -n flag, which has equivalent semantics to using set -n. Bash and possibly other shells also support this command-line flag. Therefore you can simply run the following to syntax check your script:
sh -n yourScriptFilename.sh
WARNING: This does not give you a guarantee that the script has fully POSIX compatible syntax. For example, Bash allows bashisms (e.g. arrays and c{a,u}t) to go unnoticed even when using the --posix (and/or +B) command line option in addition to -n when invoked as sh. Other shells might have similar issues.
With bash you can use -n:
bash -n file.sh
Output:
a.sh: line 3: syntax error near unexpected token `then'
a.sh: line 3: `if then fi # Something with syntax error'
Since bash supports the --posix options you may run
bash --posix -n file.sh
to perform a posix compatible check. I don't know how posixly correct that mode is in detail.

How to exit when Bash have an expression error, like [: -ne: unary operator expected

My experiences are with more recent (decent) languages. I'm used to have try/catch at my disposal.
I'm obligated by my client to use Bash.
I have a script which will do something very risky, but the first part of the script is about validating that everything is as expected before we run the risky procedure. During that validation phase, I want that if anything is wrong, it exit right away.
So I use:
set -e -u -o pipefail
set -o errtrace
But, the script still continue after it generate error like:
[: -ne: unary operator expected
It continue executing the script, and start the risky procedure even if the validation failed!
How can I detect error of that kind in Bash while executing?
First you need to know that [ ... ] is a command not a syntax element. It is a variant of the test command. [ is the command and ... ] are arguments passed to it. ] is supposed to be the last argument.
The problem is that the arguments passed to [ are parsed by [ and not by bash and the if syntax element will not throw an error on non zero return values of the executed test by it's nature.
The workaround would be to use [[ (extended test). Bash will check the syntax of arguments passed to [[
Further read: http://tldp.org/LDP/abs/html/testconstructs.html

Why doesn't this Bash script error out?

Here's my Bash script:
#!/bin/bash -e
if [ == "" ]; then
echo "BAD"
exit 1
fi
echo "OK"
And here's the output:
./test.sh: line 3: [: ==: unary operator expected
OK
The return code is 0.
There's an obvious syntax error at line 3. Rather than raise the syntax error and refuse to run the script, somehow the script just runs and reports the syntax error at run time. The -e flag hasn't protected me from this - apparently a syntax error in an if statement constitutes a false condition rather than a reason to immediately exit the program. BUT, somehow Bash has parsed that whole if ... fi block, so after ignoring the bad line, execution somehow resumes not at the next syntactically correct line but after the end of the block?
I have two questions:
What is going on?
How can I protect myself from this behaviour in future?
if runs the command [, and just examines its return code. Bash doesn't know nor care about the syntax for the [ command.
You can put some other command there, and Bash still won't know anything about its particular syntax.
Two things come to mind:
Using [[ instead of [: Bash does know and care about its syntax.
Using ShellCheck1; online, manually or within your favourite editor.
Both if and -e deal with exit codes: If it's non-zero if won't let you into the then block, and -e will exit. You can't really have both those behaviours at once. (Well, it seems [ exits with different codes for false results (1) and syntax errors (2), so it might be possible to ‘detect’ syntax errors.)
1Or some other tool, but that's the only one of which I know. Suggestions welcome.
You don't have a shell syntax error here.
You have an error in the arguments to the [ command/builtin.
The reason set -e doesn't help here is because that's explicitly not what it is supposed to do. set -e would become entirely useless if you couldn't have if statements in your code with it on. Just think about that.
If you look in the POSIX spec for what the -e/errexit flag does you see this description:
-e
When this option is on, when any command fails (for any of the reasons listed in Consequences of Shell Errors or by returning an exit status greater than zero), the shell immediately shall exit with the following exceptions:
The failure of any individual command in a multi-command pipeline shall not cause the shell to exit. Only the failure of the pipeline itself shall be considered.
The -e setting shall be ignored when executing the compound list following the while, until, if, or elif reserved word, a pipeline beginning with the ! reserved word, or any command of an AND-OR list other than the last.
If the exit status of a compound command other than a subshell command was the result of a failure while -e was being ignored, then -e shall not apply to this command.
This requirement applies to the shell environment and each subshell environment separately. For example, in:
set -e; (false; echo one) | cat; echo two
See point two there? That's your situation.
The reason the shell continues to execute is back to the "not a shell syntax error". You have a command error. The [ command/builtin attempted to parse its arguments and failed. It then returned an error return code. The if caught that, skipped its body and returned true (as per documented behavior of if when no conditions return true). So the shell script continued normally.
As I indicated in my comment, however, if you had used [[ (which is a bash-ism and a language construct) then your script would have had a syntax error and would have exited immediately on that line (at least in my tests).
From the bash man page
-e
Exit immediately if a pipeline (which may consist of a single simple command), a subshell command enclosed in parentheses, or one of the commands executed as part of a command list enclosed by braces (see SHELL GRAMMAR above) exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test following the if or elif reserved words, part of any command executed in a && or ││ list except the command following the final && or ││, any command in a pipeline but the last, or if the command's return value is being inverted with !. A trap on ERR, if set, is executed before the shell exits. This option applies to the shell environment and each subshell environment separately (see COMMAND EXECUTION ENVIRONMENT above), and may cause subshells to exit before executing all the commands in the subshell.
Emphasis mine.
First, that isn't a syntax error. You are simply providing bad arguments to the [ command.
Second, the exit status of a command in the list following the if keyword are ignored for the purpose of the -e option.

Resources