Making your script responsive

Tips and Tricks

By now, you know which flow control schemes are available in Bash, how they differ from each other, and the corresponding syntax. To make the most of those techniques, however, you need some other tricks.

Sooner or later, for example, you will need to let a script decide on the fly not just what to do and how many times to do it, but also at which speed it should do it. In these cases, all you need is the sleep command, with an argument that is recalculated every time. Here is how to make a for loop wait one second more at every iteration:

INTERVAL=0
for ((I=0;I<5;I++))
  do
  #some commands
  let "INTERVAL++"
  sleep $INTERVAL
  done

Another handy trick is efficiently extracting, inside a loop, the components of an element that has spaces in it. You already know that adding quotes in the right places is the only way to make this loop process the two actual customers, namely "Jane Eyre" and "John Smith," instead of four non-existing ones (Jane, Eyre, John and Smith):

for CUSTOMER in "Jane Eyre" "John Smith"
  do
  #something
  done

But what if, for every customer, you need to process the name and surname separately? One solution would be an additional, inner loop splitting each customer record into its parts. Often, however, you may do without that extra loop thanks to the set command, which assigns each substring of a list to a positional variable:

for CUSTOMER in "Jane Eyre" "John Smith"
do
 set -- $CUSTOMER
 echo "Name: $1 Surname: $2"
done

This will print lines like "Name: Jane Surname: Eyre".

Interrupting Loops

As useful as Bash control flow structures are, they would be pretty useless if they did not include ways to stop themselves. You can tell a script to interrupt the execution of a branch of code with the continue and break keywords. continue interrupts the current iteration of the loop it is in, skipping all the remaining commands in that particular loop cycle. On the other hand, break terminates the whole loop where it is located (see Listing 3).

Listing 3

The break Keyword

 

Both keywords accept an optional numeric parameter, which says upon how many levels of nested loops they act. Without arguments, break terminates only the innermost loop in which it is embedded, but break N breaks out of N loop levels. Similarly, continue N terminates all remaining iterations at its loop level and continues with the next iteration of the loop N levels above.

The most effective way I know, to understand these Bash features is to copy the little script shown in Listing 4, and then run it several times, each time commenting a different break statement or adding a numeric value to it.

Listing 4

Practice break Script

 

A Practical Example

I'll close this Bash tutorial installment with an example of flow control in real-world scripts. Listing 5 is a stripped down version of a script I actually put together a while ago to implement custom quota controls on a shared server. The high-level algorithm, shown in Figure 2, works as follows:

Listing 5

quoteguardian.sh

 

Figure 2: The high-level flow of Listing 5 prevents a partition from being completely filled by user files.
  1. It continuously checks the disk usage.
  2. If the disk is almost full, just declare an emergency and start deleting the biggest files, whoever they belong to, until usage falls below a threshold.
  3. If the disk is not full, but the situation is critical, remove one file per user, until usage returns below another threshold. For each user, delete oldest files first.

    1 Otherwise, continue monitoring disk usage.

Lines 3 to 10 define and initialize all the variables needed by the script. In this example, I have three users (dave, john, and mark) keeping their files in their own subfolders of the $H directory. Please note that a complete version of the script should not use a hardwired list of users as in line 9; instead, configure users through the $USERS array and modify the array whenever users are added or removed from the server!

The echo lines throughout the script report the disk usage and which files were deleted in each moment. They are useful to understand how the script actually works when you try it in a terminal. In a complete script, however, you would want to replace or complete the echo lines with commands that, for example, send a warning email to the system administrator!

Another repeated line of the script is the one used to calculate $DISKUSAGE. That line takes the total output of the df command and prints only its fifth column, but removes the percentage sign. Try it alone, one piece at a time, to see how each step works.

Line 12 starts the main loop. To make that loop go on forever, I put "1" as condition in the while statement, because any non-null value is always "true" by definition in Bash. While running, the script can be in one of three states corresponding to the thresholds mentioned above:

  • EMERGENCY: Disk usage is 95 percent or higher (THRESHOLD_MAX)
  • CRITICAL: Disk usage is between 85 and 95 percent
  • WATCHING: Disk usage is not higher than 85 percent (THRESHOLD_MED)

The default condition in line 64 guarantees that the script always acquires a known state, even when it begins and neither $DISKSTATUS nor $DISKUSAGE are defined yet.

The script stay in EMERGENCY status (lines 16 to 30), the script stays there as long as $DISKUSAGE is greater than or equal to $THRESHOLD_MAX. Whenever it enters that phase, the script first saves inside $BIGFILES a list of all the files in $H, sorted by size from biggest to smallest (line 17). It then reads that list one line at a time, deleting the $BIGGESTFILE (lines 21 to 24) until $DISKUSAGE remains above $THRESHOLD_MAX. Otherwise, it sets $DISKSTATUS to CRITICAL and breaks the loop (lines 26 and 27).

In the "CRITICAL" status (lines 32 to 52) the script still deletes files until $DISKUSAGE falls below $THRESHOLD_MED (lines 34 and 51), in order to make room on the disk. When that condition is not true anymore, $DISKSTATUS becomes "WATCHING" (line 53). However, file deletion happens at a much slower pace (line 50) and with a completely different criterion.

The code continuously loops over all users (lines 36 to 49). At every iteration, that loop creates a list of all the files belonging to the current user and saves the oldest one in the $OLDESTFILE variable (lines 38 to 40). Then, if $DISKUSAGE is still higher than $THRESHOLD_MED, that file is deleted; otherwise the loop is broken (lines 42 to 48).

The "WATCHING" state in lines 55 to 63 does nothing but check every five seconds whether $DISKUSAGE is below the $THRESHOLD_MED value or not. If not, it immediately goes back to "EMERGENCY".

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Decision Making Scripts

    The Bash shell uses different criteria to make decisions. Learn how to teach your shell scripts to make the right choice.

  • Bash vs. Vista PowerShell

    Microsoft’s new PowerShell relies on .NET framework libraries and thus has access to a treasure trove of functions and objects. How does PowerShell measure up to traditional shells like Bash?

  • Tutorials – Shell Scripts

    Letting your scripts ask complex questions and give user feedback makes them more effective.

  • Bash Tuning

    In the old days, shells were capable of little more than calling external programs and executing basic, internal commands. With all the bells and whistles in the latest versions of Bash, however, you hardly need the support of external tools.

  • Bash Alternatives

    Don't let your familiarity with the Bash shell stop you from exploring other options. We take a look at a pair of alternatives that are easy to install and easy to use: Zsh and fish.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News