Unexpected `then' bash script - bash

How do I fix this error? I can't see anything wrong with my syntax.
ipcheck() {
echolog "[INFO] Enabling IP Forwarding..."
sudo echo 1 > /proc/sys/net/ipv4/ip_forward
if[$(cat /proc/sys/net/ipv4/ip_forward) == "0"]
then
echolog "[CRITICAL] Could not enable IP Forwarding!"
exit 0
fi
echolog "[INFO] IP Forwarding successfully enabled!"
}
I know this is a very basic script, but it's part of a bigger one. The error happens on the then statement.

Shell scripting tends to be a lot more whitespace sensitive than you might be used to if you've come from other programming languages (read: C). Your if line has the problems. You are probably looking for:
if [ $(cat /proc/sys/net/ipv4/ip_forward) == "0" ]
The thing to remember here is that [ is not part of any special if syntax - it's the name of a program (sometimes a shell builtin). If you think of it like that, you can see how the command line parser needs it to be separated. Similarly, the [ command (or builtin) expects the closing ] to be separated from its other arguments, so you need a space before it, too.

The problem is that you need a space between if and [. The lack of a space is confusing bash's parser.

Place a space between the if and [$(cat...] section on line 4. For this script to run, you'll also need a space on the ] on the same line.
On a related note, if you're not using indentation in your shell scripts, you should seriously consider it as it makes maintenance and legibility of your code much easier.

Slightly refactored (improved) version that is not bash dependant:
#!/bin/sh
ipcheck() {
echolog "[INFO] Enabling IP Forwarding..."
sudo echo 1 > /proc/sys/net/ipv4/ip_forward || {
echolog "[CRITICAL] Error echoing to ip_forward file"
exit 1
}
# Paranoia check :)
status=$(cat /proc/sys/net/ipv4/ip_forward)
[ "$status" = "1" ] || {
echolog "[CRITICAL] Could not enable IP Forwarding!"
exit 1 # 1 is error in scripts, 0 is success
}
echolog "[INFO] IP Forwarding successfully enabled!"
}
Preferably make an error() function, perhaps something like:
# Call it like: error "[INFO] Could not ..."
error() {
echolog "$*"
exit 1
}
Oh and yeah, as everyone else pointed out, don't forget the spaces :)

Related

Shell Syntax error: 'While: command not found'

The code-snippet below checks whether the instance_status available or not. If it is available, wait for 15 seconds and if and echo the instance status.
I am getting the following error:
line 38: While: command not found
code:
While ["$INSTANCE_STATUS" -eq "available"]
do
wait 15
if $INSTANCE_STATUS" -ne "available"
then
echo "$SOURCE_INSTANCE_ID" is "$INSTANCE_STATUS"
fi
done
Many simple mistakes in your original code: While not while, missing spaces around [, missing double-quotes, comparision operators -eq and -ne are not for comparing strings...
Beware using wait, which is a valid shell command. But probably you meant sleep instead. Here details about the differences.
Try this:
while [ "$INSTANCE_STATUS" = "available" ]
do
sleep 15 # 'waits' 15 seconds.
# ATTENTION: here you might need to 'refresh' $INSTANCE_STATUS value to avoid an endless loop.
# Solution suggested by #Charles Duffy:
# INSTANCE_STATUS=$(systemctl status "$SOURCE_INSTANCE_ID")
if [ "$INSTANCE_STATUS" != "available" ]
then
echo "$SOURCE_INSTANCE_ID is $INSTANCE_STATUS"
fi
done
Edited: many fixes to original answer suggested ...forced ;) by #Charles Duffy. Thanks Charles, now I realize my shell script skills are getting pretty rusty.

Bash output to same line while preserving column

Ok, this is going to seem like a basic question at first, but please hear me out. It's more complex than the title makes it seem!
Here is the goal of what I am trying to do. I would like to output to console similar to Linux boot.
Operating system is doing something... [ OK ]
Now this would seem to be obvious... Just use printf and set columns. Here is the first problem. The console needs to first print the action
Operating system is doing something...
Then it needs to actually do the work and then continue by outputting to the same line with the [ OK ].
This would again seem easy to do using printf. Simply do the work (in this case, call a function) and return a conditional check and then finish running the printf to output either [ OK ] or [ FAIL ]. This technically works, but I ran into LOTS of complications doing this. This is because the function must be called inside a subshell and I cant pass certain variables that I need. So printf is out.
How about just using echo -n? That should work right? Echo the first part, run the function, then continue echoing based on the return to the same line. The problem with this solution is I can no longer preserve the column formatting that I can with printf.
Operating system is doing something... [ OK ]
Operating system is doing something else... [ OK ]
Short example... [ OK ]
Any suggestions how I can fix any of these problems to get a working solution? Thanks
Here is another way I tried with printf. This gives the illusion of working, but the method is actually flawed because it does not give you the progress indication, ie the function runs first before it ever prints out the the function is running. The "hey im doing stuff" prints immediately with the "hey im done" message. As a result, its pointless.
VALIDATE $HOST; printf "%-50s %10s\n" " Validating and sanitizing input..." "$(if [ -z "$ERROR" ]; then echo "[$GREEN OK $RESET]"; else echo "[$RED FAIL $RESET] - $ERROR"; echo; exit; fi)"
There's no particular reason all the printf strings have to be printed together, unless you're worried some code you call is going to move the cursor.
Reordering your example:
printf "%-50s " " Validating and sanitizing input..."
VALIDATE $HOST
if [ -z "$ERROR" ]; then
printf "%10s\n" "[$GREEN OK $RESET]";
else
printf "%10s\n" "[$RED FAIL $RESET] - $ERROR"
echo
exit
fi
I have no idea what $ERROR contains or where it is supposed to display.

Can an approach be found to do set -e/ERR subshell trapping immune to && and || restrictions?

While the Bash man page states that:
The ERR trap is not executed if the failed command is ... part of a command executed in a && or || list ...
I hoped that code in a subshell would be in a different context and would not be subject to the above restriction. The code below shows that even subshells are not immune from this restriction:
#!/bin/bash
main()
{
local Arg="$1"
(
set -e
echo "In main: $Arg"
trap MyTrap ERR
$(exit 1)
echo "Should not get here"
)
return 1
}
MyTrap()
{
echo "In MyTrap"
}
main 1
[[ $? -eq 0 ]] || echo "failed"
echo
main 2 || echo "failed"
The above code has the following output:
In main: 1
In MyTrap
failed
In main: 2
Should not get here
failed
My present workaround is to use file persistence to save error states in MyTrap and then inspect return codes back in the caller. For example:
MyTrap()
{
echo "_ERROR_=$?" > $HOME/.persist
echo "In MyTrap"
}
main 1
[[ -f $HOME/.persist ]] && . $HOME/.persist || _ERROR_=0
[[ $_ERROR_ -eq 0 ]] || echo "failed"
The output of the above is now:
In main: 1
In MyTrap
failed
So, the question is: Can an approach be found to do set -e/ERR subshell trapping that is immune to && and || restrictions that is simpler than the workaround above?
Notes: This question applies to:
Bash 4.2 and higher
Red Hat 7, CentOS 7, and related distros (that is, not Debian, etc.)
No third-party software should be used. Packages must be available, for example, via OS-provider packages in the [CentOS-7-x86_64-DVD-1611.iso][2] repository iso (and similarly for RHEL 7, Fedora 7, etc).
This code is NOT a solution. It is a modified version that you can test to give you some ideas.
#!/bin/bash
main()
{
local Arg="$1"
(
echo "In main: $Arg"
$(exit 1)
echo "Should not get here"
)
return 1
}
MyTrap()
{
echo "In MyTrap"
exit 1
}
set -o errtrace
set -o functrace
trap MyTrap ERR
main 1
[[ $? -eq 0 ]] || echo "failed"
echo
main 2 || echo "failed"
It will not do what you would like it to do (I assume), because there is something important missing that is too involved to explain in a post, but there are a few key things.
If you want to handle errors uniformly in all your script, you will have to do the following.
Set the trap at the top level, and do NOT use set -e
Use the shell options that cause subshells and functions to inherit traps (shown in the example)
Create a framework where you differentiate between "expected" errors (explicitely handled as some kind of exception, not as return codes), and "unexpected" exceptions (bugs), which will be trapped.
Never use logical operators directly (except on test constructs or other simple statements you decide are safe enough)
Perform logical tests after collecting exceptions, not as tests on the return codes.
Building that framework is very tricky, but doable (I have it working flawlessly on dozens of very complex scripts). Once you have done it, if you chose to blindly test a command (without explicitly handling exceptions), if it fails you have the option to crash your script in the trap (exit) instead of continuing execution with your script in an unstable state.
This probably incurs a slight performance penalty (at least the way I did it, using a special try function preceding every statement for which exception handling is used), and sure requires a LOT of discipline in coding, but in my case it makes me way more productive building complex scripts with the reasonable confidence the location of any bug (not that I have any of those...) will be much easier to pinpoint.

Bash: graceful function death on error

I'm trying to find a way to emulate the behavior of set -e in a function, but only within the scope of that function.
Basically, I want a function where if any simple command would trigger set -e it returns 1 up one level. The goal is to isolate sets of risky jobs into functions so that I can gracefully handle them.
If you want any failing command to return 1, you can achieve that by following each command with || return 1.
For instance:
false || return 1 # This will always return 1
I am a big fan of never letting any command fail without explicit handling. For my scripts, I am using an exception handling technique where I return errors in a way that is not return codes, and trap all errors (with bash traps). Any command with a non-zero return code automatically means an improperly handled situation or bug, and I prefer my scripts to fail as soon as such a situation occurs.
Caution: I highly advise against using this technique. If you run the function in a subshell environment, you almost get the behavior you desire. Consider:
#!/bin/bash
foo() ( # Use parens to get a sub-shell
set -e # Does not impact the main script
echo This is executed
false
echo This should *not* be executed
)
foo # Function call fails, returns 1
echo return: $?
# BUT: this is a good reason to avoid this technique
if foo; then # Set -e is invalid in the function
echo Foo returned 0!!
else
echo fail
fi
false # Demonstrates that set -e is not set for the script
echo ok
Seems like you are looking for "nested exceptions" somewhat like what Java gives. For your requirement of scoping it, how about doing a set -e at the beginning of the function and making sure to run set +e before returning from it?
Another idea, which is not efficient or convenient, is to call your function in a subshell:
# some code
(set -e; my_function)
if [[ $? -ne 0 ]]; then
# the function didn't succeed...
fi
# more code
In any case, please be aware that set -e is not the greatest way to handle errors in a shell script. There are way too many issues making it quite unreliable. See these related posts:
What does set -e mean in a bash script?
Error handling in Bash
The approach I take for large scripts that need to exist for a long time in a production environment is:
create a library of functions to do all the standard stuff
the library will have a wrapper around each standard action (say, mv, cp, mkdir, ln, rm, etc.) that would validate the arguments carefully and also handle exceptions
upon exception, the wrapper exits with a clear error message
the exit itself could be a library function, somewhat like this:
--
# library of common functions
trap '_error_handler' ERR
trap '_exit_handler' EXIT
trap '_int_handler' SIGINT
_error_handler() {
# appropriate code
}
# other handlers go here...
#
exit_if_error() {
error_code=${1:-0}
error_message=${2:-"Uknown error"}
[[ $error_code == 0 ]] && return 0 # it is all good
# this can be enhanced to print out the "stack trace"
>&2 printf "%s\n" $error_message
# out of here
my_exit $error_code
}
my_exit() {
exit_code=${1:-0}
_global_graceful_exit=1 # this can be checked by the "EXIT" trap handler
exit $exit_code
}
# simple wrapper for cp
my_cp() {
# add code to check arguments more effectively
cp $1 $2
exit_if_error $? "cp of '$1' to '$2' failed"
}
# main code
source /path/to/library.sh
...
my_cp file1 file2
# clutter-free code
This, along with effective use of trap to take action on ERR and EXIT events, would be a good way to write reliable shell scripts.
Doing more research, I found a solution I rather like in Google's Shell Style Guide. There are some seriously interesting suggestions here, but I think I'm going to go with this for readability:
if ! mv "${file_list}" "${dest_dir}/" ; then
echo "Unable to move ${file_list} to ${dest_dir}" >&2
exit "${E_BAD_MOVE}"
fi

Error: =: command not found (Bash Script)

I found a nifty little shell script that I wanted to use from this website here. I have followed everything step-by-step, but receive the following error when running this on my CentOS box.
./deploy: line 3: =: command not found
Line 3 only contains...
$ERRORSTRING = "Error. Please make sure you've indicated correct parameters"
I've tried toying around with a bit, but don't understand why it won't accept the "=" character. Is there something wrong with the script, or is it merely something different in the way that my server processes the script?
Thanks!
Gah, that script is full of bad scripting practices (in addition to the outright error you're running into). Here's the outright error:
$ERRORSTRING = "Error. Please make sure you've indicated correct parameters"
As devnull pointed out, this should be:
ERRORSTRING="Error. Please make sure you've indicated correct parameters"
A couple of lines down (and again near the end), we have:
echo $ERRORSTRING;
...which works, but contains two bad ideas: a variable reference without double-quotes around it (which will sometimes be parsed in unexpected ways), and a semicolon at the end of the line (which is a sign that someone is trying to write C or Java or something in a shell script). Use this instead:
echo "$ERRORSTRING"
The next line is:
elif [ $1 == "live" ]
...which might work, depending on whether the value of $1 has spaces, or is defined-but-blank, or anything like that (again, use double-quotes to prevent misparsing!). Also, the == comparison operator is nonstandard -- it'll work, because bash supports it in its [ ... ] builtin syntax, but if you're counting on having bash extensions available, why not use the much cleaner [[ ... ]] replacement? Any of these would be a better replacement for that line:
elif [ "$1" = "live" ]
elif [[ $1 == "live" ]]
elif [[ "$1" == "live" ]]
Personally, I prefer the last. The double-quotes aren't needed in this particular case, but IMO it's safest to just double-quote all variable references unless there's a specific reason not to. A bit further down, there's a elif [ $2 == "go" ] that the same comments apply to.
BTW, there's a good sanity-checking tool for shell scripts at www.shellcheck.net. It's not quite as picky as I am (e.g. it doesn't flag semicolons at the ends of lines), but it pointed out all of the actual errors in this script...
"Devnulls" answer was correct -- I had to remove the spaces around the "=" and remove the "$" from that line as well. The end result was...
ERRORSTRING="Error. Please make sure you've indicated correct parameters"
I've upvoted Devnull and gniourf_gniourf's comments.
Thank you to all whom have assisted!

Resources