Variable While Loop - linux

I would like to know how to include -, *, and ,/, in the following while loop in addition to the + I have already included. If the user enters something other than +, -, * or / I want the invalid input message to print. However, so far I have only worked out how to include one of the arguments in the code, in this case the +. How do I include the other 3 arguments in the same bit of code? I am a noobie I admit, and I don't currently have the vocabulary to search an answer specific to my needs so thought my best best was writing out the issue.
Any help appreciated. Thanks
echo "Please enter an operation of arithmetic. Press either +, -, * or /"
read operation
while [ $operation != "+" ]; do
echo "sorry, that is an invalid input- re-enter operation of arithmatic"
read operation

You can use that in a while loop like this:
while read -p "Please enter an operation of arithmetic. Press either +, -, * or /: " op &&
[[ $op != [-+/*] ]]; do
echo "sorry, that is an invalid input- re-enter operation of arithmatic"
done

You probably want select here:
PS3="Please enter an operation of arithmetic: "
select op in + - / '*'; do
case $op in
-) echo subtract something ; break ;;
+) echo add something ; break ;;
/) echo divide something ; break ;;
\*) echo multiply something ; break ;;
esac
done

Related

How do I collect arguments from the command-line? [duplicate]

This question already has answers here:
How do I parse command line arguments in Bash?
(40 answers)
Closed 3 years ago.
I am very new to shell scripting and using CLI all together. However, I want to create a simple program that can collect arguments from a user.
Optimally, I want my shell script to get a network name and instance name from the user, and assign the values to variables.
I know that $# can be used to gather arguments, but are there any other ways? I often notice when I make a command, there is, for example, something like this: $create --instance_name NAME . Can I gather an argument by using -- to specify the parameter? If so, here is an example of the command:
$collect_variable.sh --network NETWORK_NAME --instance_name INSTANCE_ID
Once again, thank you for any help. I am very new to stack overflow and unix all together, and any help is appreciated.
You can using a variable to assign to for example:
while getopts "x:y:z:" res
do
case "$res" in
x ) paramX="$OPTARG" ;;
y ) paramY="$OPTARG" ;;
z ) paramX="$OPTARG" ;;
? ) echo "not a valid value"
esac
done
Check the rest of the links that other StackO users sent, they provide great examples!
Hope that can help you solve your issue.
Peace!
There are lots of ways of gathering arguments. You can name their position, e.g. $1 is the first argument and $14 is the fourteenth.
You can refer to the argument list like $# (this preserves spacing) or like $* (this collapses spacing):
test_args() {
echo '# as "$#"'
for argument in "$#"; do
echo "$argument"
done
echo '# as "$*"'
for argument in "$*"; do
echo "$argument"
done
echo '# as $*'
for argument in $*; do
echo "$argument"
done
}
$ test_args "1 2" three four
# as "$#"
1 2
three
four
# as "$*"
1 2 three four
# as $*
1
2
three
four
Since you're exclusively using long options separated from their arguments by spaces,
while [ "$#" -gt 0 ]; do
case "$1" in
( --network ) NETWORK="$2"; shift ;;
( --network=?* ) NETWORK="${1#*=}" ;;
( --instance_name ) INSTANCE_NAME="$2"; shift ;;
( --instance_name=?* ) INSTANCE_NAME="${1#*=}" ;;
( -* ) echo "Illegal option -- $1" >&2; exit 2 ;;
( * ) break ;; # perhaps non-options are used later
esac
shift
done
This loops on each option and parses it. There are two conditions for each option, which lets us handle when the arguments are spaced from the options (--network bobnet) or when they're assigned to the options (--network=bobnet). Spaced means we need to refer to the next argument (thus the extra shift) while assigned means we need to use parameter subsitution to remove the characters at the front of the string up until (and including) the first =.
The final shift pulls the first item off of the argument list so we can operate on the next one. Shifting separately for the two-argument clauses (rather than ending with shift 2) also allows for binary options like --verbose, which doesn't contain an argument.
You can also use a while getopts loop to support a mix of short and long arguments; see my more detailed post on getopts.

How to update variable within case statement based on user input

I have created a bash script which is essentially a wizard with a number of questions, some are multiple choice. When a multiple choice question is presented, I want the user to be able to choose a number, where each number corresponds to a different answer. I want this answer to be the variable which can be later used in the script.
I realize this is what the 'select' function is used for, but I also have a requirement when the user simply hits [ENTER] a default value is assumed. As far as I know, the 'select' function assumes an empty value of "" when the [ENTER] key is pressed (an invalid option) and re-prompts the question.
The code below is me attempting to update a variable when a number is pressed. For example when the number '1' is pressed, I want the $hash variable to updated to 'sha224'.
Is there any to achieve this using a case statement without 'select'? If not what are my alternatives?
echo
echo "Select a hashing algorithm"
echo "1 - sha224"
echo "2 - sha256"
echo "3 - sha384"
echo "4 - sha512"
while true; do
read -p "Option: [sha256]:" -e hash
case $hash in
"") hash="sha256" break 2;;
1) hash="sha224" break 2;;
2) hash="sha256" break 2;;
3) hash="sha384" break 2;;
4) hash="sha512" break 2;;
*) echo "Invalid Response: Please enter [1-4] and hit [ENTER] or hit [ENTER] to select 'sha256'";;
esac
done
Insert ; before all break.
Replace $hash with ${hash:=2} to use a default value if $hash is empty.
"") hash="sha256" break 2;; can be removed.

Assigning variable to a variable inside if statement

I am trying to assign a variable from a prompt input choice with no luck. If the user inputs 1, I want target_db_name = "database2".
My code:
while true; do
read -p "What is the table name?" table_name
table_name=${table_name,,}
if hdfs dfs -test -e /foo/$table_name ;
then read -p "What is the target database you want to copy the
“foo.${table_name}” table to?
Your three options are:
1) database1
2) database2
3) database3
Type 1, 2, or 3: " target_db;
(((Here is where I want to state if $target_db = "1" then target_db_name
= "database1", if $target_db = "2" then target_db_name = "database2" etc...)))
read -p "Would you like to begin the HDFS copy with the following configuration:
Target Database: ${target_db_name}
Table Name: ${table_name}
Continue (Y/N):"
else echo "Please provide a valid table name.
Exiting this script" ; exit ; fi
done
I have tried creating another if statement with no luck.
"....Type 1, 2, or 3: " target_db;
else if $target_db = "1" then target_db_name = "edw_qa_history"; fi
if $target_db = "1" then won't work, because what follows if must be a command, not a test expression. Now, the most common command used in if statements is [ (yes, that's actually a command name; it's synonymous with the test command), which takes a test expression (and a close bracket) as its arguments and succeeds or fails depending on whether the expression is true or not. So the correct syntax would be something like:
if [ "$target_db" = "1" ]; then
Note that there are two other differences from what you had: I put double-quotes around the variable reference (almost always a good idea, to avoid may parsing oddities), and added a semicolon before then (needed to indicate where the arguments to [ end and shell syntax resumes). I also notice you have semicolons at the end of many lines of your script; this isn't necessary, the end-of-line is enough to indicate the end of a command. It's only if you have another command (or something like then) on the same line that you need a semicolon as a delimiter.
HOWEVER, as #Barmar pointed out in a comment, case would probably be better than a list of if and elif statements here. case is intended specifically for comparing a string against a list of other strings (or patterns), and executing different things depending on which one it matches. It looks something like this:
case "$target_db" in
1) target_db_name="database1" ;;
2) target_db_name="database2" ;;
3) target_db_name="database3" ;;
*) "Please provide a valid table name. Exiting this script" ; exit ;;
esac
Here, the double-semicolon is needed, even at the end of a line, to indicate the end of each case. Also, note that the * pattern (the last case) matches anything, so it functions like an else would in an if ... elif ... sequence.
Final note: use shellcheck.net to sanity-check your code.
You don't need an if statement to map the number to an array; you just need an array.
db_names=(
"datebase 1"
"database 2"
"database 3"
)
# ...
target_db_name=${db_names[$target_db - 1]}
if [[ -z $target_db_name ]]; then
exit
fi

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.

syntax error on linux division and multipication

I am trying to calculate 20% of a where a is input by the user.
echo "Please enter your basic salary"
read a
#HRA
b=`expr (20 / 100)\* $a)`|bc
echo HRA is:$b
What's wrong in this expression, which is generating an error message?
The first problem is that you want to just pass the expression into bc for evaluation, so use echo rather than expr.
Secondly, the order of evaluation you have is going to calculate 20/100 as an integer first, which will be 0, then multiple the salary by that, resulting in 0. Reordering the calculation will resolve that.
So the following should work:
echo "Please enter your basic salary"
read a
#HRA
b=`echo $a " * 20 / 100"|bc`
echo HRA is:$b
Alternatively, if you prefer to use expr rather than bc, you can escape the problem characters to make this work:
echo "Please enter your basic salary"
read a
#HRA
b=`expr $a \* 20 \/ 100`
echo HRA is:$b
You can use let operator also in bash
#!/bin/bash
echo "Please enter your basic salary"
read a
let b=$a*20/100
echo HRA is:$b

Resources