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.
Related
I want to run this script:
#!/bin/bash
echo <(true)
I run it as:
sh file.sh
And I get "Syntax error: "(" unexpected" . I found some similar situations but still can't solve this.
I'm a beginner at shell scripting , but as I understand:
the shebang I use is correct and chooses the bash shell , so the process substitution syntax should work
I try the same from the command line and it works. I checked with echo $0 and it gives me "bash" , so what's the difference from running the command in the command line and from a script that invokes the same shell?
Maybe it's something simple, but I couldn't find an explanation or solution.
You should run your script with bash, i.e. either bash ./script.sh or making use of the shebang by ./script.sh after setting it to executable. Only running it with sh ./script.sh do I get your error, as commented by Cyrus.
See also: role of shebang at unix.SE
Remove export POSIXLY_CORRECT=1 from your ~/.bashrc or ~/.profile (etc.) files.
The issue is that process substitution is an added bash feature that is not part of the posix standards.
sh file.sh
errorsh: 3: Syntax error: "(" unexpected
solution:
bash file.sh
I wrote a bash script which calls another script in the same folder. I am doing this by simply putting ./email_pacotes.sh in the main script
#awk '{print $2}' >> /tmp/lista_pacotes.log adiciona resultado ao arquivo /tmp/tmp_pacotes_adicionados.log
echo "\nPacotes adicionados até" $(date) "\n" >> /tmp/tmp_pacotes_adicionados.log
cat /tmp/diferencas.log >> /tmp/tmp_pacotes_adicionados.log
./email_pacotes.sh
#adiciona resultados anteriores
cat /tmp/pacotes_adicionados.log >> /tmp/tmp_pacotes_adicionados.log
I thought that it was working correctly, but I had to debug the script for other reasons and I found out it wasn't adding the second script the first time a run the main script.
I was getting the following message:
[...]
./email_pacotes.sh: 17: ./email_pacotes.sh: [[: not found
./email_pacotes.sh: 17: ./email_pacotes.sh: [[: not found
./email_pacotes.sh: 17: ./email_pacotes.sh: [[: not found
./email_pacotes.sh: 17: ./email_pacotes.sh: [[: not found
./email_pacotes.sh: 17: ./email_pacotes.sh: [[: not found
[...]
This happens when I run the script the first time I put it in a folder. If I run it again, the message doesn't show anymore, so I guessing it is not a problem with the syntax. I also thought could be something with permissions, but I changed both scripts to 0777 and the message persists.
Is this a normal behaviour? What could be causing this?
Obs1: I am debugging the main script using the -x option.
Obs2: I made another test now. It keeps throwing the same message, but at certain point it finally calls the script. So maybe is just the time to find the file or throw a exception?
From the error, I'm pretty sure you're running the second script with a shell other than bash. The [[ ]] conditional is supported by bash (and zsh and maybe some other shells), but is not standard and there are other shells that don't support it. So if you want to use that (or any other nonstandard bash features), you need to use a proper shebang line in that script. Generally, that means you need to start the script with #!/bin/bash (or maybe #!/usr/bin/env bash), not with #!/bin/sh.
There's another thing that worries me, though. Running the second script with ./email_pacotes.sh will look for it in the current working directory, which is inherited from the process that ran the first script, and could be pretty much anywhere. If you want it to look for the second script in the same directory the first script is in, the best way is to locate the first script with something like "$(dirname "$BASH_SOURCE")" (and guess what -- BASH_SOURCE is a bash-only feature, so start the first script with a bash shebang as well). Then you can either refer to the second script (and any other relevant files) by explicit path:
#!/bin/bash
...
scriptDir="$(dirname "$BASH_SOURCE")"
if [[ ! -d "$scriptDir" ]]; then
echo "Something's terribly wrong; I can't find myself!" >&2
exit 1
fi
...
"$scriptDir/email_pacotes.sh"
or have the script cd to its own directory and then use relative paths:
#!/bin/bash
...
cd "$(dirname "$BASH_SOURCE")" || {
echo "Something's terribly wrong; I can't cd to my own directory!" >&2
exit 1
}
...
./email_pacotes.sh
I prefer the first approach, because if the any of the scripts accepts paths (e.g. as arguments), the user will expect those paths to be interpreted relative to where the user was when they ran the script, not relative to where the script itself is; cding in the script will break this.
Same problem as this OP, but must be a seperate cause.
The following script:
#!/bin/sh
arr=("cat" "dog" "bird")
Works interactively (debian) but fails when called by crontab with:
/bin/sh: 2: /path/zero_check.sh: Syntax error: "(" unexpected
I've tried with #!/bin/bash shebang, and declaring array with declare -a arr=("cat" "dog" "bird"), to no effect.
Any idea why?
The problem here is that you are using this shebang:
#!/bin/sh
Whereas arrays are something Bash specific that shell does not allow.
So to make it work, change the shebang of your script to Bash:
#!/bin/bash
Specify your interpreter explicitly in the crontab entry. Use
bash /path/zero_check.sh
rather than
/path/zero_check.sh
Just for the documentation,
i had an old script to run which had an Syntax Error in the Shebang:
#/bin/bash
instead of
#!/bin/bash
Also check the Script is executable of course.
Very similar problem with incorrect bash function declarations. This works OK from the command line, but it causes cron to fail...
function test () { ... }
Cron should save the errors in /var/mail
I also recommend linting with "shellcheck" because it found another error I didn't notice.
Is there a generic way in a bash script to "try" something but continue if it fails? The analogue in other languages would be wrapping it in a try/catch and ignoring the exception.
Specifically I am trying to source an optional satellite script file:
. $OPTIONAL_PATH
But when executing this, if $OPTIONAL_PATH doesn't exist, the whole script screeches to a halt.
I realize I could check to see if the file exists before sourcing it, but I'm curious if there is a generic reusable mechanism I can use that will ignore the error without halting.
Update: Apparently this is not normal behavior. I'm not sure why this is happening. I'm not explicitly calling set -e anywhere ($- is hB), yet it halts on the error. Here is the output I see:
./script.sh: line 36: projects/mobile.sh: No such file or directory
I added an echo "test" immediately after the source line, but it never prints, so it's not anything after that line that is exiting. I am running Mac OS 10.9.
Update 2: Nevermind, it was indeed shebanged as #!/bin/sh instead of #!/bin/bash. Thanks for the informative answer, Kaz.
Failed commands do not abort the script unless you explicitly configure that mode with set -e.
With regard to Bash's dot command, things are tricky. If we invoke bash as /bin/sh then it bails the script if the . command does not find the file. If we invoke bash as /bin/bash then it doesn't fail!
$ cat source.sh
#!/bin/sh
. nonexistent
echo here
$ ./source.sh
./source.sh: 3: .: nonexistent: not found
$ ed source.sh
35
1s/sh/bash/
wq
37
$ ./source.sh
./source.sh: line 3: nonexistent: No such file or directory
here
It does respond to set -e; if we have #!/bin/bash, and use set -e, then the echo is not reached. So one solution is to invoke bash this way.
If you want to keep the script maximally portable, it looks like you have to do the test.
The behavior of the dot command aborting the script is required by POSIX. Search for the "dot" keyword here. Quote:
If no readable file is found, a non-interactive shell shall abort; an interactive shell shall write a diagnostic message to standard error, but this condition shall not be considered a syntax error.
Arguably, this is the right thing to do, because dot is used for including pieces of the script. How can the script continue when a whole chunk of it has not been found?
Otherwise arguably, this is braindamaged behavior inconsistent with the treatment of other commands, and so Bash makes it consistent in its non-POSIX-conforming mode. If programmers want a command to fail, they can use set -e.
I tend to agree with Bash. The POSIX behavior is actually more broken than initially meets the eye, because this also doesn't work the way you want:
if . nonexistent ; then
echo loaded
fi
Even if the command is tested, it still aborts the script when it bails.
Thank GNU-deness we have alternative utilities, with source code.
You have several options:
Make sure set -e wasn't used, or turn it off with set +e. Your bash script should not exit by default simply because the . command failed.
Test that the file exists prior to sourcing.
[ -f "$OPTIONAL_PATH" ] && . "$OPTIONAL_PATH"
This option is complicated by the fact that if $OPTIONAL_PATH does not contain
any slashes, . will still try to find the file in your path.
If you want to keep set -e on, "hide" the failure like this:
. "$OPTIONAL_PATH" || true
Even if the source fails, the exit status of the command list as a whole will be 0, due to the || true.
(Much of this is covered [better] by Kaz's answer, especially the references to the POSIX standard, but I wasn't sure when or if he would undelete his answer.)
This is not the default behavior. Did you set -e or use #!/bin/bash -e anywhere in your script, to make it automatically exit on failure?
If so, you can use
. $OPTIONAL_PATH || true
to continue anyways.
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.