I am trying some simple bash scripting, and I am working on a simple comparison script, where the program reads a character char, and if it is "Y" or "y", it prints "YES", and if it is "N" or "n", it is "NO". The letters Y, y, N, n are supposedly the only ones that will be used as input.
#!/bin/sh
read char
if (( $char = "Y" )) || (( $char = "y" ))
then
echo "YES"
else
echo "NO"
fi
The above script works with Y or y, but when I try to put N or n, it still prints "YES", and I'm trying to figure out what I am doing wrong. I know it's something to do with the parentheses, because when I use a syntax with brackets, like this
if [ "$char" == "Y" ] || [ "$char" == "y" ]
it works just fine.
I have tried all the combinations for the parentheses, like
if (( "$char" = "Y" )) || (( "$char" = "y" ))
or
if (( $char == "Y" )) || (( $char == "y" ))
or
if (( "$char" == "Y")) || (("$char" == "y" ))
or
if (( "$char" == "Y" || "$char" == "y" ))
but none of them work.
Can you please tell me what is the mistake I am making?
(( ... )) is for arithmetic only; you are performing string comparison. Use [[ instead. You can use pattern matching to check for either upper- or lowercase at the same time.
if [[ $char = [Yy] ]]; then
If you really want to use /bin/sh for portability, then use two [ commands:
if [ "$char" = Y ] || [ "$char" = y ]; then
Related
(Apologies in advance if my english is bad)
So, I have started studying bash, and want to make a Connect 4 game in it.
However, I got stuck a bit. I have 7 arrays named column0 to column6, and I want to reference them later on.
I read in another post a few days ago, that i can use nameref to do so, but I think I'm doing something wrong, as the function runs, but doesn't do anything (doesn't check for winner). Here is a snippet form the code
declare -n nr="column$j"
for ((j=0 ; j < 6; j++ )); do
if [ "${nr[0]}" = $k ] && [ "${nr[1]}" = $k ] && [ "${nr[2]}" = $k ] && [ "${nr[3]}" = $k ]; then
(this is part of the function that will check for a winner, in case 4 characters in a row match. 'k' is just a color variable, it can be $k='Y' or $k='R')
Could someone point out what I'm doing wrong here? Or am I just dumb and Bash can't work with such solutions?
Thank you in advance for any help.
Given your specifications I tried to reproduce a minimal example.
You can try to do something like this:
#!/usr/bin/env bash
k="Y"
nr_0=("R" "R" "Y" "Y")
nr_1=("R" "Y" "Y" "Y")
nr_2=("Y" "R" "Y" "Y")
nr_3=("Y" "Y" "R" "Y")
nr_4=("Y" "Y" "Y" "R")
nr_5=("Y" "Y" "Y" "Y") # the winner
nr_6=("Y" "R" "Y" "Y")
for j in {0..6}; do
tmp_array_name="nr_$j[#]"
tmp_array=("${!tmp_array_name}")
if [ "${tmp_array[0]}" == "$k" ] && [ "${tmp_array[1]}" == "$k" ] && [ "${tmp_array[2]}" == "$k" ] && [ "${tmp_array[3]}" == "$k" ]; then
echo "The winner is $tmp_array_name - ${tmp_array[*]}"
fi
done
With this output:
The winner is nr_5[#] - Y Y Y Y
You need the nameref declare assignment to be within your loop or it gets assigned an empty string:
for ((j=0 ; j < 6; j++ )); do
declare -n nr="column$j"
if [ "${nr[0]}" = $k ] && [ "${nr[1]}" = $k ] && [ "${nr[2]}" = $k ] && [ "${nr[3]}" = $k ]; then
Another option to nameref which is as risky as the dynamic naming ${!dyn_name_var}, is to use Bash's associative arrays:
declare -A grid=()
for ((column=0 ; j < 6; column++ )); do
if [ "${grid[$column,0]}" == "$k" ] &&
[ "${grid[$column,1]}" == "$k" ] &&
[ "${grid[$column,2]}" == "$k" ] &&
[ "${grid[$column,3]}" == "$k" ]
then
: ...
fi
: ...
done
This will use an associative arrays of key formed by column,line.
Example of the first two lines of the grid associative array as declared statically.
declare -A grid=(
[0,0]="o" [0,1]="x" [0,2]=" ", [0,3]="o"
[1,0]=" " [1,1]="o" [1,2]="x", [1,3]="o"
)
See: How to declare 2D array in bash
This question already has answers here:
How do I test if a variable is a number in Bash?
(40 answers)
Closed 2 years ago.
#!/bin/bash
if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ]
then
echo This is empty or does not have all 3 parameters
exit
elif [ "$1" -lt 0 ]
then
echo This aint a number
exit
fi
Trying to run a script it is suppose first check if 3 positional parameters were entered,
secondly check if the input are numbers and then display the largest. I got the first if statement to work but when I input a string for the first parameter to test the elif statement an error that says integer expected.
you have to review the regex to check if string only contains digits, but you may try this:
#!/bin/bash
if [[ "$1" == "" ]] || [[ "$2" == "" ]] || [[ "$3" == "" ]]
then
echo "This is empty or does not have all 3 parameters"
exit
elif ! [[ "$1" =~ ^[0-9]+$ ]]
then
echo "This aint a number"
exit
fi
Note that [[ is actually a command/program that returns either 0 (true) or 1 (false). Any program that obeys the same logic (like all base utils, such as grep(1) or ping(1)) can be used as condition :
[[ -z STRING ]] #Empty string
[[ -n STRING ]] #Not empty string
[[ STRING == STRING ]] #Equal
[[ STRING != STRING ]] #Not Equal
[[ NUM -eq NUM ]] #Equal
[[ NUM -ne NUM ]] #Not equal
[[ NUM -lt NUM ]] #Less than
[[ NUM -le NUM ]] #Less than or equal
[[ NUM -gt NUM ]] #Greater than
[[ NUM -ge NUM ]] #Greater than or equal
[[ STRING =~ STRING ]] #Regexp
(( NUM < NUM )) #Numeric conditions
also to check if the string contains only digits/numerical cheracter
[[ "$1" =~ ^[0-9]+$ ]]
Please use a regex to check for number
#!/bin/bash
if [ $1 == "" ] || [ $2 == "" ] || [ $3 == "" ]
then
echo This is empty or does not have all 3 parameters
exit
elif ! [[ $1 =~ ^[0-9]+$ ]]
then
echo This aint a number
exit
fi
I have this code:
fruit=apple
flag=0
[[ $fruit = "apple" && ((flag == 0)) ]] && echo "1"
[[ $fruit = "apple" && (($flag == 0)) ]] && echo "2"
[[ $fruit = "apple" && ((! flag)) ]] && echo "3"
[[ $fruit = "apple" && ((! $flag)) ]] && echo "4"
All of them are expected to echo something. However, only the second statement works properly:
[[ $fruit = "apple" && (($flag == 0)) ]] && echo "2"
Why is this? Won't arithmetic expressions work properly inside [[ ]]?
The == works the same as = - it is a string comparision.
The ( ) inside [[ ]] is used to nest expressions. Ex. [[ a = a && (b = b || c = d) ]]
A non empty string is true. So [[ some_string ]] or [[ "some other string" ]] returns true. [[ "" ]] or [[ '' ]] returns false.
The ! is negation.
[[ $fruit = "apple" && ((flag == 0)) ]]
First the expansion of $fruit happens. So it becomes: [[ "apple" = "apple" && ( ( "flag" == "0" ) ) ]]. The ( ) are just braces, this is not arithemtic expansion. The string "apple" is equal to "apple", but the string "flag" is not equal to string "0", so it always returns with false.
[[ $fruit = "apple" && (($flag == 0)) ]]
Because $flag is expanded, the [[ sees: [[ "apple" = "apple" && ((0 == 0)) ]]. So the comparision happens to work, however == is doing string comparision.
[[ $fruit = "apple" && ((! flag)) ]]
The flag is a nonempty string, so it evaulates to true. The ! flag evaulates to false.
[[ $fruit = "apple" && ((! $flag)) ]]
First $flag is expanded to 0. As 0 is a nonempty string, it evaluates to true. The ! 0 evaluates to false. The ((! 0)) is false, so the whole expression returns nonzero.
Won't arithmetic expressions work properly inside [[ ]]?
No, arithmetic expressions will not work inside [[ ]] the same way [[ echo 1 ]] does not work. The echo 1 ]] are arguments of [[ builtin, not a standalone command. The same way [[ (( 0 )) ]] the (( 0 )) ]] are interpreted as arguments to [[.
((...)) is an arithmetic command (or statement); the arithmetic expresson is $((...)). You want
[[ $fruit = "apple" ]] && ((flag == 0)) && echo 1
# etc.
[[ $fruit = apple && $((flag == 0)) ]] would fail because [[ ... ]] would simply treat the evaluation of the arithmetic expression as a non-empty string (which is always true) rather than a boolean value.
For completeness, you could use a single [[ ... ]] command:
[[ $fruit = apple && flag -eq 0 ]] && echo 1
# etc
I am trying to code a script that will tell the user if a triangle is isosceles, equilateral, or scalene. The error is occuring in line 7 (The elif line)
#!/bin/bash
read -p "Enter a number: " x
read -p "Enter a number: " y
read -p "Enter a number: " z
let "a = x + y + z"
if [ $x -eq $y ] && [ $y -eq $z ]
then echo "EQUILATERAL"
elif [[[ $x -eq $y ] && [ $y != $z ]] || [[ $x -eq $z ] && [ $z != $y ]] || [[ $y -eq $z ] && [ $z != $x ]]]
then echo "ISOSCELES"
elif [ $a -gt 1000 ]
then echo "Cannot equal more than 1000"
fi
I do realize that I could do the same thing with multiple elif lines, but I also have another elif as well and I want to keep it clean. Thanks all!
It seems like you think square brackets in the shell are like parentheses in C-style programming languages. That's not how they work. [ is a synonym for the test command, the condition it introduces ends with ]. And [[ is a special token that introduces a conditional expression, which ends with ]]. You can't mix them up, you can't add additional brackets like [[[, and they don't nest.
The grouping operators in the shell are { ... } and ( ... ); the latter also creates a subshell.
elif ( [[ $x -eq $y ]] && [[ $y != $z ]] ) || ( [[ $x -eq $z ]] && [[ $z != $y ]] ) || ( [[ $y -eq $z ]] && [[ $z != $x ]] )
In bash scripting the if condition statement is not working properly with using "&&"
ARGCOUNT=$#
if (( "$ARGCOUNT" != "2" )) ;then
echo "number of arguments must be two"
fi
DFLAG=$1
HFLAG=$2
if (((( $DFLAG = "Mon" )) || (( $DFLAG = "MON" )) || (( $DFLAG = "mon" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
echo " CS599 "
cd CS599
elif (((( $DFLAG = "Wed" )) || (( $DFLAG = "WED" )) || (( $DFLAG = "wed" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
cd CS699
echo " CS699 "
elif (((( $DFLAG = "Fri" )) || (( $DFLAG = "FRI" )) || (( $DFLAG = "fri" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
cd CS799
echo " CS799 "
else
echo "."
fi
my program is executing only else statement irrespective of arguments. means it evaluating if block false.
What is the problem ?
The parenthesis you use are for arithmetic evaluation. I think you are over using them, and it makes your script complicated.
This snippet below does work:
#!/bin/bash
ARGCOUNT=$#
if [ "$ARGCOUNT" -ne 2 ] ;then echo "number of arguments must be two"; fi
# put DFLAG in lower case (see man bash).
DFLAG=${1,,}
HFLAG=$2
if [ "$DFLAG" = 'mon' -a "$HFLAG" -ge 2 -a "$HFLAG" -le 4 ]; then
echo ok
else
echo failed
fi
As you can see, I optimized your expression:
Except for the case of $ARGCOUNT (which is safe because you initialized it to $#), don't forget to encase variable with double quote to avoid expansion.
In the declaration of DFLAG, I used the convert to lower case string operator (?). With that you won't have to check for each permutation of case in DFLAG. This might not work in bash3.
If you use the test or [ builtin, you can use -a between each expression to do a and.
Arithmetic evaluation with the test/[ builtin use the following operators: -ne (inequality), -eq (equality)-ge (greater or equal), -le (lesser or equals), -lt (lesser), -gt (greater).
As said in another answer, you can replace "$DFLAG" = 'mon' by "$DFLAG" == 'mon'. But this is not POSIX conformant (as said in my comment below) and I'm not enough knowledgeable on that to know if it's a good idea or not.
On a side note, if $HFLAG condition should always be the same, you can write your code like this:
if [ "$HFLAG" -ge 2 -a "$HFLAG" -le 4 ]; then
case "$DFLAG" in
mon|Mon|MON)
echo "monday";
;;
fry|Fry|FRY)
echo "friday";
;;
*)
echo "other"
;;
esac
fi
If that case, I putted back all permutation of case in case you were in bash3, to show you an example to do without ${DFLAG,}.
If you are looking for what mistake you did which is making condition to go to else part ...then it's simple mistake which almost every programmer do once in life ... using single "=" instead of "==" during comparison. Modify it accordingly and you should get expected flow/result in your script.
One eg.
$DFLAG = "Mon"
change to below notice the double equal sign
"$DFLAG" == "Mon"
First, you should go easy on the parenthesis. Those are fragile things.
Using Bash syntax (non-POSIX, less portable), you can write:
ARGCOUNT=$#
DFLAG=${1,,} # lower case
HFLAG=$2
# don't need the quotes in (( )) as we test arithmetic value
(( $ARGCOUNT != 2 )) && echo "number of arguments must be two"
shopt -s extglob # for #(1|2|3) below, see http://mywiki.wooledge.org/glob#extglob
if [[ $DFLAG = "mon" && $HFLAG == #(2|3|4) ]]; then
echo " CS599 "
cd CS599
if [[ $DFLAG = "wed" && $HFLAG == #(2|3|4) ]]; then
cd CS699
echo " CS699 "
if [[ $DFLAG = "fri" && $HFLAG == #(2|3|4) ]]; then
cd CS799
echo " CS799 "
else
echo "."
fi
Now you see how much you repeat yourself and can improve the algorithm. For instance, test HFLAG, if valid test DFLAG otherwise …
Read
Tests And Conditionals
Arithmetic Expression
Globbing extglob