home page » operating system » linux » Linux - Top down design

Linux - Top down design

 

As programs become larger and more complex, it becomes more difficult to design, code, and maintain them. For any large project, it is often a good idea to divide heavy and complex tasks into small and simple tasks. Imagine that we are trying to describe an ordinary job. A Martian is going to the market to buy food. We may describe the whole process through the following series of steps:

  • Get on the train
  • Drive to the market
  • parking
  • Buy food
  • Back in the car
  • Drive home
  • Back home

However, Martians may need more detailed information. We can further refine the sub task "parking" into these steps:

  • Find a parking space
  • Drive to the parking space
  • Shut down the engine
  • Tighten the hand brake
  • get off the car
  • Lock the car

This sub task of "turning off the engine" can be further refined into these steps, including "turning off the ignition device", "removing the ignition key", etc., until each step of the whole process of going to the market to buy food has been completely defined.

This process of determining the upper level steps first and then gradually refining these steps is called top-down design. This technique allows us to divide large and complex tasks into many small and simple tasks. Top down design is a common programming method, especially suitable for shell programming.

In this chapter, we will use the top-down design method to further develop our report generator script.

Shell function

At present, our script executes the following steps to generate this HTML document:

  • Open Web Page
  • Open Page Header
  • Set Page Title
  • Close Page Header
  • Open the main part of the page
  • Output Page Header
  • Output timestamp
  • Close Page Body
  • Close Page

For the next phase of development, we will add some additional tasks between steps 7 and 8. These will include:

  • System uptime and load. This is the running time of the system since the last shutdown or restart, and the average number of tasks currently running in processing in several time intervals.
  • Disk space. Total usage of storage devices in the system.
  • Home directory space. The amount of storage space used by each user.

If we have corresponding commands for each task, we can easily add them to our script through command replacement:

 #!/ bin/bash
 # Program to output a system information page
 TITLE = "System Information Report For $HOSTNAME "
 CURRENT_TIME = $( date + "%x %r %Z" )
 TIME_STAMP = "Generated $CURRENT_TIME , by $USER " cat << _EOF_ <HTML> <HEAD> <TITLE>$TITLE</TITLE> </HEAD> <BODY> <H1>$TITLE</H1> <P>$TIME_STAMP</P> $(report_uptime) $(report_disk_space) $(report_home_space) </BODY> </HTML> _EOF_

We can create these additional commands in two ways. We can write three scripts and place them in the directory listed in the environment variable PATH, or we can embed these scripts into our programs as shell functions. As we mentioned before, shell functions are "microscripts" located in other scripts as autonomous programs. Shell functions have two syntax forms:

 function name { commands return } and name () { commands return }

Here, name is the function name, and commands are a series of commands included in the function.

The two forms are equivalent and can be used alternately. Next, we will view a script that explains how to use shell functions:

 1     #!/ bin/bash two 3     # Shell function demo four 5     function funct { 6       echo "Step 2" 7       return 8     } nine 10     # Main program starts here eleven 12     echo "Step 1" 13     funct 14     echo "Step 3"

As the shell reads the script, it skips lines 1 through 11 of code, because these text lines consist of comments and function definitions. Starting from line 12, there is an echo command. Line 13 will call the shell function funct, and then the shell will execute this function just like other commands. In this way, the program control will be transferred to the sixth line and the second echo command will be executed. Then execute line 7. The return command terminates the function and gives control to the code after the function call (line 14), thus executing the last echo command. Note that in order to make the function call recognized as a shell function rather than being interpreted as the name of an external program, the shell function definition must appear before the function call in the script.

We will add the smallest shell function definition to the script:

 #!/ bin/bash
 # Program to output a system information page
 TITLE = "System Information Report For $HOSTNAME "
 CURRENT_TIME = $( date + "%x %r %Z" )
 TIME_STAMP = "Generated $CURRENT_TIME , by $USER " report_uptime ()  {
     return
 } report_disk_space ()  {
     return
 } report_home_space ()  {
     return
 } cat << _EOF_ <HTML> <HEAD> <TITLE>$TITLE</TITLE> </HEAD> <BODY> <H1>$TITLE</H1> <P>$TIME_STAMP</P> $(report_uptime) $(report_disk_space) $(report_home_space) </BODY> </HTML> _EOF_

Shell functions are named according to the same rules as variables. A function must contain at least one command. The return command (optional) meets the requirements.

local variable

In the script we have written so far, all variables (including constants) are global variables. Global variables remain throughout the program. This is good for many things, but sometimes it complicates the use of shell functions. In shell functions, local variables are often expected. Local variables can only be used in the shell function that defines them, and once the shell function is executed, they will not exist.

The names of local variables that can be used by programmers with local variables can be the same as those of existing variables. These variables can be global variables or local variables in other shell functions, but there is no need to worry about potential name conflicts.

Here is an example script that explains how to define and use local variables:

 #!/ bin/bash
 # local-vars: script to demonstrate local variables
 foo = zero # global variable foo funct_1 ()  {
     local foo # variable foo local to funct_1
     foo = one echo  "funct_1: foo = $foo "
 } funct_2 ()  {
     local foo # variable foo local to funct_2
     foo = two echo  "funct_2: foo = $foo "
 }
 echo  "global:  foo = $foo " funct_1 echo  "global: foo = $foo " funct_2 echo  "global: foo = $foo "

As we can see, local variables are defined by prefixing the variable name with the word local. This creates a variable that only works on the shell function where it is located. Outside the shell function, this variable no longer exists. When we run this script, we will see the following results:

 [ me@linuxbox  ~]$ local-vars global:  foo = 0 funct_1: foo = 1 global:  foo = 0 funct_2: foo = 2 global:  foo = 0

We see that the assignment of the local variable foo in the two shell functions does not affect the value of the variable foo defined outside the function.

This function allows shell functions to maintain their independence from each other and the script in which they are located. This is very valuable because it helps prevent interference between various parts of the program. In this way, shell functions can also be migrated. In other words, shell functions can be cut and pasted between scripts as required.

Keep script running

When developing a program, it is very useful to keep the program executable. By doing this and testing frequently, we can detect errors early in the program development process. This will make debugging problems much easier. For example, if we run the program, make a small change, and then run the program again, and finally find a problem, it is very likely that the latest change is the source of the problem. By adding null functions, which programmers call stubs, we can prove the logic flow of the program at an early stage. When building a stub, it is a good idea to include some code that provides feedback information for programmers. This information shows the logic flow being executed. Now let's take a look at the output of our script:

 [ me@linuxbox  ~]$ sys_info_page <HTML> <HEAD> <TITLE>System Information Report For twin2</TITLE> </HEAD> <BODY> <H1>System Information Report For linuxbox</H1> <P>Generated 03/19/2009 04:02:10 PM EDT, by me</P> </BODY> </HTML>

We see that there are some blank lines in the output result after the timestamp, but we cannot determine the cause of these blank lines. If we modify these functions to include some feedback information:

 report_uptime () { echo "Function report_uptime executed." return } report_disk_space () { echo "Function report_disk_space executed." return } report_home_space () { echo "Function report_home_space executed." return }

Then run the script again:

 [ me@linuxbox  ~]$ sys_info_page <HTML> <HEAD> <TITLE>System Information Report For linuxbox</TITLE> </HEAD> <BODY> <H1>System Information Report For linuxbox</H1> <P>Generated 03/20/2009 05:17:26 AM EDT, by me</P> Function report_uptime executed. Function report_disk_space executed. Function report_home_space executed. </BODY> </HTML>

Now we see that, in fact, three functions are executed.

Our function framework is in place and working. It's time to update some function code. First, the report_uptime function:

 report_uptime () { cat <<- _EOF_ <H2>System Uptime</H2> <PRE>$(uptime)</PRE> _EOF_ return }

The code is quite straightforward. We use a here document to output the title and the output result of the uptime command. The command result is surrounded by the<PRE>tag in order to maintain the output format of the command. The report_disk_space function is similar to:

 report_disk_space () { cat <<- _EOF_ <H2>Disk Space Utilization</H2> <PRE>$(df -h)</PRE> _EOF_ return }

This function uses the df - h command to determine the amount of disk space. Finally, we will build the report_home_space function:

 report_home_space () { cat <<- _EOF_ <H2>Home Space Utilization</H2> <PRE>$(du -sh /home/*)</PRE> _EOF_ return }

We use the du command with the - sh option to complete this task. However, this is not a complete solution to this problem. Although it works on some systems (such as Ubuntu), it does not work on other systems. This is because many systems will set the permissions of home directories to prevent other users from reading them. This is a reasonable security measure. In these systems, the report_home_space function only works when our script is executed with superuser privileges. A better solution is to allow scripts to adjust their behavior according to the user's permissions. We will discuss this in the next chapter.

Shell functions in your. bashrc file

Shell functions perfectly replace aliases, and are actually the preferred way to create personal commands. Aliases are very limited to the types of commands and the shell functions they support. However, shell functions allow anything that can be scripted. For example, if we like the report_disk_space shell function developed for our script, we can create a similar function named ds for our. bashrc file:

 ds () { echo “Disk Space Utilization For $HOSTNAME” df -h }

Summary

In this chapter, we introduced a common programming method, called top-down design, and we know how to use shell functions to complete the task of gradually refining as required. We also know how to use local variables to make shell functions independent of other functions and other parts of the program in which they are located. This makes it possible to write shell functions in a portable way and reuse them by placing them in multiple programs; It saves a lot of time.

Extended Reading

Original link: Linux - Top down design , Please indicate the source for reprinting!

fabulous one