Unix Scripting - Finding Minimum and Maximum (Bash Shell) - bash

My code below is part of an assignment, but I'm racking my head against the desk not understanding why it won't assign a "MIN" value. I tried assigning the MIN and MAX to ${LIST[0]} just to have the first index in place, but it returns the whole array, which doesn't make sense to me. I'm executing this on a CentOS VM (which I can't see making a difference). I know the beginning of the first and second "if" statements need better logic, but I'm more concerned on the MIN and MAX outputs.
#!/bin/bash
LIST=()
read -p "Enter a set of numbers. " LIST
MIN=
MAX=
if [ ${#LIST[*]} == 0 ]; then echo "More numbers are needed."; fi
if [ ${#LIST[#]} -gt 0 ]; then
for i in ${LIST[#]}; do
if [[ $i -gt $MAX ]]; then
MAX=$i
fi
if [[ $i -lt $MIN ]]; then
MIN=$i
fi
done
echo Max is: $MAX.
echo Min is: $MIN.
fi

The code is almost functional.
Since $LIST is an array, not a variable, change read -p "Enter a set of numbers. " LIST to:
read -p "Enter a set of numbers. " -a LIST
Move the $MIN and $MAX init code down 5 lines, (just before the for loop):
MIN=
MAX=
...and change it to:
MIN=${LIST[0]}
MAX=$MIN
And it'll work. Test:
echo 3 5 6 | ./minmax.sh
Output:
Max is: 6.
Min is: 3.

Related

I am trying to write a bash script that displays 10 different random numbers, so far some of the numbers that I have are duplicated

I think there is something wrong with the condition and the array.
this is my script
thank you for your time I appreciate.
#!/bin/bash
loop=10
range=20
count=1
declare -a prev
numb=$[1+RANDOM% $range]
prev+=($numb)
echo ===========================
echo $loop DIFFERENT RANDOM NUMBERS
echo ===========================
echo $numb
until [ "$count" -ge "$loop" ]
do
numb=$[1+RANDOM% $range]
if [[ ${prev[#]} -ne $numb ]] ; then
echo $numb
prev+=$numb
((count++))
fi
done
The code attempt to locate previously selected numbers by using the conditions [[ ${prev[#]} -ne $numb ]]. However, bash does not have "in" (or "not in") operators that work on an array and a value.
Consider instead using bash associative arrays. Each used elements is marked by entering a value into the position associated with the selected number
#! /bin/bash
loop=10
range=20
# Associative array prev[N]=1, if N was already printed
declare -A prev
echo ===========================
echo $loop DIFFERENT RANDOM NUMBERS
echo ===========================
for ((count=1 ; count <= loop ; count++)) ; do
numb=$[1+RANDOM% $range]
while [ "${prev[$numb]}" ] ; do
numb=$[1+RANDOM% $range]
done ;
echo $numb
prev[$numb]=1
done
Code using traditional for loop for (( ; ; )) to force loop to run specific number of times.
A quick version using an array -
$: loop=10 min=20 range=20 all=( $( seq $min $((min+range)) ) )
$: while (( loop-- ))
do ndx=$((RANDOM%range))
if (( all[ndx] ))
then echo "${all[ndx]}"
unset "all[ndx]"
else let loop++
fi
done
33
38
27
23
39
32
22
20
36
35
Unsetting each element as used prevents dups.
I'm pretty sure there's a better way... still thinking.

shell - iterating a loop for a (validated) supplied number or range of numbers

I need to accept input from user (i.e. 'read').
This input can be either a single positive number or a range of numbers (in the form X-Y ).
I then need to validate this input and perform an iterative loop through the range of numbers (or just once in the case of only X).
examples:
1) User supplies: "8" or "8-"
Loop runs only a single time supplying the number 8
2) User supplies: "13-22"
Loop runs 11 times (13 through 22) referencing the number 13.
3) User supplies: "22-13"
Probably should behave like #2 above...but I'm open to other clean ways to validate the input.
I have worked on the following so far, which isn't very clean, complete, or even 100% correct for what I was asking - but it shows the effort and idea I'm going for:
echo "line?"; read -r deleteline
case "$deleteline" in
''|*[!0-9\-]*) echo "not a number";;
[1-9]|[1-9][0-9]*);;
esac
deleteline_lb=$(echo $deleteline|awk -F "-" '{print $1}')
deleteline_ub=$(echo $deleteline|awk -F "-" '{print $2}')
if [ ! $deleteline_lb = "" ] && [ ! "$deleteline_ub" = "" ]; then
delete_line_count=1
delete_line_count=$(expr $deleteline_ub - $deleteline_lb)
if [ $delete_line_count -le 0 ]; then
delete_line_count=1
fi
fi
i=1; while [ $i -le $delete_line_count ]; do
echo $deleteline_lb $i
i=$(($i + 1))
done
This needs to run in sh, things like seq are not supported - so stick with posix compliant methods...
To clarify I am looking to do the following (pseudo-code):
1) accept input from user
2) validate if input is in the form "#" or "#-#" (range).
3) Execute chosen (arbitrary) code path based on proper/improper input.
4) If single # is given then store that to variable to perform future operations against.
5) If range is given, store both numbers in variable to be able to perform the operation against the lower # up to the higher number. More specifically it would be "(higher #) - (lower #) + 1". So if range were 12-17 then we need to perform operation against 12, 6x. (17 - 12 + 1). IOW, 12-17 inclusive.
6) A way to easily denote if data set is range vs single number is also desired so that code path to each can be easily branched.
thanks for helping!
UPDATE:
Using my basic code I reworked it (with a bit of input from a friend), and basically came up with this:
while true;do
printf "\\delete lines? [e=Exit] ";read -r deleteline
case "$deleteline" in
[Ee]) break;;
*)
echo "$deleteline" | egrep -q '^[[:digit:]-]*$'
if [ $? -ne 0 ]; then
printf "\\n input is not a number.\\n"
else
delete_range_start=`echo $deleteline|awk -F "-" '{print $1}'`
delete_range_end=`echo $deleteline|awk -F "-" '{print $2}'`
if [ $delete_range_end -lt $delete_range_start ]; then
printf "\\n upper range must be higher than lower range.\\n"
else
if [ "$delete_range_end" = "" ]; then
delete_range_end=$delete_range_start
elif [ $delete_range_end -gt $lineNumbers ]; then
printf "\\Setting range to last entry\\n"
fi
break
fi
fi
;;
esac
done
deleteline=$delete_range_start
deleteloop=`expr $delete_range_end - $delete_range_start + 1`
i=1
while [ $i -le $deleteloop ]; do
# Insert all processing code in here
i=`expr $i + 1`
done
If you have a posix compliant awk, try this:
echo "$userInput" | awk -F- '
($1+0==$1&&$2+0==$2){
for(i=$1;($1<$2?i<=$2:i>=$2);)
print ($1<$2?i++:i--);
next
}
$1+0==$1{
print $1;
next
}
$2+0==$2{
print $2;
next
}
($1+0!=$1&&$2+0!=$2){
exit 1
}'
The script check if the 2 fields (separated with -) are numbers. If so, it prints these numbers in an ascending or descending way depending if the first number is greater or lower than the second one.
If only one input, the script just prints it.
If none of the field are number, it exits with a non zero value.
This script could be the validation step of a shell script like this:
$ cat test.sh
#!/bin/sh
echo -n "range: "
read -r range
validated_input=$(echo "$range" | awk -F- '($1+0==$1&&$2+0==$2){for(i=$1;($1<$2?i<=$2:i>=$2);)print ($1<$2?i++:i--);next}$1+0==$1{print $1;next}$2+0==$2{print $2;next}($1+0!=$1&&$2+0!=$2){exit 1}')
if [ $? -ne 0 ]; then
echo "Incorrect range" >&2
fi
for i in $validated_input; do
echo "$i"
done
Examples:
$ ./test.sh
range: 10-6
10
9
8
7
6
$ ./test.sh
range: 8-
8
$ ./test.sh
range: hello
Incorrect range

bash - how to put $RANDOM into value?

newbie to bash:
basically I want to compare the result of $RANDOM to another value which is given by the user through 'read'
code for more info:
echo $RANDOM % 10 + 1 | bc
basically I want an if statement as well to see if the result of that $RANDOM value is equal to something that the user typed in e.g.:
if [ [$RANDOM VALUE] is same as $readinput
#readinput is the thing that was typed before
then
echo "well done you guessed it"
fi
something along the lines of that!!
to summarise
how do i make it so that i can compare a read input value to echo "$RANDOM % 10 + 1 | bc"
think of the program I am making as 'GUESS THE NUMBER!'
all help VERY MUCH APPRECIATED :)
There's no need for bc here -- since you're dealing in integers, native math will do.
printf 'Guess a number: '; read readinput
target=$(( (RANDOM % 10) + 1 )) ## or, less efficiently, target=$(bc <<<"$RANDOM % 10 + 1")
if [ "$readinput" = "$target" ]; then
echo "You correctly guessed $target"
else
echo "Sorry -- you guessed $readinput, but the real value is $target"
fi
The important thing, though, is the test command -- also named [.
test "$readinput" = "$target"
...is exactly the same as...
[ "$readinput" = "$target" ]
...which does the work of comparing two values and exiting with an exit status of 0 (which if will treat as true) should they match, or a nonzero exit status (which if will treat as false) otherwise.
The short answer is to use command substitution to store your randomly generated value, then ask the user for a guess, then compare the two. Here's a very simple example:
#/bin/bash
#Store the random number for comparison later using command substitution IE: $(command) or `command`
random=$(echo "$RANDOM % 10 + 1" | bc)
#Ask the user for their guess and store in variable user_guess
read -r -p "Enter your guess: " user_guess
#Compare the two numbers
if [ "$random" -eq "$user_guess" ]; then
echo "well done you guessed it"
else
echo "sorry, try again"
fi
Perhaps a more robust guessing program would be embedded in a loop so that it would keep asking the user until they got the correct answer. Also you should probably check that the user entered a whole number.
#!/bin/bash
keep_guessing=1
while [ "$keep_guessing" -eq 1 ]; do
#Ask the user for their guess and check that it is a whole number, if not start the loop over.
read -r -p "Enter your guess: " user_guess
[[ ! $user_guess =~ ^[0-9]+$ ]] && { echo "Please enter a number"; continue; }
#Store the random number for comparison later
random=$(echo "$RANDOM % 10 + 1" | bc)
#Compare the two numbers
if [ "$random" -eq "$user_guess" ]; then
echo "well done you guessed it"
keep_guessing=0
else
echo "sorry, try again"
fi
done

Why is this bash program going into an infinite loop?

#!/bin/bash
echo "Enter number of loops"
read count
echo $count
if [ $count -eq 0 ]
then
echo "The count cannot be zero. Enter a number again"
read count
fi
while [ $count -gt 0 ]
do
echo "Loop numner $count"
count = `expr $count - 1`
done
I am trying to simulate a Java counter in bash. Does this exist?
You have space in between your assignment statement as below:
count = `expr $count - 1`
^ ^
Remove the space between "=" like below:
count=`expr $count - 1`
Output
Enter number of loops
10
10
Loop numner 10
Loop numner 9
Loop numner 8
Loop numner 7
Loop numner 6
Loop numner 5
Loop numner 4
Loop numner 3
Loop numner 2
Loop numner 1
Note apart, backticks are discouraged and you should be using something like:
count=$(expr $count - 1)
Here's a rock solid rewriting of your script, to show you how it's usually done:
#!/bin/bash
while true; do
read -rep "Enter number of loops: " count
if [[ $count = +([[:digit:]]) ]]; then
((count=10#$count))
((count>0)) && break
printf 'The count cannot be zero. Enter a number again.\n'
else
printf 'Please enter a valid number.\n'
fi
done
while ((count>0)); do
printf 'Loop number %s\n' "$count"
((--count))
done
Using read with the -r flag to have backslashes not escape some characters (this should be the default), with the -e option so that read uses readline: it's more comfortable for the user, and with the -p option to specify the prompt.
I completely revisited the logic you're using to read user's input: read is run in an infinite loop that can only be broken when user enters a valid number. With your method, a user could enter invalid data twice, and the loop would have run with random arguments. Not good.
To check that user input is valid, I'm using pattern matching: [[ $count = +([[:digit:]]) ]] that is true if and only if count expands to a string of one or more digits, then I'm making sure that Bash will treat count in radix 10: in arithmetic context, 10#$count treats count in radix 10. Without this, an input like 08 or 09 would make some subsequent parts fail, as a leading zero means, for Bash, that the number should be interpreted in radix 8, hence 08 is not valid!
The final loop is written with Bash's arithmetic context ((...)). You don't need the external expr to perform simple arithmetic.
You can also use bash arithmetic expansion:
count="$((count -1))"
I would also suggest making the first test -le not -eq, in case the user types in a negative integer, and quote it in case the user types nothing at all.
if [ "$count" -le 0 ]
So your code would be:
#!/bin/bash
echo "Enter number of loops"
read count
echo $count
if [ "$count" -le 0 ]
then
echo "The count cannot be zero. Enter a number again"
read count
fi
while [ $count -gt 0 ]
do
echo "Loop numner $count"
count="$((count - 1))"
done

Count down with until

I get the issue when I give the user an option to enter a number, that number is going to be counted down to 1. I'm lost after the echo line, as the script echos the value entered but doesn't count it down to 1.
#!/bin/bash
COUNTER=100
until [ $COUNTER -lt 1 ]; do
read -p "Enter a number between 1-100: " COUNTER
echo COUNTER $COUNTER
let COUNTER-=1
done
Move the read out of the loop like this:
read -p "Enter a number between 1-100: " COUNTER
until [ $COUNTER -lt 1 ]; do
echo COUNTER $COUNTER
let COUNTER-=1
done
Try doing this using modern bash :
#!/bin/bash
read -p "Enter a number between 1-100: " counter
until ((counter < 1)); do
echo "counter $counter"
((counter--))
done
put read outside of the for loop to do it successfully.
avoid using UPPER CASE variables by default, there's reserved for global system variables.
((...)) is more intuitive than -gt, -lt... and is an arithmetic command, which returns an exit status of 0 if the expression is nonzero, or 1 if the expression is zero. Also used as a synonym for "let", if side effects (assignments) are needed. See http://mywiki.wooledge.org/ArithmeticExpression

Resources