UNIX multiple if conditions - bash

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" ]

Related

Bash:check arguments passed on 'while-case' loop [duplicate]

I need to check the existence of an input argument. I have the following script
if [ "$1" -gt "-1" ]
then echo hi
fi
I get
[: : integer expression expected
How do I check the input argument1 first to see if it exists?
It is:
if [ $# -eq 0 ]
then
echo "No arguments supplied"
fi
The $# variable will tell you the number of input arguments the script was passed.
Or you can check if an argument is an empty string or not like:
if [ -z "$1" ]
then
echo "No argument supplied"
fi
The -z switch will test if the expansion of "$1" is a null string or not. If it is a null string then the body is executed.
It is better to demonstrate this way
if [[ $# -eq 0 ]] ; then
echo 'some message'
exit 1
fi
You normally need to exit if you have too few arguments.
In some cases you need to check whether the user passed an argument to the script and if not, fall back to a default value. Like in the script below:
scale=${2:-1}
emulator #$1 -scale $scale
Here if the user hasn't passed scale as a 2nd parameter, I launch Android emulator with -scale 1 by default. ${varname:-word} is an expansion operator. There are other expansion operators as well:
${varname:=word} which sets the undefined varname instead of returning the word value;
${varname:?message} which either returns varname if it's defined and is not null or prints the message and aborts the script (like the first example);
${varname:+word} which returns word only if varname is defined and is not null; returns null otherwise.
Try:
#!/bin/bash
if [ "$#" -eq "0" ]
then
echo "No arguments supplied"
else
echo "Hello world"
fi
Only because there's a more base point to point out I'll add that you can simply test your string is null:
if [ "$1" ]; then
echo yes
else
echo no
fi
Likewise if you're expecting arg count just test your last:
if [ "$3" ]; then
echo has args correct or not
else
echo fixme
fi
and so on with any arg or var
Another way to detect if arguments were passed to the script:
((!$#)) && echo No arguments supplied!
Note that (( expr )) causes the expression to be evaluated as per rules of Shell Arithmetic.
In order to exit in the absence of any arguments, one can say:
((!$#)) && echo No arguments supplied! && exit 1
Another (analogous) way to say the above would be:
let $# || echo No arguments supplied
let $# || { echo No arguments supplied; exit 1; } # Exit if no arguments!
help let says:
let: let arg [arg ...]
Evaluate arithmetic expressions.
...
Exit Status:
If the last ARG evaluates to 0, let returns 1; let returns 0 otherwise.
I often use this snippet for simple scripts:
#!/bin/bash
if [ -z "$1" ]; then
echo -e "\nPlease call '$0 <argument>' to run this command!\n"
exit 1
fi
More modern
#!/usr/bin/env bash
if [[ $# -gt 0 ]]
then echo Arguments were provided.
else echo No arguments were provided.
fi
If you'd like to check if the argument exists, you can check if the # of arguments is greater than or equal to your target argument number.
The following script demonstrates how this works
test.sh
#!/usr/bin/env bash
if [ $# -ge 3 ]
then
echo script has at least 3 arguments
fi
produces the following output
$ ./test.sh
~
$ ./test.sh 1
~
$ ./test.sh 1 2
~
$ ./test.sh 1 2 3
script has at least 3 arguments
$ ./test.sh 1 2 3 4
script has at least 3 arguments
As a small reminder, the numeric test operators in Bash only work on integers (-eq, -lt, -ge, etc.)
I like to ensure my $vars are ints by
var=$(( var + 0 ))
before I test them, just to defend against the "[: integer arg required" error.
one liner bash function validation
myFunction() {
: ${1?"forgot to supply an argument"}
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
add function name and usage
myFunction() {
: ${1?"forgot to supply an argument ${FUNCNAME[0]}() Usage: ${FUNCNAME[0]} some_integer"}
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
add validation to check if integer
to add additional validation, for example to check to see if the argument passed is an integer, modify the validation one liner to call a validation function:
: ${1?"forgot to supply an argument ${FUNCNAME[0]}() Usage: ${FUNCNAME[0]} some_integer"} && validateIntegers $1 || die "Must supply an integer!"
then, construct a validation function that validates the argument, returning 0 on success, 1 on failure and a die function that aborts script on failure
validateIntegers() {
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
return 1 # failure
fi
return 0 #success
}
die() { echo "$*" 1>&2 ; exit 1; }
Even simpler - just use set -u
set -u makes sure that every referenced variable is set when its used, so just set it and forget it
myFunction() {
set -u
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
In my case (with 7 arguments) the only working solution is to check if the last argument exists:
if [[ "$7" == '' ]] ; then
echo "error"
exit
fi

Multiple `if` statements in bash script

I'm trying to write a short bash script that optionally accepts arguments from the command line, or prompts for their input
if [ [ -z "$message" ] && [ -z "$predefined" ] ] ; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" ]; then
if [ -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
fi
If neither message nor predefined has been passed in as command line arguments, then the code should prompt for a value for message; otherwise if predefined has been passed in as a command line argument, then the script should test for the existence of a file of that name and only continue if the file does exist
But I'm getting the following error
[: -z: binary operator expected
at the first of those if tests
Any help in explaining what's wrong with my syntax for that first if statement? Or providing an alternative syntax to achieve the same objectives.
The first if is not well-formed. This would work:
if [ -z "$message" ] && [ -z "$predefined" ]; then
or this:
if test -z "$message" && test -z "$predefined"; then
or this bash-specific, easy but dirty way:
if [[ -z "$message" ]] && [[ -z "$predefined" ]]; then
or this bash-specific proper way:
if [[ -z $message && -z $predefined ]]; then
In this last version the double-quotes are unnecessary, not a typo.
Thanks #mklement0 for the corrections in the bash-specific style, and for this final comment:
I should note that there's one case where double-quoting is still a must inside [[ ... ]], namely if you want a variable reference on the right side of a string comparison (==) to be treated as a literal:
v='[a]'
[[ $v == $v ]] # FALSE!
[[ $v == "$v" ]] # true
Without double-quoting, the right-hand side is interpreted as a pattern. Some people advocate always double-quoting variable references so as not to have to remember such subtleties. That said (from bash 3.2 on), you must NOT double-quote the right operand when regex matching with =~
test expression1 -a expression2
is true if both expressions are true.
test expression1 -o expression2
is true if either or both expressions are true.
if [ -z "$message" -a -z "$predefined" ]; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" -a -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
This was able to combine 4 test into 2 while also getting rid of one nested if expression; then ; fi

Shell script elif

I am new in shell script, trying to catch the return value of a program, and do something with it.
I have this script below
#!/bin/sh
if [ $# !=2 ] ; then
echo "Usage : param1 param2 "
exit 1;
elif [ $# -eq 2 ]; then
./callprogram
$out = $?
echo "$out"
fi
if [ $out==0 ]; then
echo "out ok"
fi
It keeps getting me error of
"[: 11: 0: unexpected operator
out ok
I have no clue why line 11 is wrong. if I remove "fi", it will promt that it needs "fi". Can anyone help with this matter?
Thank you
You need a space after the [ and you need to use -eq (equals) or -ne (not equals) to compare numbers in your if-statement.
To assign a variable use out=$?, not $out = $?. There should be no spaces on either side of the = sign.
Try this:
if [ $# -ne 2 ] ; then
echo "Usage : param1 param2 "
exit 1
elif [ $# -eq 2 ]; then
./callprogram
out=$?
echo "$out"
fi
if [ $out -eq 0 ]; then
echo "out ok"
fi
Change:
if [ $out==0 ]; then
to:
if [ $out = 0 ]; then
add spaces, and change '==' to '='. Note, that bash, executed as a bash accepts ==. But if you run is as a sh it will say "unexpected operator".
Why:
The [ is a command (or symlink to test binary, depending on your OS and shell). It expects $out and == and 0 and ] to be separate command arguments. If you miss the space around them, you have one argument $out==0.
BTW:
It's safer to always enquote the variables like that:
if [ "$var" ......
instead of
if [ $var
because when variable is empty, then you can get another error because of wrong number of arguments (no argument instead of empty string).
You have several problems. The one that is giving you the error is that you need a space after != on
if [ $# != 2 ]
(although -ne would be better than !=). It appears that you are calling the script with 11 arguments, and then calling [ with the arguments 11 !=2, and it does not know what to do with !=2 because you meant != 2 but forgot the space. Also, you want
out=$?
on the assignment (no $ on the LHS)
and
if [ $out = 0 ]
on the comparison. (Spaces around the operator, which is '=' instead of '=='. '==' will work on many shells, but '=' works in more shells.)
But your script would be better written without the explicit reference to $?
#!/bin/sh
if test $# != 2; then
echo "Usage: $0 param1 param2 " >&2 # Errors go to stderr, not stdout
exit 1;
fi
# you know $# is 2 here. No need to check
if ./callprogram; then
echo "out ok"
fi

Check existence of input argument in a Bash shell script

I need to check the existence of an input argument. I have the following script
if [ "$1" -gt "-1" ]
then echo hi
fi
I get
[: : integer expression expected
How do I check the input argument1 first to see if it exists?
It is:
if [ $# -eq 0 ]
then
echo "No arguments supplied"
fi
The $# variable will tell you the number of input arguments the script was passed.
Or you can check if an argument is an empty string or not like:
if [ -z "$1" ]
then
echo "No argument supplied"
fi
The -z switch will test if the expansion of "$1" is a null string or not. If it is a null string then the body is executed.
It is better to demonstrate this way
if [[ $# -eq 0 ]] ; then
echo 'some message'
exit 1
fi
You normally need to exit if you have too few arguments.
In some cases you need to check whether the user passed an argument to the script and if not, fall back to a default value. Like in the script below:
scale=${2:-1}
emulator #$1 -scale $scale
Here if the user hasn't passed scale as a 2nd parameter, I launch Android emulator with -scale 1 by default. ${varname:-word} is an expansion operator. There are other expansion operators as well:
${varname:=word} which sets the undefined varname instead of returning the word value;
${varname:?message} which either returns varname if it's defined and is not null or prints the message and aborts the script (like the first example);
${varname:+word} which returns word only if varname is defined and is not null; returns null otherwise.
Try:
#!/bin/bash
if [ "$#" -eq "0" ]
then
echo "No arguments supplied"
else
echo "Hello world"
fi
Only because there's a more base point to point out I'll add that you can simply test your string is null:
if [ "$1" ]; then
echo yes
else
echo no
fi
Likewise if you're expecting arg count just test your last:
if [ "$3" ]; then
echo has args correct or not
else
echo fixme
fi
and so on with any arg or var
Another way to detect if arguments were passed to the script:
((!$#)) && echo No arguments supplied!
Note that (( expr )) causes the expression to be evaluated as per rules of Shell Arithmetic.
In order to exit in the absence of any arguments, one can say:
((!$#)) && echo No arguments supplied! && exit 1
Another (analogous) way to say the above would be:
let $# || echo No arguments supplied
let $# || { echo No arguments supplied; exit 1; } # Exit if no arguments!
help let says:
let: let arg [arg ...]
Evaluate arithmetic expressions.
...
Exit Status:
If the last ARG evaluates to 0, let returns 1; let returns 0 otherwise.
I often use this snippet for simple scripts:
#!/bin/bash
if [ -z "$1" ]; then
echo -e "\nPlease call '$0 <argument>' to run this command!\n"
exit 1
fi
More modern
#!/usr/bin/env bash
if [[ $# -gt 0 ]]
then echo Arguments were provided.
else echo No arguments were provided.
fi
If you'd like to check if the argument exists, you can check if the # of arguments is greater than or equal to your target argument number.
The following script demonstrates how this works
test.sh
#!/usr/bin/env bash
if [ $# -ge 3 ]
then
echo script has at least 3 arguments
fi
produces the following output
$ ./test.sh
~
$ ./test.sh 1
~
$ ./test.sh 1 2
~
$ ./test.sh 1 2 3
script has at least 3 arguments
$ ./test.sh 1 2 3 4
script has at least 3 arguments
As a small reminder, the numeric test operators in Bash only work on integers (-eq, -lt, -ge, etc.)
I like to ensure my $vars are ints by
var=$(( var + 0 ))
before I test them, just to defend against the "[: integer arg required" error.
one liner bash function validation
myFunction() {
: ${1?"forgot to supply an argument"}
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
add function name and usage
myFunction() {
: ${1?"forgot to supply an argument ${FUNCNAME[0]}() Usage: ${FUNCNAME[0]} some_integer"}
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
add validation to check if integer
to add additional validation, for example to check to see if the argument passed is an integer, modify the validation one liner to call a validation function:
: ${1?"forgot to supply an argument ${FUNCNAME[0]}() Usage: ${FUNCNAME[0]} some_integer"} && validateIntegers $1 || die "Must supply an integer!"
then, construct a validation function that validates the argument, returning 0 on success, 1 on failure and a die function that aborts script on failure
validateIntegers() {
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
return 1 # failure
fi
return 0 #success
}
die() { echo "$*" 1>&2 ; exit 1; }
Even simpler - just use set -u
set -u makes sure that every referenced variable is set when its used, so just set it and forget it
myFunction() {
set -u
if [ "$1" -gt "-1" ]; then
echo hi
fi
}
In my case (with 7 arguments) the only working solution is to check if the last argument exists:
if [[ "$7" == '' ]] ; then
echo "error"
exit
fi

bash if with or and negation

why does:
#!/bin/bash
wtf=false
if [ $wtf ] || [ ! -f filethatexists.whatever ]
then
echo "WTF1"
fi
if [ ! -f filethatexists.whatever ]
then
echo "WTF2"
fi
print:
WTF1
instead of nothing? It is especially perplexing that the second form works as expected and the first not.
The basic test
[ $wtf ]
tests whether the string in the middle is empty or not.
Since $wtf contains the string 'false', the test returns true, or exit status 0 for success, because 'false' is not the same as the empty string '' — and hence you get WTF1 as the response.
Try with:
wtf=''
As pointed out by Gordon Davisson (and Dennis Williamson), it is a good idea to be careful with strings that you are testing. Indeed, I should have stated that I would always use [ -n "$wtf" ] or [ -z "$wtf" ] to test whether a variable is set, because that was necessary when I was learning shell, once upon a quarter century ago. I've had counter stories from Bash afficionados that you don't have to worry about it in Bash - however, I think the code here provides a counter-example that in fact you do still have to worry about it.
So, some best practices:
Enclose tested variables in double quotes, or
(In Bash), use [[ $wtf ]] which does know how to handle the variable expansion.
Use the -n or -z tests to test for non-empty or empty values.
There can be exceptions to the rules - but you will not go far wrong following them.
Consider the code:
wtf="1 -eq 0"
[ $wtf ] && echo "WTF0"
[[ $wtf ]] && echo "WTF1"
wtf="false"
[ $wtf ] && echo "WTF2"
[[ $wtf ]] && echo "WTF3"
wtf=""
[ $wtf ] && echo "WTF4"
[[ $wtf ]] && echo "WTF5"
wtf="false"
[ "$wtf" ] && echo "WTF6"
[[ "$wtf" ]] && echo "WTF7"
wtf=""
[ "$wtf" ] && echo "WTF8"
[[ "$wtf" ]] && echo "WTF9"
That produces:
WTF1
WTF2
WTF3
WTF6
WTF7
with both bash and ksh (as found on MacOS X 10.6.4, when run with 'bash testcode.sh' or 'ksh testcode.sh'). A real Bourne shell (if you can still find such a thing) would object to the double-bracket operations - it would not be able to find the command '[[' on $PATH.
You can extend the testing to cover more cases ad nauseam.
Here's a handy little trick:
wtf=false
if $wtf || [ ! -f filethatexists.whatever ]
In this form, the contents of the variable are executed and the return value determines whether the test passes or fails. It happens that true and false are Bash builtins that return the appropriate value.
if [ $wtf = true ] || [ ! -f . .

Resources