./test: line 22: syntax error near unexpected token `done' - bash

#!/bin/bash
# script to create new instance by taking inputs from user
source /root/keystonerc_admin
unset http_proxy
function main {
echo "Please choose:
1. Create instance from an image
2. Create instance from a volume
3. Exit"
While true do
read SELECT
case "$SELECT" in
1) SELECT=func_create_from_image;;
2) SELECT=func_create_from_volume;;
3) SELECT=exit;;
*) echo Invalid selection.; continue
esac
break
done
}
function func_create_from_image {
echo "List of flavors"
nova flavor-list
echo "Enter flavor id from the list"
read flavor_id
echo "List of images"
nova image-list
echo "Enter image id from the list"
read image_id
echo "List of security groups"
nova secgroup-list --all-tenants
echo "Enter security group name from the list"
read secgroup_name
echo "List of keypairs"
nova keypair-list
echo "Enter keypair name from the list"
read keypair_id
echo "Enter display name of instance"
read display_name
## use "set" for debugging
##set -x
nova boot --display $display_name --flavor $flavor_id --image $image_id \
--key_name $keypair_id --security_group $secgroup_name
##set +x
}
function func_create_from_volume {
echo "Please choose:
1. Use existing bootable volume
2. Create new bootable volume
3. Exit"
while true do
read SELECT
case "$SELECT" in
1) SELECT=create_instance_from__existing_volume;;
2) SELECT=create_instance_from_new_volume;;
3) SELECT=exit;;
*) echo Invalid selection.; continue
esac
break
done
}
create_instance_from_new_volume {
echo "Specify image id from the below list"
nova image-list
read image_id
echo "specify size in GB, only specify number"
read vol_size
echo "Specify image name to be displayed in cinder list"
read vol_disp_name
set -x
cinder create --image-id $image_id --display-name $vol_disp_name $vol_size
set +x
echo "volume created"
cinder list
echo "create instance"
create_instance_from__existing_volume
}
create_instance_from_existing_volume {
echo "Specify volume id from the below list"
cinder list
read vol_id
echo "Pick flavor id from the below list"
nova flavor-list
read flavor_id
echo "specify size in GB, only specify number or leave blank for system to decide"
read vol_size
echo "Specify type of volume either snap or other or leave blank if not known"
read vol_type
echo "Specify 1 if volume should be deleted when the instance terminates or specify 2 if volume remains after
instance terminates"
read vol_del
if [$vol_del!=0 or $vol_del!=1] then
echo "Specify either 1 or 0"
fi
echo "Specify display name for instance"
read inst_disp_name
echo "Creating instance"
nova boot --flavor $flavor_id --block_device_mapping vda=$vol_id:$vol_type:$vol_size:$vol_del $inst_disp_name
}

While true do
Bash is case-sensitive - you need while rather than While. And you also need to either move the do to the next line or put a semicolon before it
while true; do
Also your if condition further down is suspect:
if [$vol_del!=0 or $vol_del!=1] then
Aside from the syntax errors, this condition will always be true (since if the value is 1 then it is not 0 and vice versa). I suspect you wanted and rather than or:
if [ $vol_del != 0 -a $vol_del != 1 ]; then

Use
while true; do
instead of
While true do
because do isn't an argument to true command

In theory, do should be on a separate line:
while ....
do
....
done
Same with then:
if .....
then
.....
fi
However, people like putting them on the same line in the K&R Style. To do that, you need a semicolon to show the shell when the while line ends, so the do is on a different line:
while ....; do
....
done
This may no longer be necessary in newer versions of BASH, but it should be done. I prefer using an extra line just because the shell prefers it.
The other problem and the actual problem has to do with the While on line #13 as While (uppercase W and not lowercase w).
Two thing will help catch this in the future:
Use set -xv to turn on debugging and set +xv to turn off debugging. In this case, you'll see the error happens when you enter the while loop when Line #13 gets executed. You can set export PS4=\$LINENO+ to show line numbers when this debugging is on. To turn off debugging, you set +xv.
Use a program editor that has syntax highlighting. I saw this immediately because the While wasn't in the correct color. I took a closer look and saw the capitalized While.

Related

Create if statement to iterate over a list of names and create name if not in list?

I am working with an Azure shared image gallery and trying to write a bash if statement to iterate through the list of image definition names and if that image definition name is not there, create it elif, etc...
I have a variable set as:
defs=$(az sig image-definition list --resource-group $MyRG --gallery-name $mySIG --query [*].name) \
echo "$defs"
What I'm attempting to do is create an if statement that will iterate through this list of image definition names in my Azure compute gallery, and create a specified name if it does not exist.
My original assumption was something like if [$defs != x but not sure how to go about setting x, as it would be a user input for someone wanting to create a new definition.
Sorry if my question is unclear.
If there's more info I can provide please let me know.
The problem I'm facing is that I understand bash somewhat but not in conjunction with how exactly I am attempting to apply it to my Azure image definitions issue.
You can use read to ask a user for input.
$ read -p "please enter a definition: " x
please enter a definition: def4
$ echo $x
def4
Then, assuming $defs is a space separated list of definitions, could do something like this:
# list of defs..
defs="def1 def2 def3"
# ask user to enter a definition
read -p "please enter a definition: " x
# set this to false until def is found
def_exists=false
# iterate over list of defs and see if user value is found
for i in ${defs}; do
echo $i
if [[ ${i} == "${x}" ]]; then
echo "found it"
def_exists=true
fi
done
# if user def exists, good
if $def_exists; then
echo "definition exists already.. nothing to do"
# otherwise...
else
# do something
echo "$x not found.. do stuff.."
fi
Example where def is in existing list:
please enter a definition: def1
def1
found it
def2
def3
definition exists already.. nothing to do
Example where def is not in existing list:
please enter a definition: def4
def1
def2
def3
def4 not found.. do stuff..
This is what my defs variable holds, and what the commenter above was asking you to provide so we can see what type of data is being referenced.
$ typeset -p defs
declare -- defs="def1 def2 def3"
If the data/ definitions have spaces, etc.. could use arrays or we can look at other options. If you post some examples of the definitions, like are they a single word? multiple words with spaces in them, etc. Hope this helps!
You can use Global Parameters --query and --output to query the list from the output of az sig image-version list in Azure CLI. Please refer to :az sig image-definition
For example:
defs=$(az sig image-definition list --resource-group Wicresoft-Support --gallery-name douGallery --query [*].name --output tsv)
And then use typeset -p defs to see the exact contents of the variable. It’s a string structure from the below picture.
For example, here is bash script with Azure CLI
# convert to array
defs_array=($defs)
#declare -p defs_array
# the length of array
length=${#defs_array[#]}
read -p "please enter a name: " x
for i in ${defs_array[#]}
do
if [[ ${i} == $x ]]; then
echo "found"
break
else
if [[ ${i} == ${defs_array[-1]} ]];then
echo "not found"
fi
fi
done

How do I chain multiple user prompts together with the ability to go back a prompt?

I am wondering how I can make a bash script that has multiple menus in it.
For example, here's what the user would see upon running it:
Type the number of choosing:
1-play
2-load
3-exit
1
What is your name:
::prev::
Type the number of choosing:
1-play
2-load
3-exit
1
What is your name:
Brad
Where are you from, Brad?
Pennsylvania
What is your favourite colour?
1-blue
2-red
3-green
4-grey
5-magenta
,sdfhljalk:J;
What is your favourite colour?
1-blue
2-red
3-green
4-grey
5-magenta
2
What is your favourite toy?
train
What would you like on your sandwich?
::prev::
What is your favourite toy?
~`!##$%^& * ()_+=-{}[]|\"':;?/>.<,
What is your favourite toy?
::exit::
Exiting....
I apologize for it being long, I just want to cover all bases for the new game I'm going to be making. I want this to be the question to end all questions.
I want to be able to type ::prev:: wherever I am and have it go back to the previous question, and I'd like ::exit:: to exit the script wherever it is. Also, I'd like unrecognized input during questions with numbered responses to just reload the question without continuing, and for input containing characters that may cause a script break (something like :;!## ...) to reload the question instead of breaking.
Any help is greatly appreciated!
By the way, I'm using OS X Yosemite
First thing to do in this situation is to try and think of how, generally, you could implement something like this. Probably the biggest addition to complexity is using ::prev:: to go back a question. This means we need to represent the application state in some way such that we can move forward or backward.
Luckily, this is pretty simple: it's basically just an implementation of a stack that we need. Some visuals:
...
<--pop-- <--pop-- Location prompt <--pop-- ...
Name prompt Name prompt ...
Main menu --push-> Main menu --push-> Main menu --push-> ...
This also means each individual piece of the program needs to be self-contained. We can easily do this in shell scripting with functions.
So we need several pieces:
Function which displays a menu and allows the user to choose a value.
Function which displays a text prompt and allows the user to choose a value.
Function which manages a stack that represents the state of the program.
Individual functions for each piece of the program.
Let's first write our menu prompt function. This part is pretty easy. Bash will do most of the work using the select loop, which prints a menu for us. We'll just wrap it so that we can handle custom logic, like expecting ::exit:: or ::prev:: and some pretty-printing.
function show_menu {
echo "$1" # Print the prompt
PS3='> ' # Set prompt string 3 to '> '
select selection in "${menu_items[#]}" # Print menu using the menu_items array
do
if [[ "$REPLY" =~ ^(::exit::|::prev::)$ ]]; then
# If the user types ::exit:: or ::prev::, exit the select loop
# and return 1 from the function, with $selection containing
# the command the user entered.
selection="$REPLY"
return 1
fi
# $selection will be blank if the user did not choose a valid menu item.
if [ -z "$selection" ]; then
# Display error message if $selection is blank
echo 'Invalid input. Please choose an option from the menu.'
else
# Otherwise, return a success return code.
return 0
fi
done
}
We can now use this function like so:
menu_items=('Item 1' 'Item 2' 'Item 3')
if ! show_menu 'Please choose an item from the menu below.'; then
echo "You entered the command $selection."
fi
echo "You chose $selection."
Great! Now on to the next item on the agenda, writing the code that accepts text input from the user.
# Prompt for a required text value.
function prompt_req {
# do...while
while : ; do
# Print the prompt on one line, then '> ' on the next.
echo "$1"
printf '> '
read -r selection # Read user input into $selection
if [ -z "$selection" ]; then
# Show error if $selection is empty.
echo 'A value is required.'
continue
elif [[ "$selection" =~ ^(::exit::|::prev::)$ ]]; then
# Return non-success return code if ::exit:: or ::prev:: were entered.
return 1
elif [[ "$selection" =~ [^a-zA-Z0-9\'\ ] ]]; then
# Make sure input only contains a whitelist of allowed characters.
# If it has other characters, print an error and retry.
echo "Invalid characters in input. Allowed characters are a-z, A-Z, 0-9, ', and spaces."
continue
fi
# This break statement only runs if no issues were found with the input.
# Exits the while loop and the function returns a success return code.
break
done
}
Great. This function works similarly to the first:
if ! prompt_req 'Please enter a value.'; then
echo "You entered the command $selection."
fi
echo "You entered '$selection'."
Now that we have user input handled, we need to handle the program flow with our stack-managing function. This is fairly easy to implement in bash using an array.
When a part of the program runs and completes, it will ask the flow manager to run the next function. The flow manager will push the name of the next function onto stack, or rather, add it to the end of the array, and then run it. If ::prev:: is entered, it will pop the last function's name off of the stack, or remove the last element of the array, and then run the function before it.
Less talk, more code:
# Program flow manager
function run_funcs {
# Define our "stack" with its initial value being the function name
# passed directly to run_funcs
funcs=("$1")
# do...while
while : ; do
# Reset next_func
next_func=''
# Call the last function name in funcs.
if "${funcs[${#funcs[#]}-1]}"; then
# If the function returned 0, then no :: command was run by the user.
if [ -z "$next_func" ]; then
# If the function didn't set the next function to run, exit the program.
exit 0
else
# Otherwise, add the next function to run to the funcs array. (push)
funcs+=("$next_func")
fi
else
# If the function returned a value other than 0, a command was run.
# The exact command run will be in $selection
if [ "$selection" == "::prev::" ]; then
if [ ${#funcs[#]} -lt 2 ]; then
# If there is only 1 function in funcs, then we can't go back
# because there's no other function to call.
echo 'There is no previous screen to return to.'
else
# Remove the last function from funcs. (pop)
unset funcs[${#funcs[#]}-1]
fi
else
# If $selection isn't ::prev::, then it's ::exit::
exit 0
fi
fi
# Add a line break between function calls to keep the output clean.
echo
done
}
Our run_funcs function expects:
to be called with the name of the first function to run, and
that each function that runs will output the name of the next function to run to next_func if execution of the program must proceed.
Alright. That should be pretty simple to work with. Let's actually write the program now:
function main_menu {
menu_items=('Play' 'Load' 'Exit')
if ! show_menu 'Please choose from the menu below.'; then
return 1
fi
if [ "$selection" == 'Exit' ]; then
exit 0
fi
if [ "$selection" == 'Load' ]; then
# Logic to load game state
echo 'Game loaded.'
fi
next_func='prompt_name'
}
function prompt_name {
if ! prompt_req 'What is your name?'; then
return 1
fi
name="$selection"
next_func='prompt_location'
}
function prompt_location {
if ! prompt_req "Where are you from, $name?"; then
return 1
fi
location="$selection"
next_func='prompt_colour'
}
function prompt_colour {
menu_items=('Blue' 'Red' 'Green' 'Grey' 'Magenta')
if ! show_menu 'What is your favourite colour?'; then
return 1
fi
colour="$selection"
next_func='prompt_toy'
}
function prompt_toy {
if ! prompt_req 'What is your favourite toy?'; then
return 1
fi
toy="$selection"
next_func='print_player_info'
}
function print_player_info {
echo "Your name is $name."
echo "You are from $location."
echo "Your favourite colour is $colour."
echo "Your favourite toy is $toy."
# next_func is not set, so the program will exit after running this function.
}
echo 'My Bash Game'
echo
# Start the program, with main_menu as an entry point.
run_funcs main_menu
Everything is in order now. Let's try out our program!
$ ./bash_game.sh
My Bash Game
Please choose from the menu below.
1) Play
2) Load
3) Exit
> ::prev::
There is no previous screen to return to.
Please choose from the menu below.
1) Play
2) Load
3) Exit
> 2
Game loaded.
What is your name?
> Stephanie
Where are you from, Stephanie?
> ::prev::
What is your name?
> Samantha
Where are you from, Samantha?
> Dubl*n
Invalid characters in input. Allowed characters are a-z, A-Z, 0-9, ', and spaces.
Where are you from, Samantha?
> Dublin
What is your favourite colour?
1) Blue
2) Red
3) Green
4) Grey
5) Magenta
> Aquamarine
Invalid input. Please choose an option from the menu.
> 8
Invalid input. Please choose an option from the menu.
> 1
What is your favourite toy?
> Teddie bear
Your name is Samantha.
You are from Dublin.
Your favourite colour is Blue.
Your favourite toy is Teddie bear.
And there you have it.

How to print and edit variable content at the same place on runtime of a Unix shell script?

I have been looking all around (with no avail) to perform the following:
I want to display and able to edit if necessary the content of a variable in runtime for a Unix shell script after giving it a value. The idea goes like this:
Suppose we have a variable value either defined in script or by user input
var=12345
Print the variable value, but also leave the cursor in that position of printing and either press just enter to leave it intact or enter a new value in runtime
Edit variable content (press Enter to leave intact) : 12345
at this point at runtime, I want to leave the cursor in the position of number 1, while showing the variable, and working in the way that if I press Enter, leave the original content (12345) or read for a new value in that same place and modify. Clearing the display of the variable when typing anything else than Enter would be a big plus.
I have looked all around for a way to do this, but just haven't found anything. Anyone willing to provide a solution?
I would suggest you doing this in another way:
var=12345
echo "Change the value of Var (current value: $var)"
read -p "New value (Press Enter to skip):" nvar
if [[ "$nvar" != "" ]]; then
var="$nvar"
fi
echo $var
with this, it will prompt:
Change the value of Var (current value: 12345)
New value (Press Enter to skip):
what you wanted in the question (without the "big plus" part), could be achieved too:
var=12345
echo -ne "Now you may want to change the value: \n$var\r"
read nvar
if [[ "$nvar" != "" ]]; then
var="$nvar"
fi
echo $var
with this, it will prompt (cursor sits between ** )
Now you may want to change the value:
*1*2345
With bash, you could use the readline option for read, but that does not put the cursor at the beginning. It does however do what you want
var=12345
read -ep "Enter new value: " -i $var var
If you need the cursor to go back, you can do this:
var=12345
prompt="Enter new value: $var"
for ((i=1; i<=${#var}; i++)); do prompt+=$'\b'; done
read -p "$prompt" new
[[ -z $new ]] && new=$var

accesing functions from external files bash

This is really simple but since i m still a newbiw in shell scripting, i not able to do this. It is simple, i have a menu driven script in which when the user enters his choice i loop in the case function and execute the selected menu. Uptil now i was writing the entire code in the same script. Now i am putting the code in each menu option in a external script and accessing the code from there. So it is something like this,
#!/bin/bash
. course_func
file=names.dat
[ ! -f $file ] && > $file
while true
do
clear
echo ------------------------------------------
echo 1. Create a record
echo 2. View Records
echo 3. Search for records
echo 4. Delete records that match a pattern
echo ------------------------------------------
echo "Please enter your choice or enter (q) to quit: \c ";read choice
case $choice in
1)
create()
echo Enter to continue; read junk
;;
*)
clear
echo Wat nonsense enter properly
echo Enter to continue: ;read crap
;;
esac
done
Then i created another external script where I wrote the create() function
Code is in the link,
create()
{
while true
do
echo Please enter the name: ;read name
echo Please enter your surname: ;read surname
echo Please enter the address: ;read add
echo Please enter the cell no.: ;read cell
if yesno Do you really wish to save the following data to file
then
echo $name:$surname:$add:$cell >> $file
clear
echo The record has been inserted :D
else
echo The record was not save
fi
if yesno Do you wish to enter another record
then
:
else
xit
fi
done
}
addRecord()
{
}
search()
{
}
delRecord()
{
}
But i am getting an error,
course_func: line 31: syntax error near unexpected token `}'
course_func: line 31: `}'
menu_driven: line 18: syntax error near unexpected token `echo'
menu_driven: line 18: ` echo Enter to continue; read junk'
Line 31 is where I am ending the addRecord function, which is empty function, for the remaining cases. Can we not have a empty function in bash?
Functions may not be empty!
--Advanced Bash-Scripting Guide: Chapter 24. Functions
Instead, try:
addRecord()
{
return
}

How do i achieve "read -i" in a different way?

I Have been looking on the internet but without finding a solution (hard to know what to serach for). I want a way to show "current value" on an input. Similar to what read -e -p "something: " -i "this" $variable does but in Busybox sh. As that option only became available in Bash4 which is not default. I will have to solve it another way. Not sure if this is possible..
read -p "Select by entering an number: " ANS
case $ANS in
1 ) ANS="%R | %a %d %b" ;;
2 ) ANS="%R, %a %d %b" ;;
3 ) read -p "Enter a custom string (ex. %R): " ANS ;;
4 ) ANS="%R" ;;
* ) read -p "Just answer with a number between 1 to 4. Aborting!" end; exit 0 ;;
esac
What i want is that case #3 will show the current value of the file. Ex. If the file has %R as current value. Then that is what i want it to show on input for user to interactively change it.
Until you can give us examples of what you want the programme to take as an 'input' and then give as an 'output', I suggest you read TLPD's section on 'read'

Resources