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.