shell script Practice IV

Posted by darknuke on Tue, 14 Dec 2021 14:52:23 +0100

Excerpt from the third chapter of the second edition of shell script creation utility

Script 22 reminder tool

Simple utilities like Stickies have been popular with Windows and Mac users for years. You can use them to keep small notes on the screen and send reminders. This application is ideal for recording phone numbers or other reminders. Unfortunately, no corresponding command is available on the Unix command line, but this problem can be solved with two scripts.

The first script remember (as shown in code listing 3-1) allows you to easily save information fragments in the file rememberfile in the user's home directory. If no parameters are used when calling, the script will read from the standard input until the user presses CTRL-D to generate the file end sequence (^ D). If parameters are added, they will be saved directly to the data file.

Another supporting script, remindme (as shown in code listing 3-2), can display the contents of the entire rememberfile (if no parameters are specified) or search results (using parameters as search mode).

Code remember

#!/bin/bash

# remember -- an easy-to-use command line reminder tool

rememberfile="$HOME/.remember"

if [ $# -eq 0 ];then
        # Remind the user to input and append the input information to the file remember.
        echo "Enter note, end with ^D: "
        cat - >> $rememberfile # 1 cat command reads input from stdin (the - in the command is short for stdin or stdout, which means that, depending on the context)
else
        # Trace the parameters passed into the script to the file remember.
        echo "$@" >> $rememberfile # 2 if script parameters are specified, all parameters will be appended to the rememberfile
fi
exit 0

Code remindme

#!/bin/bash

# remindme -- find the matching line in the data file. If no parameter is specified, all the contents of the data will be displayed

rememberfile="$HOME/.remember"

if [ !-f $remrmberfile ];then
        echo "$0: You don't seem to have a .remember file." >&2
        echo "To remedy this,please use 'remember' to add reminders" ?&2
        exit 1
fi

if [ $# -eq 0 ];then
        # If no personality search criteria are specified, the entire data file is displayed
        more $rememberfile # 3 use the more command to display the file contents for users in pages
else
        # Otherwise, search the specified content and display the results neatly
        grep -i -- "$@" $rememberfile | ${PAGER:-more} # 4 use the case sensitive grep command to search for keywords, and then display the search results in pagination
fi

exit 0

Operation results

$ remember Southwest Airlines: 800-IFLYSWA 
$ remember 
Enter note, end with ^D:
Find Dave's film reviews at http://www.DaveOnFilm.com/ ^D

# If you want to check a note in a few months
$ remindme film reviews 
Find Dave's film reviews at http://www.DaveOnFilm.com/

# If you have an 800 number, you really can't remember it
$ remindme 800 
Southwest Airlines: 800-IFLYSWA

refine on

Although this is certainly not a representative work of shell programming, these scripts well demonstrate the scalability of Unix command line. If you have a new idea, the implementation method may be very simple.

There are many ways to improve these scripts. For example, you can introduce the concept of records: time stamp each remember entry, and multiple lines of input can be saved as a record for regular expression search. In this way, the telephone numbers of a group of people can be saved, and the whole group can be retrieved by remembering the name of one of them. If you are not satisfied with this, you can also add editing and deletion functions. Also, edit ~ /. Manually The remember file is also very simple.

Script 23 interactive calculator

If you haven't forgotten, Scriptbc (script #9) allows us to call bc to perform floating-point operations in the form of command-line parameters. The next step is naturally to write a wrapper to completely turn the script into an interactive calculator based on the command line. The final wrapper script (as shown in listing 3-6) very short! Make sure that scriptbc is in the PATH, otherwise the script cannot run.

Code calc

#!/bin/bash

# calc -- a command line calculator, which can be used as the front paragraph of bc


scale=2

show_help(){
cat << EOF
        In addition to standard math functions, calc also supports:

        a % b           remainder of a/b
        a ^ b           exponential: a raised to the b power
        s(x)            sine of x, x in radians
        c(x)            cosine of x, x in radians
        a(x)            arctangent of x, in radians
        l(x)            natural log of x 
        e(x)            exponential log of raising e to the x
        j(n,x)          Bessel function of integer order n of x
        scale N         show N fractional digits (default = 2)
EOF
}

if [ $# -gt 0 ];then
        exec ./scriptbc "$@"

fi

echo "Calc -- a simple calculator. Enter 'help' for help, 'quit' to quit."

/bin/echo -n "Calc> "

while read command args;do # 1 create an infinite loop and continuously display the prompt Calc > until the user enters quit or presses CTRL-D(^D) to exit
        case $command
                in
                quit|exit )             exit 0                                  ;;
                help|\? )               show_help                               ;;
                scale )                 scale=$args                             ;;
                * )                     ./scriptbc -p $scale "$command" "$args" ;;
        esac

        /bin/echo -n "Calc> "
done

echo ""

exit 0

Operation results

$ ./calc 150/3.5
42.85
$ ./calc 
Calc -- a simple calculator. Enter 'help' for help, 'quit' to quit.
Calc> 3/4
.75
Calc> 3^5
243
Calc> quit

refine on

What you can do with bc on the command line can be done in the script, but pay attention, calc.sh has no line to line memory or state retention function. This means that you can add more mathematical functions to the help system if you like. For example, the variables obase and ibase allow users to specify the numerical cardinality of input and output, but due to the lack of cross line memory, you can only modify scriptbc (script #9), or learn to enter all settings and equations on one line.

Script 24 temperature conversion

Code convertemp

#!/bin/bash

# convertatemp -- temperature conversion script. The user can enter specific units (Fahrenheit units, Celsius units or Kelvin units)
# The script will output the temperature corresponding to the other two units

if [ $# -eq 0 ];then
        cat << EOF >&2
Usage: $0 temperature[F|C|K]
where the suffix:
        F       indicates input is in Fahrenheit (default)
        C       indicates input is in Calsius
        K       indicates input is in Kelvin
EOF
        exit 1
fi

unit="$(echo $1 |sed -e 's/[-[:digit:]]*//G '|tr' [: lower:] '[: Upper:]' "# 1 matches zero or more" - "and any subsequent array and replaces with null
temp="$(echo $1 |sed -e 's/[^-[:digit:]]*//G ') "# 2 delete all non" - "and array characters

case ${unit:=F}
in
        F ) # Formula for converting Fahrenheit temperature to Celsius temperature: Tc = (F -32) / 1.8
                farn="$temp"
                cels="$(echo "scale=2;($farn -32)/1.8" |bc)" # 3 convert the formula into a sequence that can be passed to bc
                kelv="$(echo "scale=2;$cels + 273.15" |bc)"
                ;;
        C ) # Conversion of Celsius temperature to Fahrenheit temperature formula: TF = (9 / 5) * TC + 32
                cels=$temp
                kelv="$(echo "scale=2;$cels + 273.15" |bc)"
                farn="$(echo "scale=2;(1.8 * $cels) + 32" | bc)" # 4 formula for converting Celsius to Fahrenheit
                ;;

        K ) # Centigrade temperature = Kelvin temperature - 273.15,Then use the formula for converting Celsius temperature to Fahrenheit temperature # 5 convert settings to Kelvin
                kelv=$temp
                cels="$(echo "scale=2;$kelv - 273.15" |bc)"
                farn="$(echo "scale=2;(1.8 * $cels) + 32" |bc)"
                ;;

        * )
                echo "Given temperature unit is not supported"
                exit 1
esac

echo "Fahrehit = $farn"
echo "Celsius  = $cels"
echo "Kelvin   = $kelv"

exit 0

Operation results

$ ./convertatemp 212
Fahrehit = 212
Celsius  = 100.00
Kelvin   = 373.15
$ ./convertatemp 100C
Fahrehit = 212.0
Celsius  = 100
Kelvin   = 373.15
$ ./convertatemp 100K
Fahrehit = -279.67
Celsius  = -173.15
Kelvin   = 100

refine on

You can add several input options to generate concise output of only one unit conversion result at a time. For example, convertatemp -c 100F outputs only the Celsius temperature corresponding to 100 ° F. This method can also help you convert values in other scripts.

Script 25 calculating loans

Another calculation that users should often contact is probably the loan repayment amount. The script in listing 3-10 can also help you answer "what can I do with this bonus?" And "can I afford that new Tesla?" Such related issues.

Although the formula for calculating the repayment amount according to the loan amount, interest rate and loan term is a little tricky, the proper use of shell variables can tame this mathematical beast and make it surprisingly easy to understand.

Code loabcalc

#!/bin/bash

# Loan Calc -- calculate each payment according to the loan amount, interest rate and loan term (year)

# The formula is: M = P * (J / (1 - (1 + J)) ^ - n))
# Where, P = loan amount, J = monthly interest rate, N = loan term (in months)
# Users generally need to enter P, I (annual interest rate) and l (number of years)

# . ../1/library.sh     # Import script 1

if [ $# -ne 3 ];then
        echo "Usage: $0 principal interst loan-duration-years" >&2
        exit 1
fi

P=$1 I=$2 L=$3 # 2 break the formula into indirect parts
J="$(./scriptbc -p 8 $I / \(12 \* 100\))"
N="$(( $L * 12 ))"
M="$(./scriptbc -p 8 $P \* \($J / \(1-\(1+$J\)\^ -$N\)\))"

# Slightly beautify the amount:

dollars="$(echo $M | cut -d. -f1)" # 3 the second line of code obtains the part after the decimal point of the monthly payment amount, and then only two digits are reserved
cents="$(echo $M |cut -d. -f2|cut -c1-2)"

cat << EOF

A $L-year loan at $I% interest with a principal amount of $(./nicenumber $P 1 ) 
results in a payment of \$$dollars.$cents each month for the duration of 
the loan ($N payments).

EOF

exit 0

Operation results

$ ./loancalc  44900 4.75 4

A 4-year loan at 4.75% interest with a principal amount of 44,900 
results in a payment of $1028.93 each month for the duration of 
the loan (48 payments).

$ ./loancalc  44900 4.75 5

A 5-year loan at 4.75% interest with a principal amount of 44,900 
results in a payment of $842.18 each month for the duration of 
the loan (60 payments).

refine on

If the user does not provide any parameters, the script can also be prompted item by item. A more practical version is to let the user specify four parameters (loan amount, interest rate, payment times and monthly payment amount), and then automatically get the fourth value. In this way, if you know that you can only bear the expenditure of $500 a month, and the maximum term of a car loan with an interest rate of 6% is 5 years, you can determine the maximum amount that can be loaned. You can implement the corresponding options to let users pass in their needs To complete this calculation.

Script 26 trace events

This simple calendar program is actually implemented by two scripts, similar to the reminder tool in the script #22. The first script addagenda (shown in listing 3-12) allows the user to set up a periodic event (specify the day of the week for weekly events; specify the month and days for annual events) or a one-time event (specify day, month and year). All verified dates are saved in the. Agenda file in the user's home directory along with a line of event description information. The second script agenda (as shown in code listing 3-13) checks all known events and shows which event is currently scheduled.

This tool is especially useful for remembering birthdays and anniversaries. If you can't remember things, this convenient script can help you reduce a lot of pain.

Code addagenda

#!/bin/bash

# addagenda -- prompts the user to add a new event

agendafile="$HOME/.agenda"

isDayName(){

        # If there is no problem with the date, return 0; Otherwise, return 1
        case $(echo $1 |tr '[[:upper:]]' '[[:lower:]]') in
                sun*|mon*|tue*|wed*|thu*|fri*|sat* ) retval=0 ;;
                * ) retval=1 ;;
        esac
        return $retval
}

isMonthName(){

        case $(echo $1 |tr '[[:upper:]]' '[[:lower:]]') in
                jan*|feb*|apr*|may*|jun*) return 0 ;;
                jul*|aug*|sep*|oct*|dec*) return 0 ;;
                * ) return 1 ;;
        esac
}

normalize(){ # 1 specification compressed characters

        # Returns a string whose first letter is uppercase and the next two letters are lowercase
        /bin/echo -n $1 |cut -c1|tr '[[:lower:]]' '[[:upper:]]'
        echo $1 |cut -c2-3|tr '[[:upper:]]' '[[:lower:]]'
}

if [ ! -w $HOME ];then # -w whether the file exists and can be written
        echo "$0: cannot write in your home directory ($HOME)" >&2
        exit 1
fi

echo "Agenda: The Unix Reminder Service"
read -p "Date of event (day mon,day month year,or dayname):" word1 word2 word3 junk

if isDayName $word1;then
        if [ ! -z "$word2" ];then
                echo "Bad dayname format:just specify the dayname by itself." >&2
                exit 1
        fi
        date="$(normalize $word1)"
else
        if [ ! -z "$(echo $word1 |sed 's/[[:digit:]]//g')" ];then
                echo "Bad ate format: please specify day first, by day number" >&2
                exit 1
        fi

        if [ "$word1" -lt 1 -o "$word1" -gt 31 ];then
                echo "Bad date formate: day number can only be in range 1-31" >&2
                exit 1
        fi

        word2="$(normalize $word2)"

        if [ -z "$word3" ];then
                date="$word1$word2"
        else
                if [ ! -z "$(echo $word3 |sed 's/[[:digit:]]//g')" ];then
                        echo "Bad date formate: third field should be year." >&2
                        exit 1
                elif [ $word3 -lt 2000 -o $word3 -gt 2500 ];then
                        echo "Bad date format: year value should be 2000 - 2500 " >&2
                        exit 1
                fi

                date="$word1$word2$word3"
        fi
fi
read -p "One-line description: " description

# Ready to write data file

echo "$(echo $date |sed 's/ //G ') | $description "> > $agendafile # 2 writes the record after the specification to the hidden file

exit 0

Code agenda

#!/bin/bash

# agenda -- scan the user's agenda file to find out if there are plans to smash things on the same day or the next day

agendafile="$HOME/.agenda"

checkDate(){

        # Create a default value that matches the current day
        weekday=$1 day=$2 month=$3 year=$4
        format1="$weekday" format2="$day$month" format3="$day$month$year" # 3 in order to check the event, the current parent date is replaced with three possible date string formats
        echo $format1 $format2 $format3
        # Compare dates in data file
        IFS="|" # The read content naturally separates 31Oct and Hello World at the IFS division and assigns them to date description respectively

        echo "On the agenda for today:"

        while read date description;do
                echo $date $description
                if [ "$date" = "$format1" -o "$date" = "$format2" -o "$date" = "$format3" ];then
                        echo "  $description"
                fi
        done < $agendafile

}

if [ ! -e $agendafile ];then
        echo "$0: You don't seem to have an .agenda file. " >&2
        echo "To remdy this ,please use 'addagenda' to add events" >&2
        exit i
fi

# Get date of day

eval $(date '+weekday="%a" month="%b" day="%e" year="%G" ') # 4 assign the required 4 date values to the corresponding variables

day="$(echo $day|sed 's/ //g ') "# delete possible leading spaces # 5 

checkDate $weekday $day $month $year

exit 0

Operation results

$ ./addagenda 
Agenda: The Unix Reminder Service
Date of event (day mon,day month year,or dayname):31 Oct
One-line description: Hello World

$ cat ~/.agenda 
31Oct|Hello World 

# The blogger's environment here is not switched to English in domestic time, so the printing is not expected
$ ./agenda 
February 1412021
On the agenda for today:
31Oct Hello World  

refine on

For complex and interesting topics such as event tracking, this script can only touch the surface. It would be better if it could view the event schedule of the previous few days, which requires some date matching operations in the script agenda. If you use the GNU date command, matching dates is not difficult. But if not, it requires complex scripts to perform date calculation in the shell alone. The date operation will be detailed later in the book, especially in scripts #99, scripts #100 and scripts #101.

Another (simpler) improvement is to let the agenda output Nothing scheduled for today when there is no current event schedule, instead of just outputting On the agenda for today.

This script can also be used to send system wide event reminders (such as scheduled backup, company holidays and employee birthdays) in the Unix host. First, let agenda scripts on each user machine check the read-only shared file.Agenda extra. Then, script agenda is invoked in each user's.login or similar logon file.

Topics: Linux bash