Prevent hardcoding of if condition in bash - bash

Following is my sample code which is setting environment settings to the session. I am getting env setting name as first argument of the function and storing it to supplied_env , I have a list of possible environments which contains possible or allowed environments allowed for this script. I have to keep this list hard coded which is acceptable. However in the end just before sourcing the environment file, I have to run if condition with hardcoded environment name(env1 or env2). Is there any way to only keep the environment names hardcoded only at the beginning and match them dynamically later ?
#!/bin/bash
supplied_env="${1}"
possible_env="env1 env2"
if [ $# -ne 1 ];then
echo "You must provide any one of these values ${possible_env}"
exit 1;
elif ! echo "${possible_env}"|grep "$supplied_env" ;then
echo "Incorrect value provided, You must provide any one of these values ${possible_env}"
exit 2;
else
target_env=${supplied_env}
fi
if [ ${supplied_env} == "env1" ];then #<------How to avoid using `env1` string here, rather get it from possible_env ?
source ~/.env1
elif [ ${supplied_env} == "env2" ];then
source ~/.env2
else
echo "Error..."
fi
In short, in future there would be move environments like env3 ,env4 etc. I just want to update the script at one place not at multiple places.

if [ ${supplied_env} == "env1" ];then #<------How to avoid using `env1` string here, rather get it from possible_env ?
source ~/.env1
elif [ ${supplied_env} == "env2" ];then
source ~/.env2
else
echo "Error..."
fi
You can substitute $supplied_env in the source statement. Since you've already done error checking you don't need any other logic, just a single source call.
source ~/."$supplied_env"

Store the allowed names as keys in an associative array.
declare -A possible_env=([env1]=~/.env1 [env2]=~/.env2)
Then you can check for the existence of a key in this array.
supplied_env="${1:?Must supply an environment name}"
declare -A possible_env=([env1]=~/.env1 [env2]=~/.env2)
if [[ ! -v possible_env[$suppliedEnv] ]]; then
echo "Incorrect value provided, You must provide any one of these values: ${!possible_env[*]}"
exit 2;
fi
source "${possible_env[$suppliedEnv]"

Related

What's wrong with my bash script? It cannot specify my OS type

#!/bin/bash
if [ ["$OSTYPE" == "linux-gnu"*] ]; then
SCRIPT_PATH=$(dirname $(realpath -s $0))
elif [ ["$OSTYPE" == "darwin"*] ]; then
SCRIPT_PATH=$(dirname $(pwd))
echo "mac!!"
else
echo "Unknown OS!"
exit
fi
I want to write a bash script to specify the OS type.
But on my MacOS, the result shows "Unknown OS!", which is wrong.
I tried echo $OSTYPE in terminal, it shows darwin20.0.
So I wonder what's the problem in my code?
The case statement is specifically intended for comparing a single string against various patterns, and doing different things depending on which it matches:
#!/bin/bash
case "$OSTYPE" in
"linux-gnu"* )
script_path="$(dirname "$(realpath -s "$0")")" ;;
"darwin"* )
script_path="$(dirname "$(pwd)")" ;;
* )
echo "Unknown OS!" >&2
exit 1 ;;
esac
Notes: each pattern is delimited with a ) at the end. You can also put a ( at the beginning, but most people don't bother. Each case ends with a double semicolon. The * case at the end will match anything that didn't match an earlier pattern, so it functions like an else clause in an if ... elif ... statement.
Some other changes I made:
It's a good idea to double-quote variable references and command substitutions (e.g. "$(realpath -s "$0")" instead of just $(realpath -s $0)) to avoid weird parsing problems with some characters (mostly spaces) in values. (There are some places where it's safe to leave the double-quotes off, but it's not worth trying to remember where they are.)
Since there are a whole bunch of all-caps names with special functions, it's safest to use lower- or mixed-case names (e.g. script_path instead of SCRIPT_PATH) to avoid conflicts.
Error and status messages (like "Unknown OS!") should generally be sent to standard error instead of standard output. I used >&2 to redirect the message to standard error.
When a script (or function, or program, or whatever) exits after an error, it should return a nonzero exit status to indicate that it failed. Different codes can be used to indicate different problems, but 1 is commonly used as a generic "something went wrong" code, so I used exit 1 here.
And I recommend using shellcheck.net to scan your scripts for common mistakes. It'll save you a lot of trouble.
Make sure you have no spaces between your opening and closing brackets, i.e., [[ and ]] vs [ [ and ] ] and you may get rid of the quotes in your patterns:
#!/usr/bin/env bash
OSTYPE=linux-gnu-123
if [[ "$OSTYPE" == linux-gnu* ]]; then
echo "linux"
elif [[ "$OSTYPE" == darwin* ]]; then
echo "mac"
else
echo "Unknown OS!"
fi
Also, use https://www.shellcheck.net/ to verify your scripts.
The problem is your attempt checking wildcard expressions via =="..."*. This needs to be done via grep. Try something like this:
#!/usr/bin/env bash
# define method
function checkOS() {
local os="$OSTYPE";
if [[ "$os" == "msys" ]]; then
echo "windows";
elif ( echo "$os" | grep -Eq "^darwin.*$" ); then
echo "mac";
elif ( echo "$os" | grep -Eq "^linux-gnu.*$" ); then
echo "linux";
else
echo "Unknown OS!" >> /dev/stderr;
exit 1;
fi
}
# try method
os="$( checkOS )";
echo -e "Current OS is \033[1m${os}\033[0m.";

How to assign a variable inside if condition in shell script

I would like to know how to assign a variable inside if block in shell script..
Below is my code..
if [[ -z "$MMBOX_PATH" || -z "$BACKUP_PATH" || -z "$REMOTE_SERVER" || -z "$LOG_PATH" ]]
then
echo -e "Must Provide All Required Paths [$FLAG is Empty].."
exit 1
fi
The above code will run whenever it found empty variable, but I also wants to know which variable is empty (E.g., In above code suppose if LOG_PATH variable is empty then it should display in echo output in place of $FLAG )
I tried following codes..
if [[ `FLAG='MMBOX_PATH'` && -z "$MMBOX_PATH" || `FLAG='BACKUP_PATH'` && -z "$BACKUP_PATH" || `FLAG='REMOTE_SERVER'` && -z "$REMOTE_SERVER" || `FLAG='LOG_PATH'` && -z "$LOG_PATH" ]]
then
echo -e "Must Provide All Required Paths [$FLAG is Empty].."
exit 1
fi
But above code returns false hence it is not printing the content inside echo.
I also tried to keep FLAG variable before condition execution, but every time it returns 'Nothing'
if FLAG='MMBOX_PATH' && [[ -z "$MMBOX_PATH" ]]
then
echo -e "Must Provide All Required Paths [$FLAG is Empty].."
exit 1
fi
In above case I'm getting FLAG='MMBOX_PATH' in output but if I add one more condition to that if nothing is printing (Means if I check same thing for BACKUP_PATH,REMOTE_SERVER..)
if FLAG='MMBOX_PATH' && [[ -z "$MMBOX_PATH" ]] && FLAG='LOG_PATH' && [[ -z "$LOG_PATH" ]]
then
echo -e "Must Provide All Required Paths [$FLAG is Empty].."
exit 1
fi
In this case nothing is printing even though MMBOX_PATH present and LOG_PATH empty.
Note: Using if condition each and every variable it is possible to know which variable is empty,but I don't want to extend my lines with if-else conditions I just want to know in that if block itself how to assign a variable and prints once condition is true.
Can anybody help me how to get empty variable..? (/bin/bash)
If all you are doing is checking existence with the if you could use a function.
check() {
for i in "$#";do
if [[ -z "${!i}" ]]
then
echo -e "Must Provide All Required Paths [\$$i is Empty].."
exit 1
fi
done
}
check MMBOX_PATH BACKUP_PATH REMOTE_SERVER LOG_PATH
Shell already provides a syntax for verifying that a variable has a value and exits if it does not:
: ${MMBOX_PATH:?Must provide MMBOX_PATH}
: ${BACKUP_PATH:?Must provide BACKUP_PATH}
: ${REMOTE_SERVER:?Must provide REMOVE_SERVER}
: ${LOG_PATH:?Must provide LOG_PATH}
There's no need to define a check function that does the same thing.
The initial colon is the do-nothing command; the shell evaluates its arguments, and : exits with status 0 immediately. The parameter expansion is what verifies that the named parameter has a value. If it does not, the given error message is printed. If the shell is not interactive, it also exits with status 1.

Testing empty variables

I have a large number of variables in my script, and I want the script to error out if any one of the variables are empty.
I know I can:
if [[ -z "$var_1" ]] || [[ -z "$var_2" ]] || ... [[ -z "$var_n" ]]; then
# failure message
fi
However, I cannot inform the user which variable was empty if I do it in this way. Is there an alternative approach to the above so that I can inform the user about the empty variable?
#!/bin/sh
foo=(var_1 var_2 var_n)
for bar in ${foo[*]}
do
if [[ ! ${!bar} ]]
then
echo $bar is empty
fi
done
Just use ${var:?var is empty or unset} the first time you reference the variable. If empty strings are acceptable and you only care if the variables are set, do ${var?var is unset}. Using ? in the parameter expansion causes the shell to terminate and if the variable is (empty or) unset.

Is there no unless in shell script?

I tried to do the opposite of if in shell to only do something if a value in a hash doesn't exist. I've learned to create a hash in bash from here:Associative arrays in Shell scripts
declare -A myhash
I declared a simple hash:
myhash[$key]="1"
and
[ ${myhash[$key]+abc} ]
to see if myhash[$key] exist or not from here: Easiest way to check for an index or a key in an array?
I also learned to add ! in front of an expression to do the opposite in if clause from here:How to make "if not true condition"?
But the following expression
if ! [ ${myhash[$key]+abc} ]; then
echo $key
fi
doesn't work and no information is printed. There is no error message.
I tried this:
[ ${myhash[$key]+abc} ] && echo $key
And got the error message:
abc: command not found
Can anyone tell me how to make it work?
First, this
[ ${myhash[$key]+abc} ]
will test if the result of the parameter expansion is empty or not. Either ${myhash[$key]} exists and expands to a string (which itself may or may not be empty), or it does not and it expands to the string "abc". Assuming you don't set myhash[$key]="", the expansion always produces a non-empty string, and so the command succeeds. As a result,
! [ ${myhash[$key]+abc} ] would always fail.
Assuming "abc" is not a valid entry in myhash, you need to actually check if the parameter expands to an actual value, or "abc" for unset keys:
if [[ ${myhash[$key]+abc} == abc ]]; then
echo "$key does not exist in myhash"
else
echo "myhash[$key]=${myhash[$key]}"
fi
The reason nothing is printed is because you check if the value is unset after setting it.
Here's what you're doing:
#!/bin/bash
key=foo
declare -A myhash # create associative array
myhash[$key]="1" # set the value
if ! [ ${myhash[$key]+abc} ]; then # check if value is not set
echo $key # if so, print key
fi
And now you're asking "Why isn't the unset check triggering when the value is set?"
If you instead wanted to check if the key is set, you should not have inverted the existence check using !.
As for [ ${myhash[$key]+abc} ] && echo $key giving abc: command not found, I can't explain that. It would never normally happen.
That is what you would have seen if you did ${myhash[$key]+abc} && echo $key though. Are you sure you're correctly presenting the actual examples you ran, and the actual results you got
Easiest way to check for an index or a key in an array? shows how to check if it exists with [ ${array[key]+abc} ].
If it does, the command exits successfully, so the portion after the && gets executed (echo "exists")
You want to check if it doesn't exist. So you want your command to be executed if the [..] portion exits unsuccessfully. So use || instead of &&:
[ ${myhash[$key]+abc} ] || echo "does not exist"
&&: carry on with next command only if first succeeded
||: only carry on with next command if first did not succeed

No output from script

I've edited my script, and get no more errors, however, the script is not executing to the Minecraft server, no announcement attempts are made at all for that matter. I'm rally puzzled. It's as if it's not running at all like the server is not running, but it is, and should be matching "is running" from the status command.
and code is:
#!/bin/bash
checkServer=$(/etc/init.d/minecraft status);
cd /.smc;
# Is the server even running?
if [ checkServer = *"is running"* ];
then
# No count file? Create it.
if [ ! -f /.smc/lastAnnouncement.txt ];
then
echo 0 < /.smc/lastAnnouncement.txt;
fi
# Load count
lastAnn=$(cat /.smc/lastAnnouncement.txt);
# ANNOUNCEMENTS
announcement[0]='Dont forget to check out http://fb.com/pyrexiacraftfans for news and updates';
announcement[1]='Use our Facebook page to request land protection! Visit http://fb.com/pyrexiacraftfans';
# Should we restart announcement que?
if lastAnn == ${#announcement[#]}+1;
then
echo 0 < /.smc/lastAnnouncement.txt;
fi
# Send announcement
sendAnnouncement=$(/etc/init.d/minecraft command say announcement[lastAnn]);
# Next announcement count
lastAnn=$((lastAnn+1));
# Write next announacment count
echo lastAnn < /.smc/lastAnnouncement.txt;
fi
There are multiple issues with your script, ranging from useless semicolons to bad logic. The list of issues is so long that it's easier to post a corrected script than point out the issues (the other answers don't even come close to listing all the errors).
The corrected script is:
#!/bin/bash
checkServer=$(/etc/init.d/minecraft status)
cd /.smc
# Is the server even running?
if [[ $checkServer =~ "is running" ]]; then
# No count file? Create it.
if [ ! -f /.smc/lastAnnouncement.txt ]; then
echo 0 > /.smc/lastAnnouncement.txt
fi
# Load count
lastAnn=$(cat /.smc/lastAnnouncement.txt)
# ANNOUNCEMENTS
announcement[0]='Dont forget to check out http://fb.com/pyrexiacraftfans for news and updates'
announcement[1]='Use our Facebook page to request land protection! Visit http://fb.com/pyrexiacraftfans'
# Send announcement
sendAnnouncement=$(/etc/init.d/minecraft command say ${announcement[$lastAnn]})
# Next announcement count
((++lastAnn))
# Write next announacment count
# Should we restart announcement que?
if [[ $lastAnn -gt ${#announcement[#]} ]]; then
echo 0 > /.smc/lastAnnouncement.txt
else
echo $lastAnn > /.smc/lastAnnouncement.txt
fi
fi
The issues with your script (leaving aside the excess semicolons which don't hurt, just needless disk space wastage):
missing $ before variable name
Incorrect string comparison. Use =~ instead of ==, [[ instead of [, and remove * from both sides of the string *"is running"*
if [ checkServer == *"is running"* ]
Wrong redirection. you want to write to file, so >, not <. This is multiple times.
echo 0 < /.smc/lastAnnouncement.txt;
echo 0 < /.smc/lastAnnouncement.txt;
Variable names missing $ and wrong redirection
echo lastAnn < /.smc/lastAnnouncement.txt;
Easier increment with ((++lastAnn)). Also this is invalid shell, as arithmetic needs expr command or ((...)) builtin
lastAnn=$lastAnn+1;
Missing $ in variable name. Missing test , [ or [[. Missing expr or $((..)) for addition of 1. -eq should be used instead of == for number equality. Logically this should use -gt to test against last index and +1 is not required.
if lastAnn == ${#announcement[#]}+1;
I won't go into the fact that the logic of writing the message queue index was incorrect, and would never loop back to 0.
However, you did a wonderful job of trying to write a script. Many people don't even try.
Edit : I missed out a {} on the array variable usage on line 21 of the script above. Fixed.
Try:
if [ checkServer = *"is running"* ];
(Yes single equals sign)
There are many errors:
First,
if [[ $checkserver == *"is running"* ]]
Using double [[...]] and a variable reference is $checkserver.
Then,
sendAnnouncement=$(
Without space.
Also,
if [ $lastAnn == $((${#announcement[#]}+1)) ]
Probably more...
I believe your script has quite a few syntax error.
i.e. there is a problem in this line:
sendAnnouncement = $(/etc/init.d/minecraft command say $announcement[$lastAnn]);
Replace it with this:
sendAnnouncement=$(/etc/init.d/minecraft command say $announcement[$lastAnn])
bash (and other shells) doesn't allow spaces before and after the assignment operator =
Also this line:
lastAnn=$lastAnn+1;
should be replaced with:
lastAnn=$((lastAnn+1))

Resources