EDIT: SOLVED. Thank you everyone!
I am building a shell script and I am stuck with comparing. I call it via ./myscript 1000, where 1000 is in a variable called y.
The problem is I need to make it so if I pass any 4 digit number to my script such as 0001 it will not be modified by my if statements, but when I call my script it is basically telling me 0001 and 1 are the same things. How can I check the size of $y to fix my problem?
I've tried echoing y, it does show 0001 and 1 when I pass them to my script but I do not know how to have any 4 digit number left alone.
####CODE SNIPP
#NOT EVEN NEEDED, JUST FOR SHOW, need to think of 0000 to any 4 digit #
#as different, but not sure how to do it.
if [ "$y" -ge 0000 ] && [ "$y" -le 9999 ]
then
#DO NOTHING
#IF Y <= 50 && Y >= 0
elif [ "$y" -le 50 ] && [ "$y" -ge 0 ]
then
#DO SOMETHING
#IF Y > 50 && Y <= 99
elif [ "$y" -gt 50 ] && [ "$y" -le 99 ]
then
#DO SOMETHING
fi
Does anyone have any tips on how I can tell me script 0001 and 1 are two different things? I figure bash must have something to check the input length or something but I cannot find anything.
Bash supports finding the length of a string stored in a variable.
A=hello
echo ${#A} # outputs "5"
You can use this within an if statement:
A=0001
if [ ${#A} -ne 4 ]; then
# stuff done if not four digits
fi
Note that bash will treat any arguments to -eq, -ne, -lt, -le, -gt or -ge as numbers.
If you want to treat them as a string, use = and !=
$ test 1 = 2; echo $?
1
$ test 1 = 1; echo $?
0
$ test 1 = 001; echo $?
1
$
Note how 1 and 001 are considered distinct. May this help you on your way.
If you really want to know long something is, try using wc?
$ echo -n abc | wc -c
3
$ y=0001
$ echo -n $y | wc -c
4
$ test `echo -n $y | wc -c` -eq 4; echo $?
0
$ y=1
$ test `echo -n $y | wc -c` -eq 4; echo $?
1
$
The last case returns 1 informing us that $y is not 4 characters long.
I'd identify the 4-digit numbers using case:
case $y in
([0-9][0-9][0-9][0-9])
is_4_digits=yes;;
(*) is_4_digits=no;;
esac
What else you do depends on your requirements - it could be that you do everything in the '(*)' clause; I'd use ': OK' in the 4-digit case to indicate that this case is OK:
case $y in
([0-9][0-9][0-9][0-9]) : OK;;
(*) # ... do all the other work here ...
;;
esac
Related
I have a slight problem with my BASH script that I do not know the cause.
Please take a look at this simple script.
#!/bin/bash
# Given two integers X and Y. find their sum, difference, product, and quotient
# Constraints
# -100 <= X,Y <= 100
# Y != 0
# Output format
# Four lines containing the sum (X+Y), difference (X-Y), product (X x Y), and the quotient (X // Y) respectively.
# (While computing the quotient print only the integer part)
# Read data
echo "Please input for x and y!"
read x
read y
declare -i MIN=-100
declare -i MAX=100
# Checks if the valued read is in the constraints
if [ $x -gt $MAX ] || [ $x -lt $MIN ];
then
echo "Error!"
exit 1;
elif [ $y -gt $MAX ] || [$y -lt $MIN ] || [$y -eq 0];
then
echo "Error"
exit 1;
else
for operator in {"+","-","*","/",}; do echo "$x $operator $y" | bc; done
fi
The output of the script above is as follow.
Please input for x and y!
1
2
worldOfNumbers.sh: line 26: [2: command not found
worldOfNumbers.sh: line 26: [2: command not found
3
-1
2
0
As you can see, there is this [2: command not found. I believe there is something wrong with my syntax however I feel like I have typed the right one.
p.s. I use Oh My ZSH to run the program. I've also tried running in VS Code however the same thing arise.
Thank you for the help.
If you put your code through shellcheck, you'll see that it is due to the lack of spaces between your variable and your bracket. Bash is a space oriented language, and [ and ] are commands just like echo or printf.
Change
elif [ $y -gt $MAX ] || [$y -lt $MIN ] || [$y -eq 0];
to
elif [ $y -gt $MAX ] || [ $y -lt $MIN ] || [ $y -eq 0 ];
^ ^ ^
Arrows added to show where spaces were added.
You can see that [ is a command if you run this at your bash prompt:
$ which [
[: shell built-in command
Because you do not have a space between [ and 2, bash assumes that you are trying to run a command called [2 and that command does not exist, as seen by your error message.
I seemed to fix it. Here is the code.
#!/bin/bash
# Given two integers X and Y. find their sum, difference, product, and quotient
# Constraints
# -100 <= X,Y <= 100
# Y != 0
# Output format
# Four lines containing the sum (X+Y), difference (X-Y), product (X x Y), and the quotient (X // Y) respectively.
# (While computing the quotient print only the integer part)
# Read data
echo "Please input for x and y!"
read x
read y
declare -i MIN=-100
declare -i MAX=100
# Checks if the valued read is in the constraints
if [ "$x" -gt $MAX ] || [ "$x" -lt $MIN ]
then
echo "Error!"
exit 1
elif [ "$y" -gt $MAX ] || [ "$y" -lt $MIN ] || [ "$y" -eq 0 ]
then
echo "Error"
exit 1
else
for operator in {"+","-","*","/",}; do echo "$x $operator $y" | bc; done
fi
There should be a space inside the [ expression ]. Pretty interesting!
I'm implementing a merge sort algorithm in bash, but looks like it loops forever and gives error on m1 and m2 subarrays. It's a bit hard to stop loop in conditions since I have to use echo and not return. Anyone have any idea why this happens?
MergeSort (){
local a=("$#")
if [ ${#a[#]} -eq 1 ]
then
echo ${a[#]}
elif [ ${#a[#]} -eq 2 ]
then
if [ ${a[0]} -gt ${a[1]} ]
then
local t=(${a[0]} ${a[1]})
echo ${t[#]}
else
echo ${a[#]}
fi
else
local p=($(( ${#a[#]} / 2 )))
local m1=$(MergeSort "${a[#]::p}")
local m2=$(MergeSort "${a[#]:p}")
local ret=()
while true
do
if [ "${#m1[#]}" > 0 ] && [ "${#m2[#]}" > 0 ]
then
if [ ${m1[0]} <= ${m2[0]} ]
then
ret+=(${m1[0]})
m1=${m1[#]:1}
else
ret+=(${m2[0]})
m2=${m2[#]:1}
fi
elif [ ${#m1[#]} > 0 ]
then
ret+=(${ret[#]} ${m1[#]})
unset m1
elif [ ${#m2[#]} > 0 ]
then
ret+=(${ret[#]} ${m2[#]})
unset m2
else
break
fi
done
fi
echo ${ret[#]}
}
a=(6 5 6 4 2)
b=$(MergeSort "${a[#]}")
echo ${b[#]}
There are multiple issues in your shell script:
you should use -gt instead of > for numeric comparisons on array lengths
<= is not a supported string comparison operator. You should use < and quote it as '<', or better use '>' and transpose actions to preserve sort stability.
there is no need for local t, and your code does not swap the arguments. Just use echo ${a[1]} ${a[0]}
you must parse the result of recursive calls to MergeSort as arrays: local m1=($(MergeSort "${a[#]::p}"))
when popping initial elements from m1 and m2, you must reparse as arrays: m1=(${m1[#]:1})
instead of ret+=(${ret[#]} ${m1[#]}) you should just append the elements with ret+=(${m1[#]}) and instead of unset m1, you should break from the loop. As a matter of fact, if either array is empty you should just append the remaining elements from both arrays and break.
furthermore, the while true loop should be simplified as a while [ ${#m1[#]} -gt 0 ] && [ ${#m2[#]} -gt 0 ] loop followed by the tail handling.
the final echo ${ret[#]} should be moved inside the else branch of the last if
to handle embedded spaces, you should stringize all expansions but as the resulting array is expanded with echo embedded spaces that appear in the output are indistinguishable from word breaks. There is no easy workaround for this limitation.
Here is a modified version:
#!/bin/bash
MergeSort (){
local a=("$#")
if [ ${#a[#]} -eq 1 ]; then
echo "${a[#]}"
elif [ ${#a[#]} -eq 2 ]; then
if [ "${a[0]}" '>' "${a[1]}" ]; then
echo "${a[1]}" "${a[0]}"
else
echo "${a[#]}"
fi
else
local p=($(( ${#a[#]} / 2 )))
local m1=($(MergeSort "${a[#]::p}"))
local m2=($(MergeSort "${a[#]:p}"))
local ret=()
while [ ${#m1[#]} -gt 0 ] && [ ${#m2[#]} -gt 0 ]; do
if [ "${m1[0]}" '>' "${m2[0]}" ]; then
ret+=("${m2[0]}")
m2=("${m2[#]:1}")
else
ret+=("${m1[0]}")
m1=("${m1[#]:1}")
fi
done
echo "${ret[#]}" "${m1[#]}" "${m2[#]}"
fi
}
a=(6 5 6 4 2 a c b c aa 00 0 000)
b=($(MergeSort "${a[#]}"))
echo "${b[#]}"
Output: 0 00 000 2 4 5 6 6 a aa b c c
I have tried two methods but it did not help:
Method 1
port=$1
if [[ $port -eq 3000 -o 3100 -o 3200 -o 3300 -o 3400 -o 3500 \
-o 3600 -o 3700 -o 3800 -o 3900]];then
echo "Yes"
else
echo "No"
fi
Method 2
port=$1
if [[ $port -eq 3[0-9]00 ]];then
echo "Yes"
else
echo "No"
fi
correct answer would be replacing =~ with == because otherwise entering the number 3400000, the output would be "ok".
if [[ $a == 3[0-9]00 ]];then echo "ok";else echo "not ok";fi
Just change it to a regex compare:
[[ $port =~ ^3[0-9]00$ ]]
POSIX. You can switch to case that will recognize your initial format:
case $a in
3[0-9]00) echo "ok";;
*) echo "not ok";;
esac
bash only:
Actually the format 3[0-9]00 is a regex format, and you can make it
work even with if by replacing -eq which is used for integer
comparison to =~ which is used for regex matching:
if [[ $a =~ 3[0-9]00 ]];then echo "ok";else echo "not ok";fi
Num-utils based answer, using numgrep, (and numrange), which prints found numbers to stdout and returns an exit code.
First look for a number that isn't there:
seq 2957 19 4000 |
numgrep /`numrange -n, '/3000..3900i100/'`/ && a=y || a=n ; echo $a
Output:
n
...and now find a number that is:
seq 2957 7 4000 |
numgrep /`numrange -n, '/3000..3900i100/'`/ && a=y || a=n ; echo $a
Output:
3300
y
The (( ... )) construct permits arithmetic expansion and evaluation. In its simplest form, a=$(( 5 + 3 )) would set a to 5 + 3, or 8. However, this double-parentheses construct is also a mechanism for allowing C-style manipulation of variables in Bash, for example, (( var++ )).
Information source: http://tldp.org/LDP/abs/html/dblparens.html
#!/bin/bash
foo=100
if (( foo >= 50 && foo <= 200 ));
then
echo "bar"
fi
I've been trying to figure out a way to test if a parameter is a number using the command expr.
In my script so far I've written:
expr $1 + 1 2>/dev/null
if [ $? -eq 2 -o $1 -lt 0 ]
then
echo "$1 is not a positive integer" >&2
exit 1
fi
What I'm trying to do here is to add 1 to the variable. If this provokes an error (which means that the variable isn't a number), the result will be sent to /dev/null.
After, I test if the return code is 2 or if the variable is less than 0.
However this doesn't seem to work... any ideas?
P.S. Please keep in mind that I must use expr
Your condition is evaluated whatever the value of $1, it means that if $1 is not a number, the shell will throw a syntax error.
I first check if expr fails (don't pre-suppose about the error code, just test for non-zero). If it does, I set the error flag.
Then if the error flag is not set, I know that this is a number, then I check for positive (the error flag protects from evaluating $1 with a non-integer.
Not sure this is the best way but works:
error=0
expr $1 + 1 2>/dev/null >/dev/null
if [ $? -ne 0 ] ; then
error=1
fi
if [ $error -eq 0 ] ; then
if [ $1 -lt 0 ] ; then
error=1
fi
fi
if [ $error -eq 1 ] ; then
echo "$1 is not a positive integer" >&2
exit 1
fi
test:
$ exprtest.sh foo
foo is not a positive integer
$ exprtest.sh 4
$ exprtest.sh -5
-5 is not a positive integer
Note: 0 passes the test, although some may object that it's not strictly a positive integer.
you can also try this;
#!/bin/bash
expr $1 + 1 2>/dev/null >/dev/null
if [ $? -eq 2 ] || [ $1 -lt 0 ]
then
echo "$1 is not a positive integer" >&2
exit 1
fi
Eg:
user#host:/tmp/test$ ./test.sh foo
foo is not a positive integer
user#host:/tmp/test$ ./test.sh 4
user#host:/tmp/test$ ./test.sh -5
-5 is not a positive integer
I'm trying to figure out how to convert the following for loop into a while loop or until loop including if fi for UNIX Here's the code...
#!/bin/bash
if [ $# -gt 0 ] ; then
start=$1
end=$3
step=$2
for x in `seq $start $step $end` ; do
number $x
done
else
echo Enter at least one number on the command line.
fi
Any help is greatly appreciated!
(( x = start ))
until (( x > end ))
do
number $x
(( x += step ))
done
For a loop like this, it's easiest to use the numeric-style for loop:
for ((x=start; x<=end; x+=step)); do
number $x
done
Using seq is good as it has some default values for step and end value. I added this to the if if only one, or two arguments are given. Also it handles the situation if the given argument are not numbers (default values are 0). Also an endless loop may encounter if step is zero. If You remove this checking it will work the same way as the seq version.
if [ $# -gt 0 ] ; then
x=$(($1+0))
end=$((${3:-$x}+0))
step=$((${2:-1}+0))
[ $step -eq 0 ] && echo "Step is zero!" && exit 1
while [ $x -le $end -a $step -gt 0 -o $x -ge $end -a $step -lt 0 ]; do
echo $x
x=$((x+step))
done
else
echo Enter at least one number on the command line.
fi
Change echo to number in the while loop if you would like to use!
Examples:
$ ./e.sh 1
1
$ seq 1
1
$ ./e.sh 1 1 3
1
2
3
$ seq 1 1 3
1
2
3
$ ./e.sh 1 -1 3
$ seq 1 -1 3
$ ./e.sh 3 -1 1
3
2
1
$ seq 3 -1 1
3
2
1
$ ./e.sh 1 0 1
Step is zero!
$ seq 1 0 1|head
1
1
1
... (endlessly)
$ ./e.sh 1 .1 1.2
./e.sh: line 3: 1.2+0: syntax error: invalid arithmetic operator (error token is ".2+0")
$ seq 1 .1 1.2
1.0
1.1
1.2
Bash arithmetic is not working with floating point numbers, but seq does. If You need this feature You could use the x=$(echo $x + $step|bc) format. Also $((...)) should replaced by $(...|bc) like lines.
In Bash 4 you can also do it like this:
for i in {$start..$stop..$step}; do
echo $i
done