Listr – Automatic List Creation for Bash

Bash scripting is a feature of many Linux distributions. This built in scripting language allows programmers to get behind the scenes with their Linux distributions and automate repetitive or complex tasks.

I’m nostalgic over the old school feel of dialogue-based menus. I personally love a terminal program that uses lists to execute operations. Building lists in Bash can be tedious. One of the more meta applications of scripting include making scripts that write other scripts. These kinds of devoloper operations help cut costs and make work more effective with minimal effort in the future.

03.png

This is the second bash script I’ve ever written. I’m by no means a professional programmer. Some of the features are unfinished. Listr is still a work in progress. This is a learning experience for me, both in writing code and documenting its functionality. Any constructive criticism is welcome.


#!/bin/bash
##listr - automated list creation
##Josh Dean
##2017

##listr
idt=" " ##ident
flowvar=0
activedir=$testdir

##menu_main_cfg
mm1="Setup Wizard"
mm2="Directory Options"
mm3="Number of Options"
mm4=

unset inc
unset list_name
unset current_dir
unset previous_dir
echo "listr - Automated Menu Building"
echo

function menu_main {
##possible to unset all variables?
previous_dir=$current_dir
current_dir=$list_funcname
menu_main_opt=("$mm1" "$mm2" "$mm3" "$mm4" "Quit")
echo "Main Menu"
select opt in "${menu_main_opt[@]}"
do
echo
case $opt in
 ##setup wizard
 "$mm1")
 setup_wizard
 ;;
 ##Directory Options
 "$mm2")
 menu_dir_opts
 ;;
 ##How many options
 "$mm3")
 list_opts
 ;;
 ##
 "$mm4")
 echo "$mm4"
 placehold $srvr
 changeoperation $srvr server "$mm5" srvr
 ;;
 "Quit")
 exit
 ;;
 *) echo invalid option;;
 esac
 echo
 menu_main
 done
}

function list_header {
 echo "Exclude standalone header and footer? (y/n)"
 read ans
 if [ $ans = "y" ]; then
 :
 else
 flow_var=1
 dup_check
 echo "#!/bin/bash" >> $opdir
 echo >> $opdir
 fi
 echo "##$list_name" >> $opdir
 echo "##$list_name" config"" >> $opdir
}

function list_name {
 echo "Enter list name:"
 read list_name
 list_name=${list_name// /_}
 echo "Name set to:"
 echo $list_name
 update_opdir
 list_funcname="menu_""$list_name"
}

function list_opts {
echo "How many options in list?"
opt_num_int_chk
echo
echo "Creating list with $list_opts_num" "options:"
unset list_name_opt
for ((i=1;i<=$list_opts_num;++i)) do
 echo "Option $i:"
 read opt
 echo "$list_name$i=\"$opt\"" >> $opdir
 list_name_opt+=($list_name$i)
done
echo
echo "Include back option? (y/n)"
read ans
if [ $ans = "y" ]; then
 list_name_opt+=("Back")
fi
echo "Include quit option? (y/n)"
read ans
if [ $ans = "y" ]; then
 list_name_opt+=("Quit")
fi
}

function opt_num_int_chk {
read list_opts_num
if ! [[ "$list_opts_num" =~ ^[0-9]+$ ]]; then
 echo "Please enter an integer"
 list_opts
fi
}

function list_array {
echo echo >> $opdir
echo "function "$list_funcname" {" >> $opdir
echo "previous_dir=""$""current_dir" >> $opdir
echo "current_dir=$""$list_funcname" >> $opdir
echo "Enter menu title:"
read menu_title
echo "echo "\"$menu_title\" >> $opdir
echo -n $list_name"_opt" >> $opdir
echo -n "=" >> $opdir
echo -n "(" >> $opdir
tmp=0
for i in ${list_name_opt[@]}; do ##might need another $ for list_name

 if [ "$i" = "Back" ]; then
 echo -n " "\"$i\" >> $opdir
 elif [ "$i" = "Quit" ]; then
 echo -n " "\"$i\" >> $opdir
 else
 if [ "$tmp" -gt "0" ]; then
 echo -n " "\""$"$i\" >> $opdir
 else
 echo -n \""$"$i\" >> $opdir
 tmp=1
 fi
 fi
done
echo ")" >> $opdir
}

function nested_prompt {
echo "Will this list be nested? (y/n)"
read ans
if [ $ans = "y" ]; then
 echo "Name of parent list?:"
 read previous_dir
fi
}

function list_select {
echo "select opt in ""\"""$"{$list_name"_opt[@]"}\""" >> $opdir
echo do >> $opdir
echo "case ""$""opt in" >> $opdir
for i in ${list_name_opt[@]}; do ##might need another $ for list_name
 echo "$idt##"$i >> $opdir
 if [ "$i" = "Back" ]; then
 echo "$idt"\"$i\"")""" >> $opdir
 echo "$idt$idt""$previous_dir" >> $opdir ##need function call
 elif [ "$i" = "Quit" ]; then
 echo "$idt"\"$i\"")""" >> $opdir
 echo "$idt$idt""break" >> $opdir ##need part message
 else
 echo "$idt"\""$"$i\"")""" >> $opdir
 echo echo >> $opdir
 echo "$idt$idt""$i""_func" >> $opdir

 fi
 echo "$idt$idt"";;" >> $opdir
done
echo "$idt""*)" >> $opdir
echo "$idt$idt""echo invalid option;;" >> $opdir
echo "esac" >> $opdir
echo "echo" >>$opdir
echo $current_dir >> $opdir
echo "done" >> $opdir
echo "}" >> $opdir
for i in ${list_name_opt[@]}; do
 if [ "$i" = "Back" ]; then
 :
 elif [ "$i" = "Quit" ]; then
 :
 else
 echo "##$i" >> $opdir
 echo "function ""$i""_func"" {" >> $opdir
 echo "echo ""$""$i" >> $opdir
 echo "echo ""\"This is placeholder text\"" >> $opdir
 echo "}" >> $opdir
 fi
done
if [ $flow_var -gt "0" ]; then
 echo >> $opdir
 echo "##flow" >> $opdir
 echo $list_funcname >> $opdir
 flow_var=0
fi
echo "Output written to $opdir"
echo
echo "Create another list?"
read ans
if [ $ans = "y" ]; then
 list_name_opt+=("Quit")
fi
}

function update_opdir {
opdir="$activedir""/""listr_""$list_name""$inc"
}

function current_opdir {
echo "Operational directory set to $opdir"
}

function update_testdir {
read testdir
}

function update_workdir {
read workdir
}

function current_test_dir {
echo "Test directory set to $testdir"
}

function current_work_dir {
echo "Working directory set to $workdir"
}

function dir_query {
current_test_dir
current_work_dir
current_opdir
}

function check_dirs {
if [ -z "$testdir" ]; then
 echo "The test directory is not set. Set it now."
 update_testdir
fi
current_test_dir
if [ -z "$workdir" ]; then
 echo "The working directory is not set. Set it now."
 update_workdir
fi
current_work_dir
update_opdir
if [ $opdir = "listr_" ]; then
 echo "Operating Path incorrect. Select active directory."
 echo "placeholder for menu"
fi
echo "Operating path set to $opdir""$""list_name"
}

function dup_check {
if [ -a $opdir ]; then
 echo "Do you want to overwrite existing file: $opdir? (y/n)"
 read ans
 if [ $ans = "y" ]; then
 rm $opdir
 else
 echo "Append output to $opdir? (y/n)"
 read ans
 if [ $ans = "y" ]; then
 :
 else
 echo "Use incremental numbering to reconcile with existing file(s)? (y/n)"
 read ans
 if [ $ans = "y" ]; then
 dup_rec
 else
 dup_check
 fi
 fi
 fi
fi
}

function dup_rec {
if [[ -e $opdir ]]; then
 i=1 ##might need to use different variable
 while [[ -e $opdir-$i ]]; do
 let i++
 done
 inc="-$i"
 update_opdir
 echo
 current_opdir
fi
}

function setup_wizard {
echo "$mm1"
list_name
echo
list_header
echo
nested_prompt
echo
list_opts
list_array
echo
list_select
}

##dir_opts
##dir_opts config
dir_opts1="Display Current Paths"
dir_opts2="Set Working Directory"
dir_opts3="Set Test Directory"
dir_opts4="Toggle Active Directory"
dir_opts5="Unset All Directory Variables"

function menu_dir_opts {
dir_opts_opt=("$dir_opts1" "$dir_opts2" "$dir_opts3" "$dir_opts4" "$dir_opts5" "Back" "Quit")
echo "Directory Options"
select opt in "${dir_opts_opt[@]}"
do
case $opt in
 ##dir_opts1
 "$dir_opts1")
 echo
 dir_opts1_func
 ;;
 ##dir_opts2
 "$dir_opts2")
 echo
 dir_opts2_func
 ;;
 ##dir_opts3
 "$dir_opts3")
 echo
 dir_opts3_func
 ;;
 ##dir_opts4
 "$dir_opts4")
 echo
 dir_opts4_func
 ;;
 ##dir_opts5
 "$dir_opts5")
 echo
 dir_opts5_func
 ;;
 ##Back
 "Back")
 menu_main
 ;;
 ##Quit
 "Quit")
 exit
 ;;
 *)
 echo invalid option;;
esac
echo
menu_dir_opts
done
}

function dir_opts1_func {
echo $dir_opts1
dir_query
}

function dir_opts2_func {
echo $dir_opts2
update_workdir
current_work_dir
}

function dir_opts3_func {
echo $dir_opts3
update_testdir
current_test_dir
}

function dir_opts4_func {
echo $dir_opts4
menu_dir_toggle
}

function dir_opts5_func {
echo $dir_opts5
unset workdir
unset testdir
unset activedir
unset

}

##dir_toggle
##dir_toggle config
dir_toggle1="Use Working Directory"
dir_toggle2="Use Test Directory"

function menu_dir_toggle {
dir_toggle_opt=("$dir_toggle1" "$dir_toggle2" "Back" "Quit")
select opt in "${dir_toggle_opt[@]}"
do
case $opt in
 ##Use Working Directory
 "$dir_toggle1")
 echo
 dir_toggle1_func
 ;;
 ##Use Test Directory
 "$dir_toggle2")
 echo
 dir_toggle2_func
 ;;
 ##Back
 "Back")
 menu_dir_opts
 ;;
 ##Quit
 "Quit")
 exit
 ;;
 *)
 echo invalid option;;
esac
update_opdir
echo "Current operational directory:"
current_opdir
echo
echo $current_dir
done
}

function dir_toggle1_func {
echo $dir_toggle1
activedir=$workdir
}

function dir_toggle2_func {
echo $dir_toggle2
activedir=$workdir
}

##flow
check_dirs
echo
menu_main

My systems administration philosophy is that everythihng should be automated. Nothing should be too sacred to automate. In this way I’m a windfall for employers. My first objective is always automating my own objectives.

The script is called listr and it queries the user about what kind of lists need to be created and writes them into a text file so they can be implemented in other scripts. The solution is editable and scableable, allowing the users the easily edit lists that have been written with listr. This is the second “major” script I’ve written and I’m enjoying the logical predictability programming offers. If you put garbage into a program you get garbage out, reliably, everytime. If you’re logically consistent with the syntax you can do anything.

The final product is a program that can be transferred across Linux platforms to create lists on the fly. Let’s take a look at the code one line at a time.

Once the program was functional I was able to continue writing the additional features. This is congruent with the end goal: Efficiency and functionality.

Let’s take a quick look at the program in action. The program is a command line application so we launch it straight from the Bash console using the source command. This reads the script and runs it.
Since this is a first run, we’ll have to set the test and working directories. Listr can use two directories, “test” and “working”. These could be renamed to anything. The purpose of this functionality is to be able to work in two seperate directories if there’s a need to seperate the output.

01.png

We’ll set the demo test and working directories to a demo folder. Since setting these variables depends on a first run, once they’re set the program will launch into the main menu on subsequent uses. Upon subsequent runs, the working directory, test directory, and the selected operating path will be stated.
The main menu consists of 3 options, an additional placeholder option for additional features in the future, and a quit option that terminates the program.
Entering a number brings up the corresponding submenu. Let’s take a look at the setup wizard. This walks through the list creation process, legibly formats the code, and exports it to the operating directory.
The setup wizard begins by asking the user for a list name. For this example we’ll enter “Greetings”. Next, the process asks whether this lists needs to exclude a standalone header or footer. If the list is being appended to another program, a header and footer is not needed. For this example, we’ll run the list as a standalone program and choose to include these features.
Next we’re prompted to specify whether the list will be nested or not. This affects the back button. In this instance we are not.
The next step, we specify how many options will be included in the menu. In this case we’ll use 4. Next we’ll input each of the options in the menu. For our “Greetings” example, we’ll input four different greetings.
The next two prompts ask the user if they’d like to include a “Back” and “Quit” option. Since our example isn’t nested, we’ll only include the “Quit” option.]
After the navigation options, we’re prompted for a menu title. We’ll keep it simple and just name it “greeting”.

05.png
At this point our list has been created and exported to the operating directory. The user is then asked if they’d like to create another list. The process is repeatable as many times as necessary.

07.png
Let’s take look at the list listr has just created.
The bash header is included because we choose to include standalone headers. The program is ready to run out of the box (almost).
Comments are automatically written to make the code more legible. The configuration menu is provided at the top. By changing the options here, the menu can be tweaked without retooling the whole program. The Greetings_opt array will need to include any new entries, as well as the actual options in the menu. I some situations it would be faster to run the setup wizard and create another menu.
Excuse the excessive echoes that have been written to the file. This seems to be a configuration error on the terminal I’m using. The program still has a fair share of bugs. I thought it would be important to publish this as soon as possible to get experience documenting a program.
The menu function is automatically defined, named, and implemented.
The previous_dir and current_dir variables are a work in progress. The intention is to make the menu titles and back button easier to automate and implement.
The menu itself is formatted out of the box.
For easier editing, the menu options call their associated functions which are written below the menu function. Out of the box, the options have placeholder text assigned to them. For our greetings example, let’s change each one to the representative greetings. This is simple enough, requiring changes to 4 lines of the scripts in this example.
At the bottom, commented under “flow” is the original function call. This is included because we chose standalone header and footers. All the above functions are just definitions. This is the actual bit the begins the program. I’m not sure what the formal name for this part of the program would be. Excuse my lexicon if it’s wildly incorrect.
Let’s run our greeting scripts and see how it turned out.
Works like a charm! This list is ready to run as a standalone program or be implemented into another program (without the headers and footers).

12.png

Alongside the setup wizard in the listr main menu are a few additional directory options if you want to change directories after the first run or toggle between the working and test directory. This is still a work in progress.
The future plan for listr might include writing individual components of the list (just the header, just the config, just the options, etc.).
I hope someone can find use for this program. I had a great time writing it. I learned a lot about automating the writing text to files and formatting an export in the syntax of the scripting language. Here’s to hoping for more successful scripting in the future.

 

Below is the greeting menu listr created in this example. Again, please excuse the excess echoes.


#!/bin/bash

##Greetings
##Greetings config
Greetings1="Hello"
Greetings2="Good Morning"
Greetings3="Good Evening"
Greetings4="Sup"
echo
function menu_Greetings {
previous_dir=$current_dir
current_dir=$menu_Greetings
echo "greeting"
Greetings_opt=("$Greetings1" "$Greetings2" "$Greetings3" "$Greetings4" "Quit")
select opt in "${Greetings_opt[@]}"
do
case $opt in
##Greetings1
"$Greetings1")
echo
Greetings1_func
;;
##Greetings2
"$Greetings2")
echo
Greetings2_func
;;
##Greetings3
"$Greetings3")
echo
Greetings3_func
;;
##Greetings4
"$Greetings4")
echo
Greetings4_func
;;
##Quit
"Quit")
break
;;
*)
echo invalid option;;
esac
echo
menu_Greetings
done
}
##Greetings1
function Greetings1_func {
echo $Greetings1
echo "Hello!"
}
##Greetings2
function Greetings2_func {
echo $Greetings2
echo "Good morning!"
}
##Greetings3
function Greetings3_func {
echo $Greetings3
echo "Good evening!"
}
##Greetings4
function Greetings4_func {
echo $Greetings4
echo "Sup, dude!"
}

##flow
menu_Greetings