Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

A Rabbit Trail: Menu-Driven Bash Script -Part III

DZone's Guide to

A Rabbit Trail: Menu-Driven Bash Script -Part III

Today's article is about job control and graceful shutdown of the database server when hitting CTRL-C on the keyboard.

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

Introduction

In Part 2 of this "Rabbit Trail", we added a new feature to this new command-line, menu-driven tool we are developing.  This actvity is on a parallel track with our main journey of exploring Web Clients / Web Services.

The more we enhance this tool, the more helpful to us with our main project-work.

Another way of looking at this is that it gives us motivation to explore Bash shell scripting that can be applied to many situations.

This article's main point today is job control and graceful shutdown of the database server when hitting CTRL-C on the keyboard.

More Database Features

Our first two functions of the script (Parts 1 and 2 articles) was to be able to:

  • start / stop the MySQL database server
  • view a list of databases

In the time between Part 2 and this article, I've added the ability to:

  • create a new database
  • select from the list of databases
  • drop a database
  • show tables within that selected databases
  • drop a table
  • show all data in the selected table

I am not going to discuss the details of the above.  The new items all follow along the same lines and use very similar commands and script structure as the one we throughly discussed in Part 2.

You should be able to use Part 2's information to compare against the new script portions.

Here is the latest script:

#!/usr/bin/bash

#######################################################################
function shutdown_mysqld {
#######################################################################
echo;echo;echo;
echo "============================================================";
echo "Shutting down mysqld....";
echo "============================================================";
echo;echo;echo;
mysqladmin.exe -h 127.0.0.1 -u root shutdown

#######################################################################
} # end function shutdown_mysqld
#######################################################################


#######################################################################
function _exit {
#######################################################################
echo;echo;echo;
echo "============================================================";
echo "Start _exit()....";
echo "============================================================";
echo;echo;echo;

shutdown_mysqld;

exit $1;

#######################################################################
} # end function _exit
#######################################################################


#######################################################################
function start_mysqld {
#######################################################################
echo;echo;echo;
echo "============================================================";
echo "MAIN: Starting mysqld script....";
echo "============================================================";
echo;echo;echo;

mysqld --console &

#######################################################################
} # end function _exit
#######################################################################


#######################################################################
function view_rows {
#######################################################################
selected_database=$1;
selected_table=$2;

if [ "$selected_database" = "" ] || [ "$selected_table" = "" ];
then
    echo
    echo "ERROR: no database or table param passed to view_rows func";
    echo
    return 1;
fi

mysql -t -h 127.0.0.1 -u root << MYSQL 
use $selected_database;
select * from $selected_table;
MYSQL


return 0;
#######################################################################
} # end function create_database
#######################################################################

#######################################################################
function drop_table {
#######################################################################
selected_database=$1;
selected_table=$2;
while [ 1 ];
do

table_dropped=$(mysql -h 127.0.0.1 -u root << MYSQL
use $selected_database;
drop table $selected_table;
MYSQL
)
    echo $table_dropped;

    break;

done;   


return 0;
#######################################################################
} # end function drop_database
#######################################################################

#######################################################################
function act_on_selected_table {
#######################################################################
selected_database=$1;
selected_table_idx=$2;
selected_table="";
while [ 1 ];
do

tables=$(mysql -h 127.0.0.1 -u root << MYSQL
use $selected_database;
show tables;
MYSQL
)
    tables=$(echo $tables|sed "s/Tables_in_$selected_database//");

    i=1;
    for table in $tables;
    do
        if [ $i -eq $selected_table_idx ];
        then
            selected_table=$table;
        fi
        i=$((i+1));
    done

    echo; echo;
    echo "|=============================================|"
    echo "|MySQL Databases Menu:$selected_table"
    echo "|=============================================|"
    echo "|vr) View Rows                                |"
    echo "|dt) Drop Table                               |"
    echo "|=============================================|"
    echo "|b) Back                                      |"
    echo "|e) Exit This Tool                            |"
    echo "|=============================================|"


    menu_choice="";
    echo;
    read -p "Please make a selection:" menu_choice

    case $menu_choice in
        vr)
            view_rows $selected_database $selected_table
            ;;
        dt)
            drop_table $selected_database $selected_table
            break;
            ;;
        b)
            break;
            ;;
        e)
            _exit 0;
            ;;
        *)
            echo;echo;
            echo "Invalid selection: $menu_choice"
            echo;echo;
            ;;
    esac
done

return 0;
#######################################################################
} # end function act_on_selected_database
#######################################################################

#######################################################################
function show_tables {
#######################################################################
selected_database=$1;
while [ 1 ];
do

tables=$(mysql -h 127.0.0.1 -u root << MYSQL
use $selected_database;
show tables;
MYSQL
)

tables=$(echo $tables | sed "s/Tables_in_$selected_database//");


    echo; echo;
    echo "|=============================================|"
    echo "|MySQL Databases Menu:$selected_database"
    echo "|=============================================|"

    i=1;
    for table in $tables;
    do
    echo "| $i) $table"
    i=$((i+1));
    done

    echo "|=============================================|"
    echo "|b) Back                                      |"
    echo "|e) Exit This Tool                            |"
    echo "|=============================================|"


    menu_choice="";
    echo;
    read -p "Please make a selection:" menu_choice

    case $menu_choice in
        [0-9]*)
            act_on_selected_table $selected_database $menu_choice
            ;;
        b)
            break;
            ;;
        e)
            _exit 0;
            ;;
        *)
            echo;echo;
            echo "Invalid selection: $menu_choice"
            echo;echo;
            ;;
    esac
done

return 0;
#######################################################################
} # end function act_on_selected_database
#######################################################################


#######################################################################
function create_database {
#######################################################################
new_database="";
while [ 1 ];
do
    while [ "$new_database" = "" ];
    do
        read -p "New Database Name?: " new_database;
    done;

# a HERE DOCUMENT, output into a local Bash variable 'databases'.
database_created=$(mysql -h 127.0.0.1 -u root << MYSQL
create database $new_database;
MYSQL
)
    echo $database_created;

    break;

done;   


return 0;
#######################################################################
} # end function create_database
#######################################################################

#######################################################################
function drop_database {
#######################################################################
selected_database=$1;
while [ 1 ];
do

# a HERE DOCUMENT, output into a local Bash variable 'databases'.
database_dropped=$(mysql -h 127.0.0.1 -u root << MYSQL
drop database $selected_database;
MYSQL
)
    echo $database_dropped;

    break;

done;   


return 0;
#######################################################################
} # end function drop_database
#######################################################################

#######################################################################
function act_on_selected_database {
#######################################################################
selected_database_idx=$1;
selected_database="";
while [ 1 ];
do

# a HERE DOCUMENT, output into a local Bash variable 'databases'.
databases=$(mysql -h 127.0.0.1 -u root << MYSQL
show databases;
MYSQL
)
    databases=$(echo $databases|sed 's/Database//');

    ### for-loop to display our database list, numbered.
    i=1;
    for db in $databases;
    do
        if [ $i -eq $selected_database_idx ];
        then
            selected_database=$db;
        fi
        i=$((i+1));
    done


    echo; echo;
    echo "|=============================================|"
    echo "|MySQL Databases Menu:$selected_database"
    echo "|=============================================|"
    echo "|st) Show Tables                              |"
    echo "|dd) Drop Database                            |"
    echo "|=============================================|"
    echo "|b) Back                                      |"
    echo "|e) Exit This Tool                            |"
    echo "|=============================================|"


    menu_choice="";
    echo;
    read -p "Please make a selection:" menu_choice

    case $menu_choice in
        st)
            show_tables $selected_database
            ;;
        dd)
            drop_database $selected_database
            break;
            ;;
        b)
            break;
            ;;
        e)
            _exit 0;
            ;;
        *)
            echo;echo;
            echo "Invalid selection: $menu_choice"
            echo;echo;
            ;;
    esac
done

return 0;
#######################################################################
} # end function act_on_selected_database
#######################################################################

#######################################################################
function mysql_show_databases_menu {
#######################################################################
while [ 1 ];
do

# a HERE DOCUMENT, output into a local Bash variable 'databases'.
databases=$(mysql -h 127.0.0.1 -u root << MYSQL
show databases;
MYSQL
)

    databases=$(echo $databases|sed 's/Database//');

    echo; echo;
    echo "|=============================================|"
    echo "|             MySQL Databases Menu            |"
    echo "|=============================================|"

    ### for-loop to display our database list, numbered.
    i=1;
    for db in $databases;
    do
    echo "| $i) $db"
    i=$((i+1));
    done

    echo "|                                             |"
    echo "| cnd) Create New Database                    |"
    echo "|=============================================|"
    echo "|b) Back                                      |"
    echo "|e) Exit This Tool                            |"
    echo "|=============================================|"


    menu_choice="";
    echo;
    read -p "Please make a selection:" menu_choice

    case $menu_choice in
        [0-9]*)
            act_on_selected_database $menu_choice
            ;;
        b)
            break;
            ;;
        e)
            _exit 0;
            ;;
        cnd)
            create_database
            ;;
        *)
            echo;echo;
            echo "Invalid selection: $menu_choice"
            echo;echo;
            ;;
    esac
done

return 0;
#######################################################################
} # end function mysql_show_databases_menu
#######################################################################

#######################################################################
function mysql_menu {
#######################################################################
while [ 1 ];
do
    echo; echo;
    echo "|=============================================|"
    echo "|             MySQL Menu                      |"
    echo "|=============================================|"
    echo "|st) Start MySQL                              |"
    echo "|sh) Shutdown  MySQL                          |"
    echo "|                                             |"
    echo "|sd) Show Databases                           |"
    echo "|=============================================|"
    echo "|b) Back                                      |"
    echo "|e) Exit This Tool                            |"
    echo "|=============================================|"

    menu_choice="";
    echo;
    read -p "Please make a selection:" menu_choice

    case $menu_choice in
        st)
            start_mysqld
            ;;
        sh)
            shutdown_mysqld
            ;;
        sd)
            mysql_show_databases_menu
            ;;
        b)
            break;
            ;;
        e)
            _exit 0;
            ;;
        *)
            echo;echo;
            echo "Invalid selection: $menu_choice"
            echo;echo;
            ;;
    esac
done

return 0;
#######################################################################
} # end function mysql_menu
#######################################################################

#######################################################################
function main_menu {
#######################################################################
while [ 1 ];
do
    echo; echo;
    echo "|=============================================|"
    echo "|              Main Menu                      |"
    echo "|=============================================|"
    echo "|my) MySQL Utiliies                           |"
    echo "|=============================================|"
    echo "|e) Exit                                      |"
    echo "|=============================================|"

    menu_choice="";
    echo;
    read -p "Please make a selection:" menu_choice

    case $menu_choice in

        my)
            mysql_menu
            ;;
        e)
            break;
            ;;
        *)
            echo;echo;
            echo "Invalid selection: $menu_choice"
            echo;echo;
            ;;
    esac
done

return 0;

#######################################################################
} # end function main_menu
#######################################################################




#######################################################################
#######################################################################
#######################################################################
# MAIN MENU - start of script
#######################################################################
#######################################################################
#######################################################################

main_menu;

_exit 0;

If you compare the above version with the latest script from article Part 2,  you'll notice that the start and stop has turned into functions.   And everywhere there used to be an exit , there is now an _exit .  The new _exit function calls the new shutdown function.

Adding Graceful Database Shutdown

At the moment, if we use the script as it is intended, and only exit it via the "e" (exit selection), then we're good.   The database will shutdown gracefully.

However, I for one, hardly ever do that.  I'm impatient.  So I would rather just hit CTRL-C.

If you try that now when running this script, and you re-start MySQL later, you may find that the output says something about having to recover from a crash.

So, let's not leave things this way.

Job Control

Before we get to the new parts of the script that will handle CTRL-C, though, let's discuss and try out some job control in Cygwin window (no scripts).    And to understand job control, you need some understanding of processes.

Let's open a new Cygwin window, and make sure it is the ONLY Cygwin window running.

Image title

Cygwin is a sort-of attempt at a  Linux / Unix environment. It has the concept of shells.  A shell is what allows you to enter commands, and view output, etc.   A shell will also run scripts.  A shell can (and does) spawn new shells (child or sub-shells).

Every shell, sub-shell, etc, is a separately running process.  That means it has a PID.  (Process ID).

And, it belongs to a parent.  Thus, it has its PPID (Parent Process ID).

In the above window screenshot, we entered a   ps -ef  . It is a long printout of all Cygwin processes that are running.  In this case, there are three.

There is a hierachy.  You can follow that if you trace through the PIDs and PPIDs.

Here is the path from parents to children, for the above example:

 /usr/bin/mintty   --->  /usr/bin/bash  --->   /usr/bin/ps 

Or, if we do the same thing, but using the process IDs:

15204  --->  20848  ---> 16740

The  mintty  is the actual window process.  (Later) we can add color; we can change font; we can change the window size; all of that is done with mintty .

The  bash  shell is what allows us to communicate (enter commands; view output).

The  ps  command is what gave us the list of running Cygwin processes.

Foreground and Background Processes

A foreground process is any command or script that is tied and in control of the window's input and ouput ( I am being a bit fast and loose here with the definitions).

So far, everything we have done in Parts 1, 2, and this one, are all foreground processes, with one exception.  In the script, where we start mysqld server, we turned it into a background process.

 $(mysqld --console) & 

That ampersand turns it into a background process.   And that is the reason we were able to continue with our menu-driven script.   By the way, the  $(...)  is probably not necessary for our purposes. More on that later.

Disowning

Remember I mentioned the idea that child processes have a parent process.  There's also the idea that a child process can be disowned by the parent. Sometimes that is a good thing. It means it can keep running even if the parent ends.

Let's see if we can demonstrate all that we've discussed so far.

In the above window, we saw three processes.  Now let's start up MySQL database server, and then we will do another process id listing.   We will start the  mysqld  in the background, and that means we can keep control of the Cygwin window.  We will wait until  mysqld  has finished the start up output, then we just hit <ENTER> to get back our prompt.

Image title

Above is prior to the start.  Then we hit <ENTER> and wait:

Image title

Once we see output has ceased and MySQL is up, we hit <ENTER> again, and do that  ps -ef  command again.  We should see the three processes as before (mintty, bash, ps), but we should also see a new one, the mysqld .

Image title

So, mysqld is running, however we have control of the window and the prompt is responsive to us.

Notice that the  mintty  process has a parent ID (PPID) of  "1".

Notice that the  mysqld  process has a parent ID that is the (mintty --> bash)'s ID.  Mintty is the parent (or grandparent).

Now, let's open another Cygwin window, and we are going to monitor the  mysqld  process.  And then while we are monitoring it, we will exit out of the first window (that started everything).

What we should see is that  mysqld  process will change  - its parent ID will go from 684, to 1.

What happens when we close or exit the first window, is that the mintty / bash pair for that window end, but mysqld continues on.

Image title

Without too much explanation, the above command scriptlet is a while-loop, with a delay of 5 seconds in every iteration, and it takes the output of the long ps listing, and just displays the process we're interested in.  Here we go:

Image title

As expected, when we exit from the original parent window that started the MySQL server, the monitoring window shows that its parent ID changes. And that it continues to run.

Furthermore, if we exit the monitor window, we would normally assume that all Cygwin processes are done.  But we open the Windows Task Manager, and we see one  bash  process:

Image title

If we open a new Cygwin window and do another process-listing, we will see the running MySQL server.

Time to Add To Script

I think we are ready.

A key point here:   job control (what we just did) is DISABLED (usually) by default, when Cygwin (Bash) is running a script (aka batch-mode as opposed to interactive-mode).

In order for us to have our script behave like what we've been doing, we must make sure we have job control enabled.

#!/usr/bin/bash

set -m

Right at the start of the script, we add the  set -m  .  That enables job control.

#!/usr/bin/bash

set -m

trap _trap_ctrl_c INT;

Then, we add the ability to catch (trap) when a user hits the CTRL-C on the keyboard.

The above statement means we have a new function that will run in the case of CTRL-C. Here is the new function:

#######################################################################
_hit_ctrl_c=no;
function _trap_ctrl_c {
#######################################################################


echo;echo;echo;
echo "============================================================";
echo "Start _trap_ctrl_c()....";
echo "============================================================";
echo;echo;echo;
if [ "$_hit_ctrl_c" = "no" ];
then
    _hit_ctrl_c=yes;
    _exit;
fi

#######################################################################
} # end function _exit
#######################################################################

The function above isn't that complicated.  Whenever CTRL-C is hit, this function runs. When it does, it just calls the pre-existing  _exit  function that you are aware of already.

The rest of the function code is just to display what we are doing, and also to make sure that the function doesn't run more than once (say that we hit the CTRL-C multiple times).

That's not a guarrantee.

Latest Code

Here is the latest script.

Stay tuned to next time as we enhance this script!

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
cygwin ,bash ,mysql ,nohup ,web dev

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}