Basic menu BASH script in Lubuntu - linux

I am creating a basic menu driven BASH script with 6 options, I was wondering if someone can give me a basic template of the script?
#!/bin/bash
Option 1)
Option 2)
Option 3)
Option 4)
Option 5)
Option 6) Exit

Try the select statement. Example and template:
#!/bin/bash
select choice in opt1 opt2 opt3 opt4 opt5 exit
do
case $choice in
opt1)
sl;
fortune|cowsay -d;
break;;
opt2)
cd desktop/;
mkdir textfiles;
cd textfiles;
touch 1.txt 2.txt 3.txt;
cd ..;
tar-cvf textfiles.tar textfiles/;
break;;
opt3)
echo 'You chose opt3';;
opt4)
echo 'You chose opt4';;
opt5)
echo 'You chose opt5';;
exit)
break;;
*)
echo 'Invalid option';;
esac
done
I have inserted opt1 and opt2 as examples. Remember to use break;; if you want to exit the menu loop and choose better names than optN for the options.
Edit: I just copied opt1 and opt2 from your question. I haven't looked into them. If you need help with them, you should probably ask separate questions.

Related

how to call a function with 2 arguments which under the option of "getopts"

New in Linux bash script.
Here I tried to create some files with getopts. For example I'd like to create 3 files called xyzfile, in command line ./createfiles -n xyzfile 3should be given (2 arguments after the option -n). The result should be 3 files with the names xyzfile_1, xyzfile_2 and xyzfile_3.
I tried to put my createfile() function outside the while-loop and as well as inside the while-loop. But the option -n doesn't work.
I also tried to create another function called foo() with included the function createfile(), but still something wrong there.
I have no idea anymore what I can do. Hope I can get some advices from you guys. Thank you very much!
#!/bin/bash
while getopts :n:bc opt; do
case $opt in
n) echo test 3333333
createfile() {
echo "$OPTARG"
sum=$2
for((i=1;i<=sum;i++))
do
touch "$OPTARG_${i}"
done
}
createfile $OPTARG ${2};;
b) echo "test 1111111";;
c) echo "test 2222222";;
*) echo error!;;
esac
done
Use a separate option for the count, and create your files after the option processing.
Something like:
while getopts "n:c:" opt; do
case $opt in
n) name="$OPTARG";;
c) count=$OPTARG;;
# other options...
esac
done
shift $((OPTIND -1))
while (( count > 0 )); do
touch "${name}_$count"
(( count-- ))
# ...
done
getopts supports only options without, or with one argument. So you'll have to decide on which way you want your script to work. You have multiple options:
add a new option -m or similar to pass the maximum number of files you want to create: createfile -n xyzfile -m 3
you can also use the arguments that are not passed as an option, if you do your parsing well then createfile 3 -n xyzfile or createfile -n xyzfile 3 would mean the same. In my scripts I often use such positional argument if there is one option that the user always needs to pass.
You might even consider changing your way of calling the script to createfile xyzfile -n 3 or even createfile xyzfile where the name is a positional argument and the number of files optional (choose a logical default value, probably 1)...
Parse the options first, then use the values you discover. An option can take only a single argument, so -n only gets the first one (I'll keep that as the file-name stem here). The count will be an ordinary positional argument found after parsing the options.
while getopts :n:bc opt; do
case $opt in
n) stem=$OPTARG; shift 2;;
b) shift 1;;
c) shift 1;;
*) shift 1; echo error ;;
esac
done
count=${1?No count given}
createfile () {
for ((i=$1; i<=$2; i++)); do
touch "${1}_${i}"
done
}
createfile "$stem" "$count"

Parsing long and short args in ksh using loop

I am trying to parse arguments in ksh. Can't do getopt for the same as in short options I have two/three characters. Currently I am using for loop. Its stupid but am unable to find something better.
Question: How do I set option+value as one unit in order to parse?
Also if eval set -- $option will help me then how do I use it? echo on option does not show the expected "--" at the end. Am I assuming something wrong?
I am thinking of using a variable to keep track of when an option is found but this method seems too confusing and unnecessary.
Thanks for your time and help.
Update 1:
Adding code as pointed out. Thanks to markp, Andre Gelinas and random down-voter in making this question better. Trying to execute the script as given in line 2 and 3 of code - or any other combination of short and long options passed together.
#!/bin/ksh
# bash script1.sh --one 123 --two 234 --three "some string"
# bash script1.sh -o 123 -t 234 -th "some string"
# the following creates problems for short options.
#options=$(getopt -o o:t:th: -l one:two:three: "--" "$#")
#Since the below `eval set -- "$options"` did not append "--" at the end
#eval set -- "$options"
for i in $#; do
options="$options $i"
done
options="$options --"
# TODO capture args into variables
Attempted code below TODO until now:
for i in $options; do
echo $i
done
Will be capturing the args using:
while true; do
case $1 in
--one|-o) shift; ONE=$1
;;
--two|-t) shift; TWO=$1
;;
--three|-th) shift; THREE=$1
;;
--) shift; break
;;
esac
done
Try something like this :
#!/bin/ksh
#Default value
ONE=123
TWO=456
# getopts configuration
USAGE="[-author?Andre Gelinas <andre.gelinas#foo.bar>]"
USAGE+="[-copyright?2018]"
USAGE+="[+NAME?TestGetOpts.sh]"
USAGE+="[+DESCRIPTION?Try out for GetOps]"
USAGE+="[o:one]#[one:=$ONE?First.]"
USAGE+="[s:second]#[second:=$TWO?Second.]"
USAGE+="[t:three]:[three?Third.]"
USAGE+=$'[+SEE ALSO?\aman\a(1), \aGetOpts\a(1)]'
while getopts "$USAGE" optchar ; do
case $optchar in
o) ONE=$OPTARG ;;
s) TWO=$OPTARG ;;
t) THREE=$OPTARG ;;
esac
done
print "ONE = "$ONE
print "TWO = "$TWO
print "THREE = "$THREE
You can use either --one or -o. Using --man or --help are also working. Also -o and -s are numeric only, but -t will take anything. Hope this help.

how to customize select loop in bash

I was wondering if is possible customize the select loop for bash.
I have this code:
select varName in list
do
case $varName in
pattern1)
command1;;
pattern2)
command2;;
pattern1)
command3;;
*)
echo "Error select option 1..3";;
esac
done
Output is something like this:
1) columbia 3) challenger 5) atlantis 7) pathfinder
2) endeavour 4) discovery 6) enterprise
#?
I would like to order the options in landscape view and also change the prompt [#?] by something else
Thanks
select displays the PS3 prompt.
You could try something like:
echo $PS3
old_PS3=$PS3
export PS3="make a selection :D"
list='columbia challenger atlantis pathfinder endeavour discovery enterprise'
select varName in $list
do
case $varName in
pattern1)
command1;;
pattern2)
command2;;
pattern1)
command3;;
*)
echo "Error select option 1..3";;
esac
done
# set PS3 back to original
export PS3=$old_PS3

I'm looking to convert my MS-DOS .bat file into the equivalent linux .sh file

Basically I have to make my linux program do the same thing as my MS-DOS. Could someone help with pointers and things like that?
All the program currently does, is pull a menu for basic, advanced account creation and close.
Advanced has nothing in atm, and close does what you expect. Basic then asks you for:
Full name
Username
Password
It then saves it all in a .log file (after checking for usernames already entered) along with the exact time of creation. For example:
Badja
John Doe
123
23/04/2015 15:07:32.61
#echo off
c:
:1
set curdir=%test%
echo Welcome to OP-SYS Account creation. Please choose which mode you would like to continue in.
echo.
echo [1] Basic Account Creation
echo [2] Advanced Account Creation
echo [3] Exit
echo.
REM Menu choices
set /p cat=
if %cat%==1 (
goto 2
) else if %cat%==2 (
goto 3
) else if %cat%==3 (
goto 5
) else (
goto 4
)
:2
REM Basic account creation
echo Welcome to basic account creation.
REM user enters details
REM Username
echo Please Enter a Username
set /p username=
echo.
REM Real Name
echo Please enter your full name
set /p fullname=
echo.
REM Password
echo Please enter a password
set /p password=
echo.
REM Real name
REM Save to file
if exist %username%.log (
echo User name already exists, please enter a new user name to create an account, or return to the log in screen
goto 1
) else (
echo %username% >> %username%.log
echo %fullname% >> %username%.log
echo %password% >> %username%.log
echo %date% %time% >> %username%.log )
timeout /t 3 /nobreak > NUL
REM pause
goto end
:3
REM Advanced account creation
echo Welcome to advanced account creation.
echo This is not complete, please return to main menu.
Pause
goto 1
:4
REM Error
echo error
echo.
goto 1
:5
REM Exit
echo Goodbye.
goto end
:end
exit​
I know there are a few things that can't be copied over to linux, but I just don't know where to start, this is my basis:
PS3='Please enter your choice: '
options=("cat 1" "cat 2" "end")
select opt in "${options[#]}"
do
case $opt in
"cat 1")
echo "Basic"
;;
"cat 2")
echo "Advanced"
;;
"end")
break
;;
*) echo invalid option;;
esac
done
Any help would be amazing guys!
function choice1 {
echo "Do stuff here to create an account"
}
function choice2 {
echo "Get the point?"
}
function choice3 {
exit
}
#### Main
echo "Welcome to OP-SYS Account creation. Please choose which mode you would like to continue in."
echo
echo "[1] Basic Account Creation
[2] Advanced Account Creation
[3] Exit"
echo
read CHOICE #### This loads your choice into a variable
eval choice"$CHOICE" ### This is evaluation awesomeness
If the eval seems strange, you can go with a more traditional case...esac statement to call the various functions. I would suggest getting your menu cycling and escaping the way that you want before adding in your special function.
clear
echo "Welcome to OP-SYS Account creation. Please choose which mode you would like to continue in."
echo
echo "[1] Basic Account Creation
[2] Advanced Account Creation
[3] Exit"
echo

Is there a "goto" statement in bash?

Is there a "goto" statement in bash ? I know It is considered bad practice, but I need specifically "goto".
If you are using it to skip part of a large script for debugging (see Karl Nicoll's comment), then if false could be a good option (not sure if "false" is always available, for me it is in /bin/false):
# ... Code I want to run here ...
if false; then
# ... Code I want to skip here ...
fi
# ... I want to resume here ...
The difficulty comes in when it's time to rip out your debugging code. The "if false" construct is pretty straightforward and memorable, but how do you find the matching fi? If your editor allows you to block indent, you could indent the skipped block (then you'll want to put it back when you're done). Or a comment on the fi line, but it would have to be something you'll remember, which I suspect will be very programmer-dependent.
No, there is not; see §3.2.4 "Compound Commands" in the Bash Reference Manual for information about the control structures that do exist. In particular, note the mention of break and continue, which aren't as flexible as goto, but are more flexible in Bash than in some languages, and may help you achieve what you want. (Whatever it is that you want . . .)
It indeed may be useful for some debug or demonstration needs.
I found that Bob Copeland solution http://bobcopeland.com/blog/2012/10/goto-in-bash/ elegant:
#!/bin/bash
# include this boilerplate
function jumpto
{
label=$1
cmd=$(sed -n "/$label:/{:a;n;p;ba};" $0 | grep -v ':$')
eval "$cmd"
exit
}
start=${1:-"start"}
jumpto $start
start:
# your script goes here...
x=100
jumpto foo
mid:
x=101
echo "This is not printed!"
foo:
x=${x:-10}
echo x is $x
results in:
$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101
You can use case in bash to simulate a goto:
#!/bin/bash
case bar in
foo)
echo foo
;&
bar)
echo bar
;&
*)
echo star
;;
esac
produces:
bar
star
If you're testing/debugging a bash script, and simply want to skip forwards past one or more sections of code, here is a very simple way to do it that is also very easy to find and remove later (unlike most of the methods described above).
#!/bin/bash
echo "Run this"
cat >/dev/null <<GOTO_1
echo "Don't run this"
GOTO_1
echo "Also run this"
cat >/dev/null <<GOTO_2
echo "Don't run this either"
GOTO_2
echo "Yet more code I want to run"
To put your script back to normal, just delete any lines with GOTO.
We can also prettify this solution, by adding a goto command as an alias:
#!/bin/bash
shopt -s expand_aliases
alias goto="cat >/dev/null <<"
goto GOTO_1
echo "Don't run this"
GOTO_1
echo "Run this"
goto GOTO_2
echo "Don't run this either"
GOTO_2
echo "All done"
Aliases don't usually work in bash scripts, so we need the shopt command to fix that.
If you want to be able to enable/disable your goto's, we need a little bit more:
#!/bin/bash
shopt -s expand_aliases
if [ -n "$DEBUG" ] ; then
alias goto="cat >/dev/null <<"
else
alias goto=":"
fi
goto '#GOTO_1'
echo "Don't run this"
#GOTO1
echo "Run this"
goto '#GOTO_2'
echo "Don't run this either"
#GOTO_2
echo "All done"
Then you can do export DEBUG=TRUE before running the script.
The labels are comments, so won't cause syntax errors if disable our goto's (by setting goto to the ':' no-op), but this means we need to quote them in our goto statements.
Whenever using any kind of goto solution, you need to be careful that the code you're jumping past doesn't set any variables that you rely on later - you may need to move those definitions to the top of your script, or just above one of your goto statements.
Although others have already clarified that there is no direct goto equivalent in bash (and provided the closest alternatives such as functions, loops, and break), I would like to illustrate how using a loop plus break can simulate a specific type of goto statement.
The situation where I find this the most useful is when I need to return to the beginning of a section of code if certain conditions are not met. In the example below, the while loop will run forever until ping stops dropping packets to a test IP.
#!/bin/bash
TestIP="8.8.8.8"
# Loop forever (until break is issued)
while true; do
# Do a simple test for Internet connectivity
PacketLoss=$(ping "$TestIP" -c 2 | grep -Eo "[0-9]+% packet loss" | grep -Eo "^[0-9]")
# Exit the loop if ping is no longer dropping packets
if [ "$PacketLoss" == 0 ]; then
echo "Connection restored"
break
else
echo "No connectivity"
fi
done
This solution had the following issues:
Indiscriminately removes all code lines ending in a :
Treats label: anywhere on a line as a label
Here's a fixed (shell-check clean and POSIX compatible) version:
#!/bin/sh
# GOTO for bash, based upon https://stackoverflow.com/a/31269848/5353461
goto() {
label=$1
cmd=$(sed -En "/^[[:space:]]*#[[:space:]]*$label:[[:space:]]*#/{:a;n;p;ba};" "$0")
eval "$cmd"
exit
}
start=${1:-start}
goto "$start" # GOTO start: by default
#start:# Comments can occur after labels
echo start
goto end
# skip: # Whitespace is allowed
echo this is usually skipped
# end: #
echo end
There is one more ability to achieve a desired results: command trap. It can be used to clean-up purposes for example.
There is no goto in bash.
Here is some dirty workaround using trap which jumps only backwards:)
#!/bin/bash -e
trap '
echo I am
sleep 1
echo here now.
' EXIT
echo foo
goto trap 2> /dev/null
echo bar
Output:
$ ./test.sh
foo
I am
here now.
This shouldn't be used in that way, but only for educational purposes. Here is why this works:
trap is using exception handling to achieve the change in code flow.
In this case the trap is catching anything that causes the script to EXIT. The command goto doesn't exist, and hence throws an error, which would ordinarily exit the script. This error is being caught with trap, and the 2>/dev/null hides the error message that would ordinarily be displayed.
This implementation of goto is obviously not reliable, since any non-existent command (or any other error, for that manner), would execute the same trap command. In particular, you cannot choose which label to go-to.
Basically in real scenario you don't need any goto statements, they're redundant as random calls to different places only make your code difficult to understand.
If your code is invoked many times, then consider to use loop and changing its workflow to use continue and break.
If your code repeats it-self, consider writing the function and calling it as many times as you want.
If your code needs to jump into specific section based on the variable value, then consider using case statement.
If you can separate your long code into smaller pieces, consider moving it into separate files and call them from the parent script.
I found out a way to do this using functions.
Say, for example, you have 3 choices: A, B, and C. A and Bexecute a command, but C gives you more info and takes you to the original prompt again. This can be done using functions.
Note that since the line containg function demoFunction is just setting up the function, you need to call demoFunction after that script so the function will actually run.
You can easily adapt this by writing multiple other functions and calling them if you need to "GOTO" another place in your shell script.
function demoFunction {
read -n1 -p "Pick a letter to run a command [A, B, or C for more info] " runCommand
case $runCommand in
a|A) printf "\n\tpwd being executed...\n" && pwd;;
b|B) printf "\n\tls being executed...\n" && ls;;
c|C) printf "\n\toption A runs pwd, option B runs ls\n" && demoFunction;;
esac
}
demoFunction
This is a small correction of the Judy Schmidt script put up by Hubbbitus.
Putting non-escaped labels in the script was problematic on the machine and caused it to crash. This was easy enough to resolve by adding # to escape the labels. Thanks to Alexej Magura and access_granted for their suggestions.
#!/bin/bash
# include this boilerplate
function goto {
label=$1
cmd=$(sed -n "/$#label#:/{:a;n;p;ba};" $0 | grep -v ':$')
eval "$cmd"
exit
}
start=${1:-"start"}
goto $start
#start#
echo "start"
goto bing
#boom#
echo boom
goto eof
#bang#
echo bang
goto boom
#bing#
echo bing
goto bang
#eof#
echo "the end mother-hugger..."
A simple searchable goto for the use of commenting out code blocks when debugging.
GOTO=false
if ${GOTO}; then
echo "GOTO failed"
...
fi # End of GOTO
echo "GOTO done"
Result is-> GOTO done
My idea for creating something like "goto" is to use select with case and assign a variable, which I then check in an if statement. Not perfect, but may help in some cases
Example:
#!/usr/bin/env bash
select goto in Ubuntu Debian Quit ; do
case $goto in
Ubuntu) { CHOICE="Ubuntu" ; break ; } ;;
Debian) { CHOICE="Debian" ; break ; } ;;
Quit) { echo "Bye" ; exit ; } ;;
*) { echo "Invalid selection, please try again..." ; } ;;
esac
done
if [ "$CHOICE" == "Ubuntu" ]; then
echo "I'm in Ubuntu"
fi
if [ "$CHOICE" == "Debian" ]; then
echo "I'm in Debian"
fi
Why don't anyone just use functions directly ?
BTW functions are easier to deal with than making a new thing
My style :
#!/bin/bash
# Your functions
function1 ()
{
commands
}
function2 ()
{
commands
}
:
:
functionn ()
{
commands
}
# Execute 1 to n in order
for i in {1..n}
do
function$i
done
# with conditions
for i in {1..n}
do
[ condition$i ] && function$i
done
# Random order
function1
functionn
function5
:
:
function3
Example for above style :
#!/bin/bash
# Your functions
function1 ()
{
echo "Task 1"
}
function2 ()
{
echo "Task 2"
}
function3 ()
{
echo "Task 3"
}
function1
function3
function2
Output :
Task 1
Task 3
Task 2
Drawbacks :
Script in an organized way.
Less problems and not prone to errors.
You can make function inside a existing function.
Move back and forth without any problems.

Resources