Menu driven scripts

About writing shell scripts and making the most of your shell
Forum rules
Topics in this forum are automatically closed 6 months after creation.
Ray94520
Level 1
Level 1
Posts: 30
Joined: Thu Aug 12, 2021 4:17 pm

Menu driven scripts

Post by Ray94520 »

I'm trying to upgrade my menu process.
I currently have menu driven scripts.
That I pull in using Cat statements which are excellent

I am Trying to put my menus in the script itself and I found this 1 that I like so far

Code: Select all

#title="Select example"
prompt=$'\e[1;32m Choose your favorite food: \e[0m'
foods=("Pizza" "Pho" "Tacos" "Quit" "Testing")

echo "$title"
PS3="
$prompt"
select fav in "${foods[@]}"; do

    case $fav in
        
        "Pizza")
            echo "Americans eat roughly 100 acres of $fav each day!"
            
	    # optionally call a function or run some code here
            ;;
        "Pho")
            echo "$fav is a Vietnamese soup that is commonly mispronounced like go, instead of duh."
	    # optionally call a function or run some code here
            ;;
        "Tacos")
            echo "According to NationalTacoDay.com, Americans are eating 4.5 billion $fav each year."
	    # optionally call a function or run some code here
	    break
            ;;
	"Quit")
	    echo "User requested exit"
	    exit
	    ;;
        *) echo "invalid option $REPLY";;
    esac
    REPLY=  # this acts like a loop it will make the script start at the beginning
done
I actually found a way on here to change the color in the PS3= and it works perfectly

What I'm trying to do now is when the script is running. I have the following

1) pizza
2) PHO
3) tacos
4) quit
5) testing

What I'm trying to do is change the color of the numbers
and the text alongside them. I haven't been able to find out how to do this

Can someone help me with this
Last edited by LockBot on Wed Dec 28, 2022 7:16 am, edited 3 times in total.
Reason: Topic automatically closed 6 months after creation. New replies are no longer allowed.
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Menu driven scripts

Post by Termy »

You've made the code much harder to read by not indenting your code accordingly.

You might want to try something more manual? The select loop is great, but limited and can be kinda clunky when you want to do something more with it. I always prompt the user in a similar way as I've done below, although usually without all the colors and stuff. BTW, cat(1) is not a statement.

Code: Select all

#!/usr/bin/env bash

read -d '' -r Options <<-EOF
	\e[92mWhat is your favorite food?\e[0m

	  \e[93m*\e[0m Pizza
	  \e[93m*\e[0m Pho
	  \e[93m*\e[0m Tacos

	  \e[91m*\e[0m [Q]uit
EOF

printf '%b\n\n' "$Options"

while :; do
	read -p ': '

	case ${REPLY,,} in
		pizza)
			printf 'Americans eat roughly 100 acres of pizza each day!\n' ;;
		pho)
			printf 'Pho is a Vietnamese soup, commonly mispronounced like go, instead of duh.\n' ;;
		tacos)
			printf 'According to NationalTacoDay.com, Americans eat 4.5 billion tacos each year.\n' ;;
		q|quit)
			break ;;
		'')
			printf 'Err: Empty response -- try again.\n' 1>&2 ;;
		*)
			printf 'Err: Invalid response -- try again.\n' 1>&2 ;;
	esac
done
Some pointers:
  • Matching '' in a case statement, essentially matches a null string. This must be done before the * section. Using just ) would be a syntax error, hence the use of quotes.
  • You were displaying the prompt in bold, because of 1, but I thought I'd demonstrate using a bright color (92) instead.
  • The -d flag will essentially tell the read builtin to slurp the entire input, reading it all into the variable, instead of just the first line.
  • The -r flag tells read to allow the use of escapes, put simply. This is why I used the %b format specification with the printf builtin, so that I could expand those ANSI color escape sequences.
  • The <<-EOF thing is a heredoc (more completely: here document), which essentially allows you to conveniently feed data into something, even using multiple lines. Doing this can afford you clean, easy-to-parse code, while making your life much easier. There are some additional features, such as the ability to ignore Tab characters with the optional -, as is used here, but note that this is a BASH-specific feature.

    BTW, you can redirect the contents of the heredoc, but note that you do the redirection on the same line as when you start the heredoc, such as:

    Code: Select all

    cat <<-'EOF' > some_file.txt
    	Here is a here document.
    	It supports multiple lines.
    	Expansion is allowed by default.
    	The `-` is optional in BASH, ignoring Tabs.
    EOF
    
    So, in the above example, cat(1) reads from the heredoc, then the shell takes its STDOUT and sends it to the file. I single-quoted the EOF string to essentially force a literal string, disallowing expansion of parameters. Double-quotes could have been used for the same effect, but due to the regular nature of double-quotes in shell and various other languages, I used single-quotes to avoid confusion. The EOF string is just the convention, so you can choose another valid WORD.
  • The read builtin has a -p flag, allowing you to specify the prompt at the same time as awaiting STDIN. You can't normally set the color, but there is a trick you can use, with $'...', where the ANSI color escape sequences go where the ... are; I'm not sure how reliable that is here, though.
  • The while : part is equivalent to while true, where true and : are synonymous builtins.
  • The use of ${REPLY,,} is a form of parameter expansion, allowing the contents of the variable to be expanded to all (because ,, not ,) its alphabetic characters converted to lowercase.
I would avoid needlessly colorizing your output. Does the color provide anything useful? Does it emphasize something worthy of emphasis? Does it make something otherwise confusing much easier to read? Will the user have access to colors and will it display sufficiently to the user? These are the sort of things I ask myself when deciding whether to use colors. That being said, I understand if you're just practising the escape sequences, as I know they can be a lot to remember.

Regarding the following line of yours:

Code: Select all

prompt=$'\e[1;32m Choose your favorite food: \e[0m'
That does not need the leading $; that particular syntax is more for the escape sequence trick I mentioned above.
I'm also Terminalforlife on GitHub.
Ray94520
Level 1
Level 1
Posts: 30
Joined: Thu Aug 12, 2021 4:17 pm

Re: Menu driven scripts

Post by Ray94520 »

Thank you very much for your reply.
So I am a new user at scripting and I'm doing my best to learn. I appreciate the pointers
the script I found and showed you. I actually found on the Internet and thought it would be helpful in upgrading my menu driven scripts.
My scripts are very simple, very basic

so anyway I had a feeling I could not modify this current script to do what I want to do
so I will do my best to put yours together to see if I can make my menus run better.
The reason I'm trying to upgrade my menus is because I pull in menus with cat that were generated by text documents.
And then use case statements to execute

I think it would be nicer if I could put the menu in the script. That's what my goal is

Thank you very much for your help
User avatar
xenopeek
Level 25
Level 25
Posts: 29597
Joined: Wed Jul 06, 2011 3:58 am

Re: Menu driven scripts

Post by xenopeek »

Termy wrote: Mon May 23, 2022 12:36 pmYou've made the code much harder to read by not indenting your code accordingly.
I've added code tags around the code in the first post, to make it more readable and preserve whitespace.
Image
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Menu driven scripts

Post by Termy »

xenopeek wrote: Mon May 23, 2022 2:24 pm [...]
Noice! Was hoping someone would do that. Was it just the formatting getting butchered by the site?
I'm also Terminalforlife on GitHub.
User avatar
xenopeek
Level 25
Level 25
Posts: 29597
Joined: Wed Jul 06, 2011 3:58 am

Re: Menu driven scripts

Post by xenopeek »

Termy wrote: Mon May 23, 2022 2:41 pmWas it just the formatting getting butchered by the site?
Well don't blame the site :) HTML largely ignores whitespace. Leading and trailing whitespace on elements is entirely ignored and between words and elements repeated whitespace are treated like a single whitespace. To preserve whitespace in rendered HTML the text needs to be put between <pre> tags or similar. That's what the [code][/code] tags here do.
Image
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Menu driven scripts

Post by Termy »

xenopeek wrote: Mon May 23, 2022 2:54 pm [...]
Ooooh yeah, I forgot about that. Been a while since I dealt with HTML. I was more referring to phpBB than specifically this site. :lol:
I'm also Terminalforlife on GitHub.
Ray94520
Level 1
Level 1
Posts: 30
Joined: Thu Aug 12, 2021 4:17 pm

Re: Menu driven scripts

Post by Ray94520 »

I'm not sure I understand what you just said :?:

Are you saying I posted it incorrectly
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Menu driven scripts

Post by Termy »

Ray94520 wrote: Mon May 23, 2022 3:12 pm [...]
Basically, when you want to post a block of code (or terminal output) on these forums, it's best to use a [code][/code] block, because it maintains correct formatting and is separately scrollable.
I'm also Terminalforlife on GitHub.
User avatar
AndyMH
Level 21
Level 21
Posts: 13728
Joined: Fri Mar 04, 2016 5:23 pm
Location: Wiltshire

Re: Menu driven scripts

Post by AndyMH »

Ray94520 wrote: Sun May 22, 2022 5:54 pm What I'm trying to do is change the color of the numbers
and the text alongside them.
What I do in a script

Code: Select all

#setup text colour output for terminal
red=`tput setaf 1`
grn=`tput setaf 2`
yel=`tput setaf 3`
def=`tput sgr0` #default or reset
then when I want to change the colours, echo the relevant variable, an example

Code: Select all

#-------------------------------------------------------------------------------
# Add synology to fstab
#-------------------------------------------------------------------------------
echo $yel"Checking fstab for synology"$def
#check if already in fstab
present=`cat /etc/fstab | grep diskstation.local/home`
if [ ${#present} -eq 0 ]; then #if not then add
    echo $yel"adding synology to fstab"$def
    #copy passwords file
    sudo cp $PWD/synology/credentials /etc/samba/credentials
    echo "#diskstation mounting under cifs" | sudo tee -a /etc/fstab
    echo "//diskstation.local/home/	/media/synology	cifs	credentials=/etc/samba/credentials,uid=1000,gid=1000,nofail	0	0" | sudo tee -a /etc/fstab
    sudo mkdir /media/synology
else
    echo $yel"synology already in fstab"$def
fi
more here:
https://unix.stackexchange.com/question ... olor-codes
Thinkcentre M720Q - LM21.3 cinnamon, 4 x T430 - LM21.3 cinnamon, Homebrew desktop i5-8400+GTX1080 Cinnamon 19.0
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Menu driven scripts

Post by Termy »

AndyMH wrote: Mon May 23, 2022 5:44 pm [...]
I assume you use tput(1) here for portability, or is it to avoid having to remember all the ANSI color escape sequences? ;) :lol:
I'm also Terminalforlife on GitHub.
Ray94520
Level 1
Level 1
Posts: 30
Joined: Thu Aug 12, 2021 4:17 pm

Re: Menu driven scripts

Post by Ray94520 »

Well I don't post on here too often, so I apologize for the as you call it cold blocking. The next time, if there is I will do so.

In certain areas I do use tput it has been quite handy in some cases
User avatar
AndyMH
Level 21
Level 21
Posts: 13728
Joined: Fri Mar 04, 2016 5:23 pm
Location: Wiltshire

Re: Menu driven scripts

Post by AndyMH »

Termy wrote: Mon May 23, 2022 6:04 pm I assume you use tput(1) here for portability, or is it to avoid having to remember all the ANSI color escape sequences? ;) :lol:
To avoid the escape sequences :)
Thinkcentre M720Q - LM21.3 cinnamon, 4 x T430 - LM21.3 cinnamon, Homebrew desktop i5-8400+GTX1080 Cinnamon 19.0
Ray94520
Level 1
Level 1
Posts: 30
Joined: Thu Aug 12, 2021 4:17 pm

Re: Menu driven scripts

Post by Ray94520 »

Well I'm learning that tput is much easier to learn and use :)
User avatar
xenopeek
Level 25
Level 25
Posts: 29597
Joined: Wed Jul 06, 2011 3:58 am

Re: Menu driven scripts

Post by xenopeek »

Every command substitution, subshell or pipe is a costly fork. It doesn't matter a lot when you use tput for a handful of colors but it adds up. And if you reuse the code later in a longer script, thinking it will be performant, it might come back to bite you. I've had that happen. So I use native bash when available which means using ANSI escape codes for setting colors. I have a script that defines constants for setting basic colors and text attributes so I don't have to re-invent the ANSI codes each time. It's faster to run that script and load all those constants than it is to call tput even only once.

Just to show how costly command substitution is I compared a simple script that does red=`tput setaf 1` 10,000 times to one that does red=$'\e[31m' 10,000 times. tput took 0m10,452s and the native bash 0m0,023s. That's 454 times faster :shock:
Image
Ray94520
Level 1
Level 1
Posts: 30
Joined: Thu Aug 12, 2021 4:17 pm

Re: Menu driven scripts

Post by Ray94520 »

Well that's an interesting experiment There. That ANSI codes are much faster
But most of my scripts I do in bash is really just for myself,
but that's an interesting experiment :)
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Menu driven scripts

Post by Termy »

xenopeek wrote: Tue May 24, 2022 3:30 pm Every command substitution, subshell or pipe is a costly fork. It doesn't matter a lot when you use tput for a handful of colors but it adds up.
Well said. I've spent many years banging on about that sort of thing!
I'm also Terminalforlife on GitHub.
user6c57b8
Level 2
Level 2
Posts: 52
Joined: Mon Aug 05, 2019 1:07 pm

Re: Menu driven scripts

Post by user6c57b8 »

Ray94520 wrote: Sun May 22, 2022 5:54 pm I'm trying to upgrade my menu process.
...
Can someone help me with this
Merry Christmas:

Image

put this text file in the same directory as your script (save it as food-list.txt):

Code: Select all

<!-- possible colors:
     black, black_bold, blue, green, magenta, red
     (all ON_WHITE) -->
<red>pizza</red>
<blue>PHO</blue>
<green>tacos</green>
<black_bold>quit</black_bold>
<black>testing</black>
here is the script/code:

Code: Select all

#!/bin/bash
#tested 13%

#<REFERENCE>
#black=>30
#bold=>1
#red=>31
#green=>32
#blue=>34
#on_white=>47
#exit_color=>0
#\e[${color_name}mHello\e[${exit_color}m
#</REFERENCE>

echo $'\e[1;32m Choose your favorite food: \e[0m' 
food_list_html="$(perl -e 'open(my $f, "food-list.txt") or die $!;undef $/;$_=<$f>;close($f);s/<!--.*?-->//sg;s/^\s+//sg;s/\s+$//gm;s/\n//gs;print;')"
food_list_bashed="$food_list_html"
unset food_list_html
#note: these colors are on a forced white background
#      (the white background is the 47m)
food_list_bashed="${food_list_bashed/<blue>/\\e[34m\\e[47m}"
food_list_bashed="${food_list_bashed/<\/blue>/\\e[0m\\n}"
food_list_bashed="${food_list_bashed/<red>/\\e[31m\\e[47m}"
food_list_bashed="${food_list_bashed/<\/red>/\\e[0m\\n}"
food_list_bashed="${food_list_bashed/<green>/\\e[32m\\e[47m}"
food_list_bashed="${food_list_bashed/<\/green>/\\e[0m\\n}"
food_list_bashed="${food_list_bashed/<blue>/\\e[34m\\e[47m}"
food_list_bashed="${food_list_bashed/<\/blue>/\\e[0m\\n}"
food_list_bashed="${food_list_bashed/<black>/\\e[30m\\e[47m}"
food_list_bashed="${food_list_bashed/<\/black>/\\e[0m\\n}"
food_list_bashed="${food_list_bashed/<black_bold>/\\e[30m\\e[1m\\e[47m}"
food_list_bashed="${food_list_bashed/<\/black_bold>/\\e[0m\\n}"
food_list_bashed="${food_list_bashed/<magenta>/\\e[35m\\e[47m}"
food_list_bashed="${food_list_bashed/<\/magenta>/\\e[0m\\n}"

num=1
declare -a array_entry_list
# the -n option is for no newline.
# the -e option is for the newline and color escape codes being utilized
while read entry <&9; do
  [ -z "$entry" ] && continue #if the entry is empty go to next entry
  #I ripped the below perl code from /usr/share/perl/5.32.1/Term/ANSIColor.pm
  array_entry_list+=("$(echo "$entry" | perl -e '$_=<>;chomp;s{ \e\[ [\d;]* m }{}xmsg;print;')")
  echo -e "\\e[36m$num\\e[0m) $entry" #I used 36-CYAN color for the numbers
  let num++
done 9< <(echo -ne "$food_list_bashed")
unset food_list_bashed

max_number_selection=$num
unset num

# '-n 1' means you accept only one character from the user before accepting
# the input. read `help read` to get more information about this command.
read -n 1 -p '? ' mychoice
while :; do
  if [[ $mychoice =~ ^[0-9]+$ && $mychoice -gt 0 && $mychoice -lt $max_number_selection ]]; then
    echo
    let mychoice--
    echo "You chose ${array_entry_list[$mychoice]}"
    break
  fi
  echo
  read -n 1 -p 'guess again? ' mychoice
done
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Menu driven scripts

Post by Termy »

user6c57b8 wrote: Wed May 25, 2022 6:11 pm [...]
Don't forget to or die("$!") on close(), in PERL. You need to declare the $array_entry_list array variable as empty, to protect it from the environment, because you're pushing to it. You don't need to use the let builtin in BASH here, you can directly perform arithmetic on a variable, including the assignment, with, for example (( num++ )).

I might be missing something here, but why are you read-ing from a new (9) file descriptor, redirecting to the loop and read, instead of just redirecting the file's contents directly into the while loop? BTW, you can specify a file descriptor to read with the -u flag. I'm also confused as to why you're echo-ing in the process substitution.

Line 16 can be done with BASH itself, without bringing PERL into a BASH script. I love PERL, but not that much. :P The PERL formatting only serves to rather frustratingly obfuscate the code, for example:

Code: Select all

perl -e 'open(my $f, "food-list.txt") or die $!;undef $/;$_=<$f>;close($f);s/<!--.*?-->//sg;s/^\s+//sg;s/\s+$//gm;s/\n//gs;print;')
You don't need PERL to slurp a file. You can read from a file in various ways in BASH, but here's a simple and effective way:

Code: Select all

NAME=`< FILE`
Where it essentially dumps the contents of the file into the shell variable. As for removing the HTML-like comments, that could trivially be done with BASH, as you've more or less demonstrated (with pattern substitution, but also with a little bit of logic). As can the other operations, while also being much clearer and much more efficient. It might be preferable to just use the usual hash character for comments, since it's familiar to the OP and would reduce unneeded complexity of your code.

I do like the idea of tags for the colors; that's a cool idea.
I'm also Terminalforlife on GitHub.
user6c57b8
Level 2
Level 2
Posts: 52
Joined: Mon Aug 05, 2019 1:07 pm

Re: Menu driven scripts

Post by user6c57b8 »

Termy wrote: Don't forget to or die("$!") on close(), in PERL.
There is a 99% chance of success the close() will work assuming the open() worked. Knowing this you're sure you want to add that die("$!") to close()? It feels 98%redundant. Like 99%junk-code.
Termy wrote: You need to declare the $array_entry_list array variable as empty, to protect it from the environment, because you're pushing to it.
omg you're right. That's 78% surprising. I thought the declare -a array_entry_list would've cleared it if it already existed. Huh now I realized I didn't even need declare -a array_entry_list I could just go array_entry_list=().
Termy wrote: You don't need to use the let builtin in BASH here, you can directly perform arithmetic on a variable, including the assignment, with, for example (( num++ )).
That's 13%cool.
Termy wrote: I might be missing something here, but why are you read-ing from a new (9) file descriptor, redirecting to the loop and read, instead of just redirecting the file's contents directly into the while loop?
Because if you pipe to while (read), the variables within the loop will reset their value when the loop exits:

Code: Select all

v=1
echo ignored | while read myline; do v=2; done
echo $v #stays at 1

v=1
while read myline <&9; do v=2; done 9< <(echo ignored)
echo $v #changes to 2
Termy wrote: BTW, you can specify a file descriptor to read with the -u flag. I'm also confused as to why you're echo-ing in the process substitution.
Is there a better way to read a bash variable line-by-line while preserving the variables within the while loop (when the loop exits)?
Termy wrote: Line 16 can be done with BASH itself, without bringing PERL into a BASH script.
I 13%agree. If I remade the script there would be a 13-19% chance I'd do it in BASH.
Termy wrote: The PERL formatting only serves to rather frustratingly obfuscate the code, for example:

Code: Select all

perl -e 'open(my $f, "food-list.txt") or die $!;undef $/;$_=<$f>;close($f);s/<!--.*?-->//sg;s/^\s+//sg;s/\s+$//gm;s/\n//gs;print;')
I've seen worse. lol j/k. I would've spanned the line across many lines and added comments so people don't have to stare at it for 2 hours, get a coffee, and stare at it for another 2 hours...but I feel so cool...and that's why people smoke. If I were to remake the script I would still keep it because I was thinking 90% fast for me. And plus it looks 96% cool.
Termy wrote: You don't need PERL to slurp a file. You can read from a file in various ways in BASH, but here's a simple and effective way:

Code: Select all

NAME=`< FILE`
17%sweet. Although I didn't use the PERL just to slurp a file. I used it because I was 700%familiar with the /s, /g, and /m perl regular expression modifiers. And learning the bash regex modifiers could've taken like 40 minutes to get familiar with. And I was 3-96% sure bash wouldn't be able to regex substitute as well as I needed (ie. missing modifier features).
Termy wrote:As can the other operations, while also being much clearer and much more efficient.
clearer I think 3-9%agree.
more efficient..yeah like +700% more optimized. Assuming you just mean replacing the perl commands/stuff/code with bash commands/code.
Termy wrote: It might be preferable to just use the usual hash character for comments, since it's familiar to the OP and would reduce unneeded complexity of your code.
Oh you mean the HTML comment(s)? <!-- -->?
Termy wrote: I do like the idea of tags for the colors; that's a cool idea.
Yeah I don't know how I came up with that lol.
Locked

Return to “Scripts & Bash”