Shell variable value in pre-defined range - linux

I want a variable whose value should be in already defined range.
for e.g.
variable DAY=$1
should only get values Mon,Tue,Wed,Thus,Fri,Sat,Sun.
if user enter something else, it will not accept it.
I know i can do this by defining array or normally a variable storing all the days and then check variable DAY in a "for or while" loop.
But i want the simplest way without loop.
Thanks in advance.

Use the extended glob support to match exactly one of a list of choices.
range="Mon|Tue|Wed|Thu|Fri"
read day
if [[ $day = #($range) ]]; then
echo "$day is valid"
else
echo "$day is not valid"
fi

Without using a loop you can do something like this:
EOL=$'\n'
arr=(Mon Tue Wed Thus Fri Sat Sun)
read -p 'Enter day value: ' day
Tue
[[ $(printf "$EOL%s$EOL" "${arr[#]}") == *"$EOL$day$EOL"* ]] &&
echo "in range" || echo "not in range"
in range
read -p 'Enter day value: ' day
ue We
[[ $(printf "$EOL%s$EOL" "${arr[#]}") == *"$EOL$day$EOL"* ]] &&
echo "in range" || echo "not in range"
not in range

There's really no need for an array.
read -r day
case ' Mon Tue Wed Thu Fri Sat Sun ' in
*" $day "*)
echo "in range";;
*) echo "not in range";;
esac
Notice the spaces around the string expressions.
If you need to be strict, you should reject "Tue Wed" which this doesn't do. It would require a second case wrapper, which is clunky but not completely unwieldy.

To expand slightly on anubhava's answer:
arr=(Mon Tue Wed Thus Fri Sat Sun)
read -p 'Enter day value: ' day
case "${arr[#]}" in *"$day"*) echo "in range";; esac
Basically, this is building an array, turning it into a string, and doing a pattern match on it.
In the context of a case expression, there's no difference between "${arr[#]}" and "${arr[*]}"; I personally would stick with *, which makes it clearer that you're building one big string instead of a list of separate ones.
With this specific set of values, you don't have to worry about substring overlap, but the above check is still overly forgiving. For instance, if you enter u, it will be considered "in range". That may not be what you want.
A more exact test would be something like this:
case "${arr[*]}" in
"$day "*|*" $day "*|*" $day") echo "in range";;
*) echo "not in range";;
esac
This still permits the user to enter multiple days as "Mon Tue" or similar; an easy way to fix that in this particular case is to change the read line from read day to read day _.
The "$day "* pattern will match at the beginning of the array (so if they enter Mon in this case). The *" $day" pattern will match at the end of the array (Fri), and the *" $day "* pattern will match the rest. Nothing else but an exact day string will match.
Also, here you can see how to handle the else case - a pattern of * matches anything that hasn't already matched something else, so that's the equivalent of else in a case.

Related

Add value to array only if not already in, using bash

As I said the title. I tried this code:
areasArray=()
while IFS= read -r line
do
areaName="$(awk -F ";" '{print $3}')"
echo $areaName
if [[ ! " ${areasArray[#]} " =~ " $areaName " ]]; then
areasArray+=($areaName)
echo ${areasArray[*]}
fi
done < $reportFile
$reportFile refers to a CSV file that looks like this:
something;something;US
something;something;US
something;something;UK
something;something;FR
something;something;UK
something;something;FR
And the array will looks like this: US US UK FR UK FR. But I want every zone to be added only if it's not already there. So it should looks like this: US UK FR. How can I do that? Thanks.
If you need to perform lookup, use an associative array, not an indexed array. Basically, you'll use the keys of the associate array like a set.
declare -A areasArray
while IFS=";" read _ _ areaName _; do
if [[ ! -v areasArray[$areaName] ]]; then
areasArray[$areaName]=
fi
done < "$reportFile"
Unless there is a specific action you want to take only if the key isn't already present, you can skip the if; areasArray[$areaName]= is idempotent.

Compare the days of the week in unix scripting

I want to compare the current day of the week and execute a set of statements depending on day.
to_day=$(date +%a)
if[ "$to_day" = "Sun" ]
then
echo "Today is sunday"
echo "First day of the week"
elif[ "$to_day" = "Mon" ]
then
echo "Today is monday"
echo "Second day of the week"
and so on...
I have tried the below if formats
if[$to_day = "Tue"]
if["$to_day" -eq "Tue"]
if["$to_day" == 'Tue']
if["$to_day" = 'Tue']
But the error is still present and it reads "if[ Tue = Tue ]:Command not fount ". I have tried the above with spaces after the braces also.
Make sure in your first line is:
#!/bin/bash
and if it is in your code then make sure if: /bin and /usr/bin or /usr/local/bin directories are in you path, because all commands are here. You can check with:
$ echo $PATH
and you will get a similar answer:
/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11R6/bin:/usr/local/bin:/home/vivekgite/bin
but if you don't have those paths, you can add theirs with:
$ export PATH=$PATH:/bin:/usr/local/bin

0999: Value too great for base (error token is "0999")

This is a shortened-version of a script for reading 8mm tapes from a EXB-8500 with an autoloader (only 10 tapes at a time maximum) attached. It dd's in tape data (straight binary) and saves it to files that are named after the tape's 4-digit number (exmaple D1002.dat) in both our main storage and our backup. During this time it's logging info and displaying its status in the terminal so we can see how far along it is.
#!/bin/bash
echo "Please enter number of tapes: [int]"
read i
j=1
until [ $i -lt $j ]
do
echo "What is the number of tape $j ?"
read Tape_$j
(( j += 1 ))
done
echo "Load tapes into the tower and press return when the drive is ready"
read a
j=1
until [ $i -lt $j ]
do
k="Tape_$j"
echo "tower1 $j D$(($k)) `date` Begin"
BEG=$j" "D$(($k))" "`date`" ""Begin"
echo "tower1 $j D$(($k)) `date` End"
END=$j" "D$(($k))" "`date`" ""End"
echo "$BEG $END"
echo "$BEG $END"
sleep 2
(( j += 1 ))
done
echo "tower1 done"
Everything was hunky-dory until we got under 1000 (startig at 0999). Error code was ./tower1: 0999: Value too great for base (error token is "0999"). Now I already realize that this is because the script is forcing octal values when I type in the leading 0, and I know I should insert a 10# somewhere in the script, but the question is: Where?
Also is there a way for me to just define Tape_$j as a string? I feel like that would clear up a lot of these problems
To get the error, run the script, define however many tapes you want (at least one, lol), and insert a leading 0 into the name of the tape
EXAMPLE:
./test
Please enter number of tapes: [int]
1
What is the number of tape 1?
0999
./test: 0999: Value too great for base (error token is "0999")
You don't want to use $k as a number, but as a string. You used the numeric expression to evaluate a variable value as a variable name. That's very bad practice.
Fortunately, you can use variable indirection in bash to achieve your goal. No numbers involved, no error thrown.
echo "tower1 $j ${!k} `date` Begin"
BEG=$j" "D${!k}" "`date`" ""Begin"
And similarly in other places.

Check and modify format of variable in expect script

I am trying to verify that the format of a variable is a number and is at least 10 digits long with leading zeros, inside of an expect script.
In a bash script it would look something like this:
[[ "$var" != +([0-9]) ]] && echo "bad input" && exit
while [[ $(echo -n ${var} | wc -c) -lt 10 ]] ; do var="0${var}" ; done
For the following input:
16
I am trying to achieve the following output:
0000000016
The simplest way to check whether a variable has just digits is to use a regular expression. Expect's regular expressions are entirely up to the task:
if {![regexp {^\d+$} $var]} {
puts "bad input"
exit
}
Padding with zeroes is best done by formatting the value; if you know C's printf(), you'll recognize the format:
set var [format "%010d" $var]
Expect is actually just an extension of TCL, so you can use any facility that TCL provides. TCL is an unusual language, but it's not hard to do what you want.
# Set a test string.
set testvar 1234567890
# Store the match (if any) in matchvar.
regexp {\d{10,}} $testvar matchvar
puts $matchvar
# Test that matchvar holds an integer.
string is integer $matchvar
The string is command is relatively new, so you might have to rely on the return value of regexp if your TCL interpreter doesn't support it.

Linux bash: Multiple variable assignment

Does exist in linux bash something similar to the following code in PHP:
list($var1, $var2, $var3) = function_that_returns_a_three_element_array() ;
i.e. you assign in one sentence a corresponding value to 3 different variables.
Let's say I have the bash function myBashFuntion that writes to stdout the string "qwert asdfg zxcvb".
Is it possible to do something like:
(var1 var2 var3) = ( `myBashFuntion param1 param2` )
The part at the left of the equal sign is not valid syntax of course. I'm just trying to explain what I'm asking for.
What does work, though, is the following:
array = ( `myBashFuntion param1 param2` )
echo ${array[0]} ${array[1]} ${array[2]}
But an indexed array is not as descriptive as plain variable names.
However, I could just do:
var1 = ${array[0]} ; var2 = ${array[1]} ; var3 = ${array[2]}
But those are 3 more statements that I'd prefer to avoid.
I'm just looking for a shortcut syntax. Is it possible?
First thing that comes into my mind:
read -r a b c <<<$(echo 1 2 3) ; echo "$a|$b|$c"
output is, unsurprisingly
1|2|3
I wanted to assign the values to an array. So, extending Michael Krelin's approach, I did:
read a[{1..3}] <<< $(echo 2 4 6); echo "${a[1]}|${a[2]}|${a[3]}"
which yields:
2|4|6
as expected.
I think this might help...
In order to break down user inputted dates (mm/dd/yyyy) in my scripts, I store the day, month, and year into an array, and then put the values into separate variables as follows:
DATE_ARRAY=(`echo $2 | sed -e 's/\// /g'`)
MONTH=(`echo ${DATE_ARRAY[0]}`)
DAY=(`echo ${DATE_ARRAY[1]}`)
YEAR=(`echo ${DATE_ARRAY[2]}`)
Sometimes you have to do something funky. Let's say you want to read from a command (the date example by SDGuero for example) but you want to avoid multiple forks.
read month day year << DATE_COMMAND
$(date "+%m %d %Y")
DATE_COMMAND
echo $month $day $year
You could also pipe into the read command, but then you'd have to use the variables within a subshell:
day=n/a; month=n/a; year=n/a
date "+%d %m %Y" | { read day month year ; echo $day $month $year; }
echo $day $month $year
results in...
13 08 2013
n/a n/a n/a
Chapter 5 of the Bash Cookbook by O'Reilly, discusses (at some length) the reasons for the requirement in a variable assignment that there be no spaces around the '=' sign
MYVAR="something"
The explanation has something to do with distinguishing between the name of a command and a variable (where '=' may be a valid argument).
This all seems a little like justifying after the event, but in any case there is no mention of a method of assigning to a list of variables.
let var1=var2=var3=0
or
var1=var2=var3="Default value"

Resources