i've been working on transforming if elif statement to getopts for sometime but i can't seems to get it. I would like to run getopts statement without inserting any options.
if [ $? eq 0 ]; then
echo "username exist"
exit 1
elif grep $uid
echo"$uid"
elif grep $gid
echo"$gid"
might be some stupid question, but yeah. just started learning with bash. Thanks for your patients.
Related
I'm working on a few scripts where I need to check for a few environment variables and list all the ones missing. I'm seeing a lot of posts where it would check for one and exit.
if [ -z "$BLAH" ]; then
echo "Missing BLAH"
exit 1
fi
However, I would like to print all the missing ones and then exit if anything is not set. I'm doing something like this right now, Is there any more elegant way to do this?
function check_env_vars {
status=0
for name in $*; do
value="${!name}"
echo "$value"
if [[ -z "$value" ]]; then
echo "$name environment variable must not be empty"
status=1
fi
done
return $status
}
if [[ check_env_vars "BLAH" "BLAH1" "BLAH2" -ne 0 ]]; then
exit 1
fi
Appreciate any thoughts or ideas.
if already checks the exit status; that's what it does with [[ ... ]] in the first place.
if ! check_env_vars "BLAH" "BLAH1" "BLAH2"; then
exit 1
fi
That said, bash already has syntax for verifying that a variable is set and non-null:
check_vars () {
for name; do
: ${!name:?$name must not be empty}
done
}
What would be the best way to check the exit status in an if statement in order to echo a specific output?
I'm thinking of it being:
if [ $? -eq 1 ]
then
echo "blah blah blah"
fi
The issue I am also having is that the exit statement is before the if statement simply because it has to have that exit code. Also, I know I'm doing something wrong since the exit would obviously exit the program.
Every command that runs has an exit status.
That check is looking at the exit status of the command that finished most recently before that line runs.
If you want your script to exit when that test returns true (the previous command failed) then you put exit 1 (or whatever) inside that if block after the echo.
That being said, if you are running the command and are wanting to test its output, using the following is often more straightforward.
if some_command; then
echo command returned true
else
echo command returned some error
fi
Or to turn that around use ! for negation
if ! some_command; then
echo command returned some error
else
echo command returned true
fi
Note though that neither of those cares what the error code is. If you know you only care about a specific error code then you need to check $? manually.
Note that exit codes != 0 are used to report errors. So, it's better to do:
retVal=$?
if [ $retVal -ne 0 ]; then
echo "Error"
fi
exit $retVal
instead of
# will fail for error codes == 1
retVal=$?
if [ $retVal -eq 1 ]; then
echo "Error"
fi
exit $retVal
An alternative to an explicit if statement
Minimally:
test $? -eq 0 || echo "something bad happened"
Complete:
EXITCODE=$?
test $EXITCODE -eq 0 && echo "something good happened" || echo "something bad happened";
exit $EXITCODE
$? is a parameter like any other. You can save its value to use before ultimately calling exit.
exit_status=$?
if [ $exit_status -eq 1 ]; then
echo "blah blah blah"
fi
exit $exit_status
For the record, if the script is run with set -e (or #!/bin/bash -e) and you therefore cannot check $? directly (since the script would terminate on any return code other than zero), but want to handle a specific code, #gboffis comment is great:
/some/command || error_code=$?
if [ "${error_code}" -eq 2 ]; then
...
Just to add to the helpful and detailed answer:
If you have to check the exit code explicitly, it is better to use the arithmetic operator, (( ... )), this way:
run_some_command
(($? != 0)) && { printf '%s\n' "Command exited with non-zero"; exit 1; }
Or, use a case statement:
run_some_command; ec=$? # grab the exit code into a variable so that it can
# be reused later, without the fear of being overwritten
case $ec in
0) ;;
1) printf '%s\n' "Command exited with non-zero"; exit 1;;
*) do_something_else;;
esac
Related answer about error handling in Bash:
Raise error in a Bash script
If you are writing a function – which is always preferred – you can propagate the error like this:
function()
{
if <command>; then
echo worked
else
return
fi
}
Now, the caller can do things like function && next as expected! This is useful if you have a lot of things to do in the if block, etc. (otherwise there are one-liners for this). It can easily be tested using the false command.
Using Z shell (zsh) you can simply use:
if [[ $(false)? -eq 1 ]]; then echo "yes" ;fi
When using Bash and set -e is on, you can use:
false || exit_code=$?
if [[ ${exit_code} -ne 0 ]]; then echo ${exit_code}; fi
This might only be useful in a limited set of use-cases, I use this specifically when I need to capture the output from a command and write it to a log file if the exit code reports that something went wrong.
RESULT=$(my_command_that_might_fail)
if (exit $?)
then
echo "everything went fine."
else
echo "ERROR: $RESULT" >> my_logfile.txt
fi
you can just add this if statement:
if [ $? -ne 0 ];
then
echo 'The previous command was not executed successfully';
fi
Below test scripts below work for
simple bash test commands
multiple test commands
bash test commands with pipe included:
if [[ $(echo -en "abc\n def" |grep -e "^abc") && ! $(echo -en "abc\n def" |grep -e "^def") ]] ; then
echo "pipe true"
else
echo "pipe false"
fi
if [[ $(echo -en "abc\n def" |grep -e "^abc") && $(echo -en "abc\n def" |grep -e "^def") ]] ; then
echo "pipe true"
else
echo "pipe false"
fi
The output is:
pipe true
pipe false
#!/usr/bin/env bash
if [[ $# -eq '0' ]]
then
var=command
if [[ ${var} -eq '0' ]]
then
do something
else
do something else
fi
fi
if [[ $# -eq '1' ]]
usage;
fi
if [[ $# -eq '2' ]]
if [[ "$1" != "-r" ]]
then
usage;
fi
if [[ "$2" =~ some_pattern ]]
then
do something
else
echo "Pattern is in an improper format. Please enter it as: correct_pattern, and try again"
exit 1
fi
usage="Usage: meta_script.sh -r correct_pattern
-r for reset is used to manually pass a parameter instead of using the default"
exit 1
fi
When I run this script, this is the error I get:
./meta_script.sh: line 31: syntax error near unexpected token `fi'
./meta_script.sh: line 31: `fi'
In the first if statement where I'm checking if the number of parameters are equal to 1, I had put a then, but I got the same error as above, except with then instead of fi. It's almost as if no matter what I put and where, I get these errors and when I remove them to try and fix it, I get another bunch of similar errors. Please help me correct this script. Thanks!
With regard to the segment:
if [[ $# -eq '2' ]]
if [[ "$1" != "-r" ]]
then
you are missing the then for the first if statement. Putting that in should get you past that error:
if [[ $# -eq 2 ]] ; then
if [[ "$1" != "-r" ]] ; then
You'll see I've put the then on the same line since it's a good habit to get into, realising that if and then always go together (same as while and do). It also allows you to see more lines of "useful" code on any given terminal :-)
I've also gotten rid of the useless quotes around 2 since $# always returns a numeric value. I'd advise sticking to using quotes just for strings.
I have some problem with my code here. This is my code:
#!bin/sh
grep "$1" Rail.txt > test.txt
if [ "$#" -eq 1 -a grep -q "$1" test.txt ]; then
grep "$1" Rail.txt
else
echo not found
fi
Problem:
It says: script.sh: line 3: [: too many arguments every time I run it.
I'm not sure what's wrong with my condition whether I use the wrong operators or parenthesis or square brackets.
At a semi-educated guess, what you want to write is:
if [ "$#" -eq 1 ] && grep -q "$1" test.txt; then
On what ocassions should we use the square brackets?
Historically, the test command, /bin/test was linked to /bin/[, and was an external command, not a shell built-in. These days (and for several decades now), it has been a shell built-in. However, it follows the structure of a command, requiring spaces to separate arguments, and if it is invoked as [, then the last argument must be ].
As to when you use it, you use it when you need to test a condition.
Note that you can write:
if ls; false; true; then echo "the last command counts"; else echo "no it doesn't"; fi
The if command executes a sequence of one or more commands, and tests the exit status of the last command. If the exit status is 0, success, the then clause is executed; if the exit status is not 0, then the else clause is taken.
So, you can use the test when you need to test something. It can be part of an if or elif or while (or until). It can also be used on its own with || or &&:
[ -z "$1" ] && echo "No arguments - or the first argument is an empty string"
[ "$var1" -gt "$var2" ] || echo "Oops!" && exit 1
These could be written as if statements too, of course:
if [ -z "$1" ]
then echo "No arguments - or the first argument is an empty string"
fi
if [ "$var1" -le "$var2" ]
then
echo "Oops!"
exit 1
fi
Note that I needed to invert the test in the second rewrite. Bash has a built-in ! operator which inverts the exit status of the command that follows, so I could also have written:
if ! [ "$var1" -gt "$var2" ]
and test has a ! too, so it could also be written:
if [ ! "$var1" -gt "$var2" ]
I would like my Bash script to print an error message if the required argument count is not met.
I tried the following code:
#!/bin/bash
echo Script name: $0
echo $# arguments
if [$# -ne 1];
then echo "illegal number of parameters"
fi
For some unknown reason I've got the following error:
test: line 4: [2: command not found
What am I doing wrong?
Just like any other simple command, [ ... ] or test requires spaces between its arguments.
if [ "$#" -ne 1 ]; then
echo "Illegal number of parameters"
fi
Or
if test "$#" -ne 1; then
echo "Illegal number of parameters"
fi
Suggestions
When in Bash, prefer using [[ ]] instead as it doesn't do word splitting and pathname expansion to its variables that quoting may not be necessary unless it's part of an expression.
[[ $# -ne 1 ]]
It also has some other features like unquoted condition grouping, pattern matching (extended pattern matching with extglob) and regex matching.
The following example checks if arguments are valid. It allows a single argument or two.
[[ ($# -eq 1 || ($# -eq 2 && $2 == <glob pattern>)) && $1 =~ <regex pattern> ]]
For pure arithmetic expressions, using (( )) to some may still be better, but they are still possible in [[ ]] with its arithmetic operators like -eq, -ne, -lt, -le, -gt, or -ge by placing the expression as a single string argument:
A=1
[[ 'A + 1' -eq 2 ]] && echo true ## Prints true.
That should be helpful if you would need to combine it with other features of [[ ]] as well.
Take note that [[ ]] and (( )) are keywords which have same level of parsing as if, case, while, and for.
Also as Dave suggested, error messages are better sent to stderr so they don't get included when stdout is redirected:
echo "Illegal number of parameters" >&2
Exiting the script
It's also logical to make the script exit when invalid parameters are passed to it. This has already been suggested in the comments by ekangas but someone edited this answer to have it with -1 as the returned value, so I might as well do it right.
-1 though accepted by Bash as an argument to exit is not explicitly documented and is not right to be used as a common suggestion. 64 is also the most formal value since it's defined in sysexits.h with #define EX_USAGE 64 /* command line usage error */. Most tools like ls also return 2 on invalid arguments. I also used to return 2 in my scripts but lately I no longer really cared, and simply used 1 in all errors. But let's just place 2 here since it's most common and probably not OS-specific.
if [[ $# -ne 1 ]]; then
echo "Illegal number of parameters" >&2
exit 2
fi
References
Bash Conditional Expressions
Conditional Constructs
Pattern Matching
Word Splitting
Filename Expansion (prev. Pathname Expansion)
Simple Commands
It might be a good idea to use arithmetic expressions if you're dealing with numbers.
if (( $# != 1 )); then
>&2 echo "Illegal number of parameters"
fi
>&2 is used to write the error message to stderr.
On []: !=, =, == ... are string comparison operators and -eq, -gt ... are arithmetic binary ones.
I would use:
if [ "$#" != "1" ]; then
Or:
if [ $# -eq 1 ]; then
If you're only interested in bailing if a particular argument is missing, Parameter Substitution is great:
#!/bin/bash
# usage-message.sh
: ${1?"Usage: $0 ARGUMENT"}
# Script exits here if command-line parameter absent,
#+ with following error message.
# usage-message.sh: 1: Usage: usage-message.sh ARGUMENT
A simple one liner that works can be done using:
[ "$#" -ne 1 ] && ( usage && exit 1 ) || main
This breaks down to:
test the bash variable for size of parameters $# not equals 1 (our number of sub commands)
if true then call usage() function and exit with status 1
else call main() function
Things to note:
usage() can just be simple echo "$0: params"
main can be one long script
Check out this bash cheatsheet, it can help alot.
To check the length of arguments passed in, you use "$#"
To use the array of arguments passed in, you use "$#"
An example of checking the length, and iterating would be:
myFunc() {
if [[ "$#" -gt 0 ]]; then
for arg in "$#"; do
echo $arg
done
fi
}
myFunc "$#"
This articled helped me, but was missing a few things for me and my situation. Hopefully this helps someone.
Here a simple one liners to check if only one parameter is given otherwise exit the script:
[ "$#" -ne 1 ] && echo "USAGE $0 <PARAMETER>" && exit
There is a lot of good information here, but I wanted to add a simple snippet that I find useful.
How does it differ from some above?
Prints usage to stderr, which is more proper than printing to stdout
Return with exit code mentioned in this other answer
Does not make it into a one liner...
_usage(){
_echoerr "Usage: $0 <args>"
}
_echoerr(){
echo "$*" >&2
}
if [ "$#" -eq 0 ]; then # NOTE: May need to customize this conditional
_usage
exit 2
fi
main "$#"
In case you want to be on the safe side, I recommend to use getopts.
Here is a small example:
while getopts "x:c" opt; do
case $opt in
c)
echo "-$opt was triggered, deploy to ci account" >&2
DEPLOY_CI_ACCT="true"
;;
x)
echo "-$opt was triggered, Parameter: $OPTARG" >&2
CMD_TO_EXEC=${OPTARG}
;;
\?)
echo "Invalid option: -$OPTARG" >&2
Usage
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
Usage
exit 1
;;
esac
done
see more details here for example http://wiki.bash-hackers.org/howto/getopts_tutorial
You should add spaces between test condition:
if [ $# -ne 1 ];
then echo "illegal number of parameters"
fi
I hope this helps.