Appendix A. Contributed Scripts
These scripts, while not fitting into the text of this document, doillustrate some interesting shell programming techniques. They are useful,too. Have fun analyzing and running them.
Example A-1. mailformat: Formatting an e-mail message
1 #!/bin/bash 2 # mail-format.sh (ver. 1.1): Format e-mail messages. 3 4 # Gets rid of carets, tabs, and also folds excessively long lines. 5 6 # ================================================================= 7 # Standard Check for Script Argument(s) 8 ARGS=1 9 E_BADARGS=65 10 E_NOFILE=66 11 12 if [ $# -ne $ARGS ] # Correct number of arguments passed to script? 13 then 14 echo "Usage: `basename $0` filename" 15 exit $E_BADARGS 16 fi 17 18 if [ -f "$1" ] # Check if file exists. 19 then 20 file_name=$1 21 else 22 echo "File \"$1\" does not exist." 23 exit $E_NOFILE 24 fi 25 # ================================================================= 26 27 MAXWIDTH=70 # Width to fold excessively long lines to. 28 29 # --------------------------------- 30 # A variable can hold a sed script. 31 sedscript='s/^>// 32 s/^ *>// 33 s/^ *// 34 s/ *//' 35 # --------------------------------- 36 37 # Delete carets and tabs at beginning of lines, 38 #+ then fold lines to $MAXWIDTH characters. 39 sed "$sedscript" $1 | fold -s --width=$MAXWIDTH 40 # -s option to "fold" 41 #+ breaks lines at whitespace, if possible. 42 43 44 # This script was inspired by an article in a well-known trade journal 45 #+ extolling a 164K MS Windows utility with similar functionality. 46 # 47 # An nice set of text processing utilities and an efficient 48 #+ scripting language provide an alternative to bloated executables. 49 50 exit 0
Example A-2. rn: A simple-minded file rename utility
This script is a modification of Example 12-19.
1 #! /bin/bash 2 # 3 # Very simpleminded filename "rename" utility (based on "lowercase.sh"). 4 # 5 # The "ren" utility, by Vladimir Lanin (lanin@csd2.nyu.edu), 6 #+ does a much better job of this. 7 8 9 ARGS=2 10 E_BADARGS=65 11 ONE=1 # For getting singular/plural right (see below). 12 13 if [ $# -ne "$ARGS" ] 14 then 15 echo "Usage: `basename $0` old-pattern new-pattern" 16 # As in "rn gif jpg", which renames all gif files in working directory to jpg. 17 exit $E_BADARGS 18 fi 19 20 number=0 # Keeps track of how many files actually renamed. 21 22 23 for filename in *$1* #Traverse all matching files in directory. 24 do 25 if [ -f "$filename" ] # If finds match... 26 then 27 fname=`basename $filename` # Strip off path. 28 n=`echo $fname | sed -e "s/$1/$2/"` # Substitute new for old in filename. 29 mv $fname $n # Rename. 30 let "number += 1" 31 fi 32 done 33 34 if [ "$number" -eq "$ONE" ] # For correct grammar. 35 then 36 echo "$number file renamed." 37 else 38 echo "$number files renamed." 39 fi 40 41 exit 0 42 43 44 # Exercises: 45 # --------- 46 # What type of files will this not work on? 47 # How can this be fixed? 48 # 49 # Rewrite this script to process all the files in a directory 50 #+ containing spaces in their names, and to rename them, 51 #+ substituting an underscore for each space.
Example A-3. blank-rename: renames filenames containingblanks
This is an even simpler-minded version of previous script.
1 #! /bin/bash 2 # blank-rename.sh 3 # 4 # Substitutes underscores for blanks in all the filenames in a directory. 5 6 ONE=1 # For getting singular/plural right (see below). 7 number=0 # Keeps track of how many files actually renamed. 8 FOUND=0 # Successful return value. 9 10 for filename in * #Traverse all files in directory. 11 do 12 echo "$filename" | grep -q " " # Check whether filename 13 if [ $? -eq $FOUND ] #+ contains space(s). 14 then 15 fname=$filename # Strip off path. 16 n=`echo $fname | sed -e "s/ /_/g"` # Substitute underscore for blank. 17 mv "$fname" "$n" # Do the actual renaming. 18 let "number += 1" 19 fi 20 done 21 22 if [ "$number" -eq "$ONE" ] # For correct grammar. 23 then 24 echo "$number file renamed." 25 else 26 echo "$number files renamed." 27 fi 28 29 exit 0
Example A-4. encryptedpw: Uploading to an ftp site,using a locally encrypted password
1 #!/bin/bash 2 3 # Example "ex72.sh" modified to use encrypted password. 4 5 # Note that this is still rather insecure, 6 #+ since the decrypted password is sent in the clear. 7 # Use something like "ssh" if this is a concern. 8 9 E_BADARGS=65 10 11 if [ -z "$1" ] 12 then 13 echo "Usage: `basename $0` filename" 14 exit $E_BADARGS 15 fi 16 17 Username=bozo # Change to suit. 18 pword=/home/bozo/secret/password_encrypted.file 19 # File containing encrypted password. 20 21 Filename=`basename $1` # Strips pathname out of file name. 22 23 Server="XXX" 24 Directory="YYY" # Change above to actual server name & directory. 25 26 27 Password=`cruft <$pword` # Decrypt password. 28 # Uses the author's own "cruft" file encryption package, 29 #+ based on the classic "onetime pad" algorithm, 30 #+ and obtainable from: 31 #+ Primary-site: ftp://ibiblio.org/pub/Linux/utils/file 32 #+ cruft-0.2.tar.gz [16k] 33 34 35 ftp -n $Server <<End-Of-Session 36 user $Username $Password 37 binary 38 bell 39 cd $Directory 40 put $Filename 41 bye 42 End-Of-Session 43 # -n option to "ftp" disables auto-logon. 44 # Note that "bell" rings 'bell' after each file transfer. 45 46 exit 0
Example A-5. copy-cd: Copying a data CD
1 #!/bin/bash 2 # copy-cd.sh: copying a data CD 3 4 CDROM=/dev/cdrom # CD ROM device 5 OF=/home/bozo/projects/cdimage.iso # output file 6 # /xxxx/xxxxxxx/ Change to suit your system. 7 BLOCKSIZE=2048 8 SPEED=2 # May use higher speed if supported. 9 DEVICE=cdrom 10 # DEVICE="0,0" on older versions of cdrecord. 11 12 echo; echo "Insert source CD, but do *not* mount it." 13 echo "Press ENTER when ready. " 14 read ready # Wait for input, $ready not used. 15 16 echo; echo "Copying the source CD to $OF." 17 echo "This may take a while. Please be patient." 18 19 dd if=$CDROM of=$OF bs=$BLOCKSIZE # Raw device copy. 20 21 22 echo; echo "Remove data CD." 23 echo "Insert blank CDR." 24 echo "Press ENTER when ready. " 25 read ready # Wait for input, $ready not used. 26 27 echo "Copying $OF to CDR." 28 29 cdrecord -v -isosize speed=$SPEED dev=$DEVICE $OF 30 # Uses Joerg Schilling's "cdrecord" package (see its docs). 31 # http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html 32 33 34 echo; echo "Done copying $OF to CDR on device $CDROM." 35 36 echo "Do you want to erase the image file (y/n)? " # Probably a huge file. 37 read answer 38 39 case "$answer" in 40 [yY]) rm -f $OF 41 echo "$OF erased." 42 ;; 43 *) echo "$OF not erased.";; 44 esac 45 46 echo 47 48 # Exercise: 49 # Change the above "case" statement to also accept "yes" and "Yes" as input. 50 51 exit 0
Example A-6. Collatz series
1 #!/bin/bash 2 # collatz.sh 3 4 # The notorious "hailstone" or Collatz series. 5 # ------------------------------------------- 6 # 1) Get the integer "seed" from the command line. 7 # 2) NUMBER <--- seed 8 # 3) Print NUMBER. 9 # 4) If NUMBER is even, divide by 2, or 10 # 5)+ if odd, multiply by 3 and add 1. 11 # 6) NUMBER <--- result 12 # 7) Loop back to step 3 (for specified number of iterations). 13 # 14 # The theory is that every sequence, 15 #+ no matter how large the initial value, 16 #+ eventually settles down to repeating "4,2,1..." cycles, 17 #+ even after fluctuating through a wide range of values. 18 # 19 # This is an instance of an "iterate", 20 #+ an operation that feeds its output back into the input. 21 # Sometimes the result is a "chaotic" series. 22 23 24 MAX_ITERATIONS=200 25 # For large seed numbers (>32000), increase MAX_ITERATIONS. 26 27 h=${1:-$$} # Seed 28 # Use $PID as seed, 29 #+ if not specified as command-line arg. 30 31 echo 32 echo "C($h) --- $MAX_ITERATIONS Iterations" 33 echo 34 35 for ((i=1; i<=MAX_ITERATIONS; i++)) 36 do 37 38 echo -n "$h " 39 # ^^^^^ 40 # tab 41 42 let "remainder = h % 2" 43 if [ "$remainder" -eq 0 ] # Even? 44 then 45 let "h /= 2" # Divide by 2. 46 else 47 let "h = h*3 + 1" # Multiply by 3 and add 1. 48 fi 49 50 51 COLUMNS=10 # Output 10 values per line. 52 let "line_break = i % $COLUMNS" 53 if [ "$line_break" -eq 0 ] 54 then 55 echo 56 fi 57 58 done 59 60 echo 61 62 # For more information on this mathematical function, 63 #+ see "Computers, Pattern, Chaos, and Beauty", by Pickover, p. 185 ff., 64 #+ as listed in the bibliography. 65 66 exit 0
Example A-7. days-between: Calculate number of daysbetween two dates
1 #!/bin/bash 2 # days-between.sh: Number of days between two dates. 3 # Usage: ./days-between.sh [M]M/[D]D/YYYY [M]M/[D]D/YYYY 4 # 5 # Note: Script modified to account for changes in Bash 2.05b 6 #+ that closed the loophole permitting large negative 7 #+ integer return values. 8 9 ARGS=2 # Two command line parameters expected. 10 E_PARAM_ERR=65 # Param error. 11 12 REFYR=1600 # Reference year. 13 CENTURY=100 14 DIY=365 15 ADJ_DIY=367 # Adjusted for leap year + fraction. 16 MIY=12 17 DIM=31 18 LEAPCYCLE=4 19 20 MAXRETVAL=255 # Largest permissable 21 #+ positive return value from a function. 22 23 diff= # Declare global variable for date difference. 24 value= # Declare global variable for absolute value. 25 day= # Declare globals for day, month, year. 26 month= 27 year= 28 29 30 Param_Error () # Command line parameters wrong. 31 { 32 echo "Usage: `basename $0` [M]M/[D]D/YYYY [M]M/[D]D/YYYY" 33 echo " (date must be after 1/3/1600)" 34 exit $E_PARAM_ERR 35 } 36 37 38 Parse_Date () # Parse date from command line params. 39 { 40 month=${1%%/**} 41 dm=${1%/**} # Day and month. 42 day=${dm#*/} 43 let "year = `basename $1`" # Not a filename, but works just the same. 44 } 45 46 47 check_date () # Checks for invalid date(s) passed. 48 { 49 [ "$day" -gt "$DIM" ] || [ "$month" -gt "$MIY" ] || [ "$year" -lt "$REFYR" ] && Param_Error 50 # Exit script on bad value(s). 51 # Uses "or-list / and-list". 52 # 53 # Exercise: Implement more rigorous date checking. 54 } 55 56 57 strip_leading_zero () # Better to strip possible leading zero(s) 58 { #+ from day and/or month 59 return ${1#0} #+ since otherwise Bash will interpret them 60 } #+ as octal VALUES (NULL, sect 2.9.2.1). 61 62 63 day_index () # Gauss' Formula: 64 { # Days from Jan. 3, 1600 to date passed as param. 65 66 day=$1 67 month=$2 68 year=$3 69 70 let "month = $month - 2" 71 if [ "$month" -le 0 ] 72 then 73 let "month += 12" 74 let "year -= 1" 75 fi 76 77 let "year -= $REFYR" 78 let "indexyr = $year / $CENTURY" 79 80 81 let "Days = $DIY*$year + $year/$LEAPCYCLE - $indexyr + $indexyr/$LEAPCYCLE + $ADJ_DIY*$month/$MIY + $day - $DIM" 82 # For an in-depth explanation of this algorithm, see 83 #+ http://home.t-online.de/home/berndt.schwerdtfeger/cal.htm 84 85 86 echo $Days 87 88 } 89 90 91 calculate_difference () # Difference between to day indices. 92 { 93 let "diff = $1 - $2" # Global variable. 94 } 95 96 97 abs () # Absolute value 98 { # Uses global "value" variable. 99 if [ "$1" -lt 0 ] # If negative 100 then #+ then 101 let "value = 0 - $1" #+ change sign, 102 else #+ else 103 let "value = $1" #+ leave it alone. 104 fi 105 } 106 107 108 109 if [ $# -ne "$ARGS" ] # Require two command line params. 110 then 111 Param_Error 112 fi 113 114 Parse_Date $1 115 check_date $day $month $year # See if valid date. 116 117 strip_leading_zero $day # Remove any leading zeroes 118 day=$? #+ on day and/or month. 119 strip_leading_zero $month 120 month=$? 121 122 let "date1 = `day_index $day $month $year`" 123 124 125 Parse_Date $2 126 check_date $day $month $year 127 128 strip_leading_zero $day 129 day=$? 130 strip_leading_zero $month 131 month=$? 132 133 date2=$(day_index $day $month $year) # Command substitution. 134 135 136 calculate_difference $date1 $date2 137 138 abs $diff # Make sure it's positive. 139 diff=$value 140 141 echo $diff 142 143 exit 0 144 # Compare this script with 145 #+ the implementation of Gauss' Formula in a C program at: 146 #+ http://buschencrew.hypermart.net/software/datedif
Example A-8. Make a "dictionary"
1 #!/bin/bash 2 # makedict.sh [make dictionary] 3 4 # Modification of /usr/sbin/mkdict script. 5 # Original script copyright 1993, by Alec Muffett. 6 # 7 # This modified script included in this document in a manner 8 #+ consistent with the "LICENSE" document of the "Crack" package 9 #+ that the original script is a part of. 10 11 # This script processes text files to produce a sorted list 12 #+ of words found in the files. 13 # This may be useful for compiling dictionaries 14 #+ and for lexicographic research. 15 16 17 E_BADARGS=65 18 19 if [ ! -r "$1" ] # Need at least one 20 then #+ valid file argument. 21 echo "Usage: $0 files-to-process" 22 exit $E_BADARGS 23 fi 24 25 26 # SORT="sort" # No longer necessary to define options 27 #+ to sort. Changed from original script. 28 29 cat $* | # Contents of specified files to stdout. 30 tr A-Z a-z | # Convert to lowercase. 31 tr ' ' '\012' | # New: change spaces to newlines. 32 # tr -cd '\012[a-z][0-9]' | # Get rid of everything non-alphanumeric 33 #+ (original script). 34 tr -c '\012a-z' '\012' | # Rather than deleting 35 #+ now change non-alpha to newlines. 36 sort | # $SORT options unnecessary now. 37 uniq | # Remove duplicates. 38 grep -v '^#' | # Delete lines beginning with a hashmark. 39 grep -v '^$' # Delete blank lines. 40 41 exit 0
Example A-9. Soundex conversion
1 #!/bin/bash 2 # soundex.sh: Calculate "soundex" code for names 3 4 # ======================================================= 5 # Soundex script 6 # by 7 # Mendel Cooper 8 # thegrendel@theriver.com 9 # 23 January, 2002 10 # 11 # Placed in the Public Domain. 12 # 13 # A slightly different version of this script appeared in 14 #+ Ed Schaefer's July, 2002 "Shell Corner" column 15 #+ in "Unix Review" on-line, 16 #+ http://www.unixreview.com/documents/uni1026336632258/ 17 # ======================================================= 18 19 20 ARGCOUNT=1 # Need name as argument. 21 E_WRONGARGS=70 22 23 if [ $# -ne "$ARGCOUNT" ] 24 then 25 echo "Usage: `basename $0` name" 26 exit $E_WRONGARGS 27 fi 28 29 30 assign_value () # Assigns numerical value 31 { #+ to letters of name. 32 33 val1=bfpv # 'b,f,p,v' = 1 34 val2=cgjkqsxz # 'c,g,j,k,q,s,x,z' = 2 35 val3=dt # etc. 36 val4=l 37 val5=mn 38 val6=r 39 40 # Exceptionally clever use of 'tr' follows. 41 # Try to figure out what is going on here. 42 43 value=$( echo "$1" \ 44 | tr -d wh \ 45 | tr $val1 1 | tr $val2 2 | tr $val3 3 \ 46 | tr $val4 4 | tr $val5 5 | tr $val6 6 \ 47 | tr -s 123456 \ 48 | tr -d aeiouy ) 49 50 # Assign letter values. 51 # Remove duplicate numbers, except when separated by vowels. 52 # Ignore vowels, except as separators, so delete them last. 53 # Ignore 'w' and 'h', even as separators, so delete them first. 54 # 55 # The above command substitution lays more pipe than a plumber <g>. 56 57 } 58 59 60 input_name="$1" 61 echo 62 echo "Name = $input_name" 63 64 65 # Change all characters of name input to lowercase. 66 # ------------------------------------------------ 67 name=$( echo $input_name | tr A-Z a-z ) 68 # ------------------------------------------------ 69 # Just in case argument to script is mixed case. 70 71 72 # Prefix of soundex code: first letter of name. 73 # -------------------------------------------- 74 75 76 char_pos=0 # Initialize character position. 77 prefix0=${name:$char_pos:1} 78 prefix=`echo $prefix0 | tr a-z A-Z` 79 # Uppercase 1st letter of soundex. 80 81 let "char_pos += 1" # Bump character position to 2nd letter of name. 82 name1=${name:$char_pos} 83 84 85 # ++++++++++++++++++++++++++ Exception Patch +++++++++++++++++++++++++++++++++ 86 # Now, we run both the input name and the name shifted one char to the right 87 #+ through the value-assigning function. 88 # If we get the same value out, that means that the first two characters 89 #+ of the name have the same value assigned, and that one should cancel. 90 # However, we also need to test whether the first letter of the name 91 #+ is a vowel or 'w' or 'h', because otherwise this would bollix things up. 92 93 char1=`echo $prefix | tr A-Z a-z` # First letter of name, lowercased. 94 95 assign_value $name 96 s1=$value 97 assign_value $name1 98 s2=$value 99 assign_value $char1 100 s3=$value 101 s3=9$s3 # If first letter of name is a vowel 102 #+ or 'w' or 'h', 103 #+ then its "value" will be null (unset). 104 #+ Therefore, set it to 9, an otherwise 105 #+ unused value, which can be tested for. 106 107 108 if [[ "$s1" -ne "$s2" || "$s3" -eq 9 ]] 109 then 110 suffix=$s2 111 else 112 suffix=${s2:$char_pos} 113 fi 114 # ++++++++++++++++++++++ end Exception Patch +++++++++++++++++++++++++++++++++ 115 116 117 padding=000 # Use at most 3 zeroes to pad. 118 119 120 soun=$prefix$suffix$padding # Pad with zeroes. 121 122 MAXLEN=4 # Truncate to maximum of 4 chars. 123 soundex=${soun:0:$MAXLEN} 124 125 echo "Soundex = $soundex" 126 127 echo 128 129 # The soundex code is a method of indexing and classifying names 130 #+ by grouping together the ones that sound alike. 131 # The soundex code for a given name is the first letter of the name, 132 #+ followed by a calculated three-number code. 133 # Similar sounding names should have almost the same soundex codes. 134 135 # Examples: 136 # Smith and Smythe both have a "S-530" soundex. 137 # Harrison = H-625 138 # Hargison = H-622 139 # Harriman = H-655 140 141 # This works out fairly well in practice, but there are numerous anomalies. 142 # 143 # 144 # The U.S. Census and certain other governmental agencies use soundex, 145 # as do genealogical researchers. 146 # 147 # For more information, 148 #+ see the "National Archives and Records Administration home page", 149 #+ http://www.nara.gov/genealogy/soundex/soundex.html 150 151 152 153 # Exercise: 154 # -------- 155 # Simplify the "Exception Patch" section of this script. 156 157 exit 0
Example A-10. "Game of Life"
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-10.txt
Example A-11. Data file for "Game of Life"
1 # This is an example "generation 0" start-up file for "life.sh". 2 # -------------------------------------------------------------- 3 # The "gen0" file is a 10 x 10 grid using a period (.) for live cells, 4 #+ and an underscore (_) for dead ones. We cannot simply use spaces 5 #+ for dead cells in this file because of a peculiarity in Bash arrays. 6 # [Exercise for the reader: explain this.] 7 # 8 # Lines beginning with a '#' are comments, and the script ignores them. 9 __.__..___ 10 ___._.____ 11 ____.___.. 12 _._______. 13 ____._____ 14 ..__...___ 15 ____._____ 16 ___...____ 17 __.._..___ 18 _..___..__
+++
The following two scripts are by Mark Moraes of the Universityof Toronto. See the enclosed file "Moraes-COPYRIGHT"for permissions and restrictions.
Example A-12. behead: Removing mail and news message headers
1 #! /bin/sh 2 # Strips off the header from a mail/News message i.e. till the first 3 # empty line 4 # Mark Moraes, University of Toronto 5 6 # ==> These comments added by author of this document. 7 8 if [ $# -eq 0 ]; then 9 # ==> If no command line args present, then works on file redirected to stdin. 10 sed -e '1,/^$/d' -e '/^[ ]*$/d' 11 # --> Delete empty lines and all lines until 12 # --> first one beginning with white space. 13 else 14 # ==> If command line args present, then work on files named. 15 for i do 16 sed -e '1,/^$/d' -e '/^[ ]*$/d' $i 17 # --> Ditto, as above. 18 done 19 fi 20 21 # ==> Exercise: Add error checking and other options. 22 # ==> 23 # ==> Note that the small sed script repeats, except for the arg passed. 24 # ==> Does it make sense to embed it in a function? Why or why not?
Example A-13. ftpget: Downloading files via ftp
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-13.txt
+
Antek Sawicki contributed the following script, which makes veryclever use of the parameter substitution operators discussed inSection 9.3.
Example A-14. password: Generating random8-character passwords
1 #!/bin/bash 2 # May need to be invoked with #!/bin/bash2 on older machines. 3 # 4 # Random password generator for Bash 2.x by Antek Sawicki <tenox@tenox.tc>, 5 # who generously gave permission to the document author to use it here. 6 # 7 # ==> Comments added by document author ==> 8 9 10 MATRIX="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 11 # ==> Password will consist of alphanumeric characters. 12 LENGTH="8" 13 # ==> May change 'LENGTH' for longer password. 14 15 16 while [ "${n:=1}" -le "$LENGTH" ] 17 # ==> Recall that := is "default substitution" operator. 18 # ==> So, if 'n' has not been initialized, set it to 1. 19 do 20 PASS="$PASS${MATRIX:$(($RANDOM%${#MATRIX})):1}" 21 # ==> Very clever, but tricky. 22 23 # ==> Starting from the innermost nesting... 24 # ==> ${#MATRIX} returns length of array MATRIX. 25 26 # ==> $RANDOM%${#MATRIX} returns random number between 1 27 # ==> and [length of MATRIX] - 1. 28 29 # ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1} 30 # ==> returns expansion of MATRIX at random position, by length 1. 31 # ==> See {var:pos:len} parameter substitution in Chapter 9. 32 # ==> and the associated examples. 33 34 # ==> PASS=... simply pastes this result onto previous PASS (concatenation). 35 36 # ==> To visualize this more clearly, uncomment the following line 37 # echo "$PASS" 38 # ==> to see PASS being built up, 39 # ==> one character at a time, each iteration of the loop. 40 41 let n+=1 42 # ==> Increment 'n' for next pass. 43 done 44 45 echo "$PASS" # ==> Or, redirect to a file, as desired. 46 47 exit 0
+
James R. Van Zandt contributed this script,which uses named pipes and, in his words, "really exercisesquoting and escaping".
Example A-15. fifo: Making daily backups, using named pipes
1 #!/bin/bash 2 # ==> Script by James R. Van Zandt, and used here with his permission. 3 4 # ==> Comments added by author of this document. 5 6 7 HERE=`uname -n` # ==> hostname 8 THERE=bilbo 9 echo "starting remote backup to $THERE at `date +%r`" 10 # ==> `date +%r` returns time in 12-hour format, i.e. "08:08:34 PM". 11 12 # make sure /pipe really is a pipe and not a plain file 13 rm -rf /pipe 14 mkfifo /pipe # ==> Create a "named pipe", named "/pipe". 15 16 # ==> 'su xyz' runs commands as user "xyz". 17 # ==> 'ssh' invokes secure shell (remote login client). 18 su xyz -c "ssh $THERE \"cat >/home/xyz/backup/${HERE}-daily.tar.gz\" < /pipe"& 19 cd / 20 tar -czf - bin boot dev etc home info lib man root sbin share usr var >/pipe 21 # ==> Uses named pipe, /pipe, to communicate between processes: 22 # ==> 'tar/gzip' writes to /pipe and 'ssh' reads from /pipe. 23 24 # ==> The end result is this backs up the main directories, from / on down. 25 26 # ==> What are the advantages of a "named pipe" in this situation, 27 # ==>+ as opposed to an "anonymous pipe", with |? 28 # ==> Will an anonymous pipe even work here? 29 30 31 exit 0
+
St閜hane Chazelas contributed the following script todemonstrate that generating prime numbers does not requirearrays.
Example A-16. Generating prime numbers using the modulo operator
1 #!/bin/bash 2 # primes.sh: Generate prime numbers, without using arrays. 3 # Script contributed by Stephane Chazelas. 4 5 # This does *not* use the classic "Sieve of Eratosthenes" algorithm, 6 #+ but instead uses the more intuitive method of testing each candidate number 7 #+ for factors (divisors), using the "%" modulo operator. 8 9 10 LIMIT=1000 # Primes 2 - 1000 11 12 Primes() 13 { 14 (( n = $1 + 1 )) # Bump to next integer. 15 shift # Next parameter in list. 16 # echo "_n=$n i=$i_" 17 18 if (( n == LIMIT )) 19 then echo $* 20 return 21 fi 22 23 for i; do # "i" gets set to "@", previous values of $n. 24 # echo "-n=$n i=$i-" 25 (( i * i > n )) && break # Optimization. 26 (( n % i )) && continue # Sift out non-primes using modulo operator. 27 Primes $n $@ # Recursion inside loop. 28 return 29 done 30 31 Primes $n $@ $n # Recursion outside loop. 32 # Successively accumulate positional parameters. 33 # "$@" is the accumulating list of primes. 34 } 35 36 Primes 1 37 38 exit 0 39 40 # Uncomment lines 16 and 24 to help figure out what is going on. 41 42 # Compare the speed of this algorithm for generating primes 43 #+ with the Sieve of Eratosthenes (ex68.sh). 44 45 # Exercise: Rewrite this script without recursion, for faster execution.
+
This is Rick Boivie's revision of Jordi Sanfeliu'streescript.
Example A-17. tree: Displaying a directory tree
1 #!/bin/bash 2 # tree.sh 3 4 # Written by Rick Boivie. 5 # Used with permission. 6 # This is a revised and simplified version of a script 7 #+ by Jordi Sanfeliu (and patched by Ian Kjos). 8 # This script replaces the earlier version used in 9 #+ previous releases of the Advanced Bash Scripting Guide. 10 11 # ==> Comments added by the author of this document. 12 13 14 search () { 15 for dir in `echo *` 16 # ==> `echo *` lists all the files in current working directory, 17 #+ ==> without line breaks. 18 # ==> Similar effect to for dir in * 19 # ==> but "dir in `echo *`" will not handle filenames with blanks. 20 do 21 if [ -d "$dir" ] ; then # ==> If it is a directory (-d)... 22 zz=0 # ==> Temp variable, keeping track of directory level. 23 while [ $zz != $1 ] # Keep track of inner nested loop. 24 do 25 echo -n "| " # ==> Display vertical connector symbol, 26 # ==> with 2 spaces & no line feed in order to indent. 27 zz=`expr $zz + 1` # ==> Increment zz. 28 done 29 30 if [ -L "$dir" ] ; then # ==> If directory is a symbolic link... 31 echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'` 32 # ==> Display horiz. connector and list directory name, but... 33 # ==> delete date/time part of long listing. 34 else 35 echo "+---$dir" # ==> Display horizontal connector symbol... 36 # ==> and print directory name. 37 numdirs=`expr $numdirs + 1` # ==> Increment directory count. 38 if cd "$dir" ; then # ==> If can move to subdirectory... 39 search `expr $1 + 1` # with recursion ;-) 40 # ==> Function calls itself. 41 cd .. 42 fi 43 fi 44 fi 45 done 46 } 47 48 if [ $# != 0 ] ; then 49 cd $1 # move to indicated directory. 50 #else # stay in current directory 51 fi 52 53 echo "Initial directory = `pwd`" 54 numdirs=0 55 56 search 0 57 echo "Total directories = $numdirs" 58 59 exit 0
Noah Friedman gave permission to use his stringfunctionscript, which essentially reproduces some of theC-library string manipulation functions.
Example A-18. string functions: C-like string functions
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-18.txt
Michael Zick's complex array example uses the md5sumcheck sum command to encode directoryinformation.
Example A-19. Directory information
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-19.txt
Sthane Chazelas demonstrates object-oriented programming in aBash script.
Example A-20. Object-oriented database
1 #!/bin/bash 2 # obj-oriented.sh: Object-oriented programming in a shell script. 3 # Script by Stephane Chazelas. 4 5 # Important Note: 6 # --------- ---- 7 # If running this script under version 3 or later of Bash, 8 #+ replace all periods in function names with a "legal" character, 9 #+ for example, an underscore. 10 11 12 person.new() # Looks almost like a class declaration in C++. 13 { 14 local obj_name=$1 name=$2 firstname=$3 birthdate=$4 15 16 eval "$obj_name.set_name() { 17 eval \"$obj_name.get_name() { 18 echo \$1 19 }\" 20 }" 21 22 eval "$obj_name.set_firstname() { 23 eval \"$obj_name.get_firstname() { 24 echo \$1 25 }\" 26 }" 27 28 eval "$obj_name.set_birthdate() { 29 eval \"$obj_name.get_birthdate() { 30 echo \$1 31 }\" 32 eval \"$obj_name.show_birthdate() { 33 echo \$(date -d \"1/1/1970 0:0:\$1 GMT\") 34 }\" 35 eval \"$obj_name.get_age() { 36 echo \$(( (\$(date +%s) - \$1) / 3600 / 24 / 365 )) 37 }\" 38 }" 39 40 $obj_name.set_name $name 41 $obj_name.set_firstname $firstname 42 $obj_name.set_birthdate $birthdate 43 } 44 45 echo 46 47 person.new self Bozeman Bozo 101272413 48 # Create an instance of "person.new" (actually passing args to the function). 49 50 self.get_firstname # Bozo 51 self.get_name # Bozeman 52 self.get_age # 28 53 self.get_birthdate # 101272413 54 self.show_birthdate # Sat Mar 17 20:13:33 MST 1973 55 56 echo 57 58 # typeset -f 59 #+ to see the created functions (careful, it scrolls off the page). 60 61 exit 0
Mariusz Gniazdowski contributes a hashlibrary for use in scripts.
Example A-21. Library of hash functions
1 # Hash: 2 # Hash function library 3 # Author: Mariusz Gniazdowski <mgniazd-at-gmail.com> 4 # Date: 2005-04-07 5 6 # Functions making emulating hashes in Bash a little less painful. 7 8 9 # Limitations: 10 # * Only global variables are supported. 11 # * Each hash instance generates one global variable per value. 12 # * Variable names collisions are possible 13 #+ if you define variable like __hash__hashname_key 14 # * Keys must use chars that can be part of a Bash variable name 15 #+ (no dashes, periods, etc.). 16 # * The hash is created as a variable: 17 # ... hashname_keyname 18 # So if somone will create hashes like: 19 # myhash_ + mykey = myhash__mykey 20 # myhash + _mykey = myhash__mykey 21 # Then there will be a collision. 22 # (This should not pose a major problem.) 23 24 25 Hash_config_varname_prefix=__hash__ 26 27 28 # Emulates: hash[key]=value 29 # 30 # Params: 31 # 1 - hash 32 # 2 - key 33 # 3 - value 34 function hash_set { 35 eval "${Hash_config_varname_prefix}${1}_${2}=\"${3}\"" 36 } 37 38 39 # Emulates: value=hash[key] 40 # 41 # Params: 42 # 1 - hash 43 # 2 - key 44 # 3 - value (name of global variable to set) 45 function hash_get_into { 46 eval "$3=\"\$${Hash_config_varname_prefix}${1}_${2}\"" 47 } 48 49 50 # Emulates: echo hash[key] 51 # 52 # Params: 53 # 1 - hash 54 # 2 - key 55 # 3 - echo params (like -n, for example) 56 function hash_echo { 57 eval "echo $3 \"\$${Hash_config_varname_prefix}${1}_${2}\"" 58 } 59 60 61 # Emulates: hash1[key1]=hash2[key2] 62 # 63 # Params: 64 # 1 - hash1 65 # 2 - key1 66 # 3 - hash2 67 # 4 - key2 68 function hash_copy { 69 eval "${Hash_config_varname_prefix}${1}_${2}=\"\$${Hash_config_varname_prefix}${3}_${4}\"" 70 } 71 72 73 # Emulates: hash[keyN-1]=hash[key2]=...hash[key1] 74 # 75 # Copies first key to rest of keys. 76 # 77 # Params: 78 # 1 - hash1 79 # 2 - key1 80 # 3 - key2 81 # . . . 82 # N - keyN 83 function hash_dup { 84 local hashName="$1" keyName="$2" 85 shift 2 86 until [ ${#} -le 0 ]; do 87 eval "${Hash_config_varname_prefix}${hashName}_${1}=\"\$${Hash_config_varname_prefix}${hashName}_${keyName}\"" 88 shift; 89 done; 90 } 91 92 93 # Emulates: unset hash[key] 94 # 95 # Params: 96 # 1 - hash 97 # 2 - key 98 function hash_unset { 99 eval "unset ${Hash_config_varname_prefix}${1}_${2}" 100 } 101 102 103 # Emulates something similar to: ref=&hash[key] 104 # 105 # The reference is name of the variable in which value is held. 106 # 107 # Params: 108 # 1 - hash 109 # 2 - key 110 # 3 - ref - Name of global variable to set. 111 function hash_get_ref_into { 112 eval "$3=\"${Hash_config_varname_prefix}${1}_${2}\"" 113 } 114 115 116 # Emulates something similar to: echo &hash[key] 117 # 118 # That reference is name of variable in which value is held. 119 # 120 # Params: 121 # 1 - hash 122 # 2 - key 123 # 3 - echo params (like -n for example) 124 function hash_echo_ref { 125 eval "echo $3 \"${Hash_config_varname_prefix}${1}_${2}\"" 126 } 127 128 129 130 # Emulates something similar to: $$hash[key](param1, param2, ...) 131 # 132 # Params: 133 # 1 - hash 134 # 2 - key 135 # 3,4, ... - Function parameters 136 function hash_call { 137 local hash key 138 hash=$1 139 key=$2 140 shift 2 141 eval "eval \"\$${Hash_config_varname_prefix}${hash}_${key} \\\"\\\$@\\\"\"" 142 } 143 144 145 # Emulates something similar to: isset(hash[key]) or hash[key]==NULL 146 # 147 # Params: 148 # 1 - hash 149 # 2 - key 150 # Returns: 151 # 0 - there is such key 152 # 1 - there is no such key 153 function hash_is_set { 154 eval "if [[ \"\${${Hash_config_varname_prefix}${1}_${2}-a}\" = \"a\" && 155 \"\${${Hash_config_varname_prefix}${1}_${2}-b}\" = \"b\" ]]; then return 1; else return 0; fi" 156 } 157 158 159 # Emulates something similar to: 160 # foreach($hash as $key => $value) { fun($key,$value); } 161 # 162 # It is possible to write different variations of this function. 163 # Here we use a function call to make it as "generic" as possible. 164 # 165 # Params: 166 # 1 - hash 167 # 2 - function name 168 function hash_foreach { 169 local keyname oldIFS="$IFS" 170 IFS=' ' 171 for i in $(eval "echo \${!${Hash_config_varname_prefix}${1}_*}"); do 172 keyname=$(eval "echo \${i##${Hash_config_varname_prefix}${1}_}") 173 eval "$2 $keyname \"\$$i\"" 174 done 175 IFS="$oldIFS" 176 } 177 178 # NOTE: In lines 103 and 116, ampersand changed. 179 # But, it doesn't matter, because these are comment lines anyhow.
Here is an example script using the foregoing hash library.
Example A-22. Colorizing text using hash functions
1 #!/bin/bash 2 # hash-example.sh: Colorizing text. 3 # Author: Mariusz Gniazdowski <mgniazd-at-gmail.com> 4 5 . Hash.lib # Load the library of functions. 6 7 hash_set colors red "\033[0;31m" 8 hash_set colors blue "\033[0;34m" 9 hash_set colors light_blue "\033[1;34m" 10 hash_set colors light_red "\033[1;31m" 11 hash_set colors cyan "\033[0;36m" 12 hash_set colors light_green "\033[1;32m" 13 hash_set colors light_gray "\033[0;37m" 14 hash_set colors green "\033[0;32m" 15 hash_set colors yellow "\033[1;33m" 16 hash_set colors light_purple "\033[1;35m" 17 hash_set colors purple "\033[0;35m" 18 hash_set colors reset_color "\033[0;00m" 19 20 21 # $1 - keyname 22 # $2 - value 23 try_colors() { 24 echo -en "$2" 25 echo "This line is $1." 26 } 27 hash_foreach colors try_colors 28 hash_echo colors reset_color -en 29 30 echo -e '\nLet us overwrite some colors with yellow.\n' 31 # It's hard to read yellow text on some terminals. 32 hash_dup colors yellow red light_green blue green light_gray cyan 33 hash_foreach colors try_colors 34 hash_echo colors reset_color -en 35 36 echo -e '\nLet us delete them and try colors once more . . .\n' 37 38 for i in red light_green blue green light_gray cyan; do 39 hash_unset colors $i 40 done 41 hash_foreach colors try_colors 42 hash_echo colors reset_color -en 43 44 hash_set other txt "Other examples . . ." 45 hash_echo other txt 46 hash_get_into other txt text 47 echo $text 48 49 hash_set other my_fun try_colors 50 hash_call other my_fun purple "`hash_echo colors purple`" 51 hash_echo colors reset_color -en 52 53 echo; echo "Back to normal?"; echo 54 55 exit $? 56 57 # On some terminals, the "light" colors print in bold, 58 # and end up looking darker than the normal ones. 59 # Why is this? 60
Now for a script that installs and mountsthose cute USB keychain solid-state "hard drives."
Example A-23. Mounting USB keychain storage devices
1 #!/bin/bash 2 # ==> usb.sh 3 # ==> Script for mounting and installing pen/keychain USB storage devices. 4 # ==> Runs as root at system startup (see below). 5 # ==> 6 # ==> Newer Linux distros (2004 or later) autodetect 7 # ==> and install USB pen drives, and therefore don't need this script. 8 # ==> But, it's still instructive. 9 10 # This code is free software covered by GNU GPL license version 2 or above. 11 # Please refer to http://www.gnu.org/ for the full license text. 12 # 13 # Some code lifted from usb-mount by Michael Hamilton's usb-mount (LGPL) 14 #+ see http://users.actrix.co.nz/michael/usbmount.html 15 # 16 # INSTALL 17 # ------- 18 # Put this in /etc/hotplug/usb/diskonkey. 19 # Then look in /etc/hotplug/usb.distmap, and copy all usb-storage entries 20 #+ into /etc/hotplug/usb.usermap, substituting "usb-storage" for "diskonkey". 21 # Otherwise this code is only run during the kernel module invocation/removal 22 #+ (at least in my tests), which defeats the purpose. 23 # 24 # TODO 25 # ---- 26 # Handle more than one diskonkey device at one time (e.g. /dev/diskonkey1 27 #+ and /mnt/diskonkey1), etc. The biggest problem here is the handling in 28 #+ devlabel, which I haven't yet tried. 29 # 30 # AUTHOR and SUPPORT 31 # ------------------ 32 # Konstantin Riabitsev, <icon linux duke edu>. 33 # Send any problem reports to my email address at the moment. 34 # 35 # ==> Comments added by ABS Guide author. 36 37 38 39 SYMLINKDEV=/dev/diskonkey 40 MOUNTPOINT=/mnt/diskonkey 41 DEVLABEL=/sbin/devlabel 42 DEVLABELCONFIG=/etc/sysconfig/devlabel 43 IAM=$0 44 45 ## 46 # Functions lifted near-verbatim from usb-mount code. 47 # 48 function allAttachedScsiUsb { 49 find /proc/scsi/ -path '/proc/scsi/usb-storage*' -type f | xargs grep -l 'Attached: Yes' 50 } 51 function scsiDevFromScsiUsb { 52 echo $1 | awk -F"[-/]" '{ n=$(NF-1); print "/dev/sd" substr("abcdefghijklmnopqrstuvwxyz", n+1, 53 1) }' 54 } 55 56 if [ "${ACTION}" = "add" ] && [ -f "${DEVICE}" ]; then 57 ## 58 # lifted from usbcam code. 59 # 60 if [ -f /var/run/console.lock ]; then 61 CONSOLEOWNER=`cat /var/run/console.lock` 62 elif [ -f /var/lock/console.lock ]; then 63 CONSOLEOWNER=`cat /var/lock/console.lock` 64 else 65 CONSOLEOWNER= 66 fi 67 for procEntry in $(allAttachedScsiUsb); do 68 scsiDev=$(scsiDevFromScsiUsb $procEntry) 69 # Some bug with usb-storage? 70 # Partitions are not in /proc/partitions until they are accessed 71 #+ somehow. 72 /sbin/fdisk -l $scsiDev >/dev/null 73 ## 74 # Most devices have partitioning info, so the data would be on 75 #+ /dev/sd?1. However, some stupider ones don't have any partitioning 76 #+ and use the entire device for data storage. This tries to 77 #+ guess semi-intelligently if we have a /dev/sd?1 and if not, then 78 #+ it uses the entire device and hopes for the better. 79 # 80 if grep -q `basename $scsiDev`1 /proc/partitions; then 81 part="$scsiDev""1" 82 else 83 part=$scsiDev 84 fi 85 ## 86 # Change ownership of the partition to the console user so they can 87 #+ mount it. 88 # 89 if [ ! -z "$CONSOLEOWNER" ]; then 90 chown $CONSOLEOWNER:disk $part 91 fi 92 ## 93 # This checks if we already have this UUID defined with devlabel. 94 # If not, it then adds the device to the list. 95 # 96 prodid=`$DEVLABEL printid -d $part` 97 if ! grep -q $prodid $DEVLABELCONFIG; then 98 # cross our fingers and hope it works 99 $DEVLABEL add -d $part -s $SYMLINKDEV 2>/dev/null 100 fi 101 ## 102 # Check if the mount point exists and create if it doesn't. 103 # 104 if [ ! -e $MOUNTPOINT ]; then 105 mkdir -p $MOUNTPOINT 106 fi 107 ## 108 # Take care of /etc/fstab so mounting is easy. 109 # 110 if ! grep -q "^$SYMLINKDEV" /etc/fstab; then 111 # Add an fstab entry 112 echo -e \ 113 "$SYMLINKDEV\t\t$MOUNTPOINT\t\tauto\tnoauto,owner,kudzu 0 0" \ 114 >> /etc/fstab 115 fi 116 done 117 if [ ! -z "$REMOVER" ]; then 118 ## 119 # Make sure this script is triggered on device removal. 120 # 121 mkdir -p `dirname $REMOVER` 122 ln -s $IAM $REMOVER 123 fi 124 elif [ "${ACTION}" = "remove" ]; then 125 ## 126 # If the device is mounted, unmount it cleanly. 127 # 128 if grep -q "$MOUNTPOINT" /etc/mtab; then 129 # unmount cleanly 130 umount -l $MOUNTPOINT 131 fi 132 ## 133 # Remove it from /etc/fstab if it's there. 134 # 135 if grep -q "^$SYMLINKDEV" /etc/fstab; then 136 grep -v "^$SYMLINKDEV" /etc/fstab > /etc/.fstab.new 137 mv -f /etc/.fstab.new /etc/fstab 138 fi 139 fi 140 141 exit 0
Here is something to warm the hearts of webmasters and mistresseseverywhere: a script that saves weblogs.
Example A-24. Preserving weblogs
1 #!/bin/bash 2 # archiveweblogs.sh v1.0 3 4 # Troy Engel <tengel@fluid.com> 5 # Slightly modified by document author. 6 # Used with permission. 7 # 8 # This script will preserve the normally rotated and 9 #+ thrown away weblogs from a default RedHat/Apache installation. 10 # It will save the files with a date/time stamp in the filename, 11 #+ bzipped, to a given directory. 12 # 13 # Run this from crontab nightly at an off hour, 14 #+ as bzip2 can suck up some serious CPU on huge logs: 15 # 0 2 * * * /opt/sbin/archiveweblogs.sh 16 17 18 PROBLEM=66 19 20 # Set this to your backup dir. 21 BKP_DIR=/opt/backups/weblogs 22 23 # Default Apache/RedHat stuff 24 LOG_DAYS="4 3 2 1" 25 LOG_DIR=/var/log/httpd 26 LOG_FILES="access_log error_log" 27 28 # Default RedHat program locations 29 LS=/bin/ls 30 MV=/bin/mv 31 ID=/usr/bin/id 32 CUT=/bin/cut 33 COL=/usr/bin/column 34 BZ2=/usr/bin/bzip2 35 36 # Are we root? 37 USER=`$ID -u` 38 if [ "X$USER" != "X0" ]; then 39 echo "PANIC: Only root can run this script!" 40 exit $PROBLEM 41 fi 42 43 # Backup dir exists/writable? 44 if [ ! -x $BKP_DIR ]; then 45 echo "PANIC: $BKP_DIR doesn't exist or isn't writable!" 46 exit $PROBLEM 47 fi 48 49 # Move, rename and bzip2 the logs 50 for logday in $LOG_DAYS; do 51 for logfile in $LOG_FILES; do 52 MYFILE="$LOG_DIR/$logfile.$logday" 53 if [ -w $MYFILE ]; then 54 DTS=`$LS -lgo --time-style=+%Y%m%d $MYFILE | $COL -t | $CUT -d ' ' -f7` 55 $MV $MYFILE $BKP_DIR/$logfile.$DTS 56 $BZ2 $BKP_DIR/$logfile.$DTS 57 else 58 # Only spew an error if the file exits (ergo non-writable). 59 if [ -f $MYFILE ]; then 60 echo "ERROR: $MYFILE not writable. Skipping." 61 fi 62 fi 63 done 64 done 65 66 exit 0
How do you keep the shell from expanding and reinterpretingstrings?
Example A-25. Protecting literal strings
1 #! /bin/bash 2 # protect_literal.sh 3 4 # set -vx 5 6 :<<-'_Protect_Literal_String_Doc' 7 8 Copyright (c) Michael S. Zick, 2003; All Rights Reserved 9 License: Unrestricted reuse in any form, for any purpose. 10 Warranty: None 11 Revision: $ID$ 12 13 Documentation redirected to the Bash no-operation. 14 Bash will '/dev/null' this block when the script is first read. 15 (Uncomment the above set command to see this action.) 16 17 Remove the first (Sha-Bang) line when sourcing this as a library 18 procedure. Also comment out the example use code in the two 19 places where shown. 20 21 22 Usage: 23 _protect_literal_str 'Whatever string meets your ${fancy}' 24 Just echos the argument to standard out, hard quotes 25 restored. 26 27 $(_protect_literal_str 'Whatever string meets your ${fancy}') 28 as the right-hand-side of an assignment statement. 29 30 Does: 31 As the right-hand-side of an assignment, preserves the 32 hard quotes protecting the contents of the literal during 33 assignment. 34 35 Notes: 36 The strange names (_*) are used to avoid trampling on 37 the user's chosen names when this is sourced as a 38 library. 39 40 _Protect_Literal_String_Doc 41 42 # The 'for illustration' function form 43 44 _protect_literal_str() { 45 46 # Pick an un-used, non-printing character as local IFS. 47 # Not required, but shows that we are ignoring it. 48 local IFS=$'\x1B' # \ESC character 49 50 # Enclose the All-Elements-Of in hard quotes during assignment. 51 local tmp=$'\x27'$@$'\x27' 52 # local tmp=$'\''$@$'\'' # Even uglier. 53 54 local len=${#tmp} # Info only. 55 echo $tmp is $len long. # Output AND information. 56 } 57 58 # This is the short-named version. 59 _pls() { 60 local IFS=$'x1B' # \ESC character (not required) 61 echo $'\x27'$@$'\x27' # Hard quoted parameter glob 62 } 63 64 # :<<-'_Protect_Literal_String_Test' 65 # # # Remove the above "# " to disable this code. # # # 66 67 # See how that looks when printed. 68 echo 69 echo "- - Test One - -" 70 _protect_literal_str 'Hello $user' 71 _protect_literal_str 'Hello "${username}"' 72 echo 73 74 # Which yields: 75 # - - Test One - - 76 # 'Hello $user' is 13 long. 77 # 'Hello "${username}"' is 21 long. 78 79 # Looks as expected, but why all of the trouble? 80 # The difference is hidden inside the Bash internal order 81 #+ of operations. 82 # Which shows when you use it on the RHS of an assignment. 83 84 # Declare an array for test values. 85 declare -a arrayZ 86 87 # Assign elements with various types of quotes and escapes. 88 arrayZ=( zero "$(_pls 'Hello ${Me}')" 'Hello ${You}' "\'Pass: ${pw}\'" ) 89 90 # Now list that array and see what is there. 91 echo "- - Test Two - -" 92 for (( i=0 ; i<${#arrayZ[*]} ; i++ )) 93 do 94 echo Element $i: ${arrayZ[$i]} is: ${#arrayZ[$i]} long. 95 done 96 echo 97 98 # Which yields: 99 # - - Test Two - - 100 # Element 0: zero is: 4 long. # Our marker element 101 # Element 1: 'Hello ${Me}' is: 13 long. # Our "$(_pls '...' )" 102 # Element 2: Hello ${You} is: 12 long. # Quotes are missing 103 # Element 3: \'Pass: \' is: 10 long. # ${pw} expanded to nothing 104 105 # Now make an assignment with that result. 106 declare -a array2=( ${arrayZ[@]} ) 107 108 # And print what happened. 109 echo "- - Test Three - -" 110 for (( i=0 ; i<${#array2[*]} ; i++ )) 111 do 112 echo Element $i: ${array2[$i]} is: ${#array2[$i]} long. 113 done 114 echo 115 116 # Which yields: 117 # - - Test Three - - 118 # Element 0: zero is: 4 long. # Our marker element. 119 # Element 1: Hello ${Me} is: 11 long. # Intended result. 120 # Element 2: Hello is: 5 long. # ${You} expanded to nothing. 121 # Element 3: 'Pass: is: 6 long. # Split on the whitespace. 122 # Element 4: ' is: 1 long. # The end quote is here now. 123 124 # Our Element 1 has had its leading and trailing hard quotes stripped. 125 # Although not shown, leading and trailing whitespace is also stripped. 126 # Now that the string contents are set, Bash will always, internally, 127 #+ hard quote the contents as required during its operations. 128 129 # Why? 130 # Considering our "$(_pls 'Hello ${Me}')" construction: 131 # " ... " -> Expansion required, strip the quotes. 132 # $( ... ) -> Replace with the result of..., strip this. 133 # _pls ' ... ' -> called with literal arguments, strip the quotes. 134 # The result returned includes hard quotes; BUT the above processing 135 #+ has already been done, so they become part of the value assigned. 136 # 137 # Similarly, during further usage of the string variable, the ${Me} 138 #+ is part of the contents (result) and survives any operations 139 # (Until explicitly told to evaluate the string). 140 141 # Hint: See what happens when the hard quotes ($'\x27') are replaced 142 #+ with soft quotes ($'\x22') in the above procedures. 143 # Interesting also is to remove the addition of any quoting. 144 145 # _Protect_Literal_String_Test 146 # # # Remove the above "# " to disable this code. # # # 147 148 exit 0
What if you wantthe shell to expandand reinterpret strings?
Example A-26. Unprotecting literal strings
1 #! /bin/bash 2 # unprotect_literal.sh 3 4 # set -vx 5 6 :<<-'_UnProtect_Literal_String_Doc' 7 8 Copyright (c) Michael S. Zick, 2003; All Rights Reserved 9 License: Unrestricted reuse in any form, for any purpose. 10 Warranty: None 11 Revision: $ID$ 12 13 Documentation redirected to the Bash no-operation. Bash will 14 '/dev/null' this block when the script is first read. 15 (Uncomment the above set command to see this action.) 16 17 Remove the first (Sha-Bang) line when sourcing this as a library 18 procedure. Also comment out the example use code in the two 19 places where shown. 20 21 22 Usage: 23 Complement of the "$(_pls 'Literal String')" function. 24 (See the protect_literal.sh example.) 25 26 StringVar=$(_upls ProtectedSringVariable) 27 28 Does: 29 When used on the right-hand-side of an assignment statement; 30 makes the substitions embedded in the protected string. 31 32 Notes: 33 The strange names (_*) are used to avoid trampling on 34 the user's chosen names when this is sourced as a 35 library. 36 37 38 _UnProtect_Literal_String_Doc 39 40 _upls() { 41 local IFS=$'x1B' # \ESC character (not required) 42 eval echo $@ # Substitution on the glob. 43 } 44 45 # :<<-'_UnProtect_Literal_String_Test' 46 # # # Remove the above "# " to disable this code. # # # 47 48 49 _pls() { 50 local IFS=$'x1B' # \ESC character (not required) 51 echo $'\x27'$@$'\x27' # Hard quoted parameter glob 52 } 53 54 # Declare an array for test values. 55 declare -a arrayZ 56 57 # Assign elements with various types of quotes and escapes. 58 arrayZ=( zero "$(_pls 'Hello ${Me}')" 'Hello ${You}' "\'Pass: ${pw}\'" ) 59 60 # Now make an assignment with that result. 61 declare -a array2=( ${arrayZ[@]} ) 62 63 # Which yielded: 64 # - - Test Three - - 65 # Element 0: zero is: 4 long # Our marker element. 66 # Element 1: Hello ${Me} is: 11 long # Intended result. 67 # Element 2: Hello is: 5 long # ${You} expanded to nothing. 68 # Element 3: 'Pass: is: 6 long # Split on the whitespace. 69 # Element 4: ' is: 1 long # The end quote is here now. 70 71 # set -vx 72 73 # Initialize 'Me' to something for the embedded ${Me} substitution. 74 # This needs to be done ONLY just prior to evaluating the 75 #+ protected string. 76 # (This is why it was protected to begin with.) 77 78 Me="to the array guy." 79 80 # Set a string variable destination to the result. 81 newVar=$(_upls ${array2[1]}) 82 83 # Show what the contents are. 84 echo $newVar 85 86 # Do we really need a function to do this? 87 newerVar=$(eval echo ${array2[1]}) 88 echo $newerVar 89 90 # I guess not, but the _upls function gives us a place to hang 91 #+ the documentation on. 92 # This helps when we forget what a # construction like: 93 #+ $(eval echo ... ) means. 94 95 # What if Me isn't set when the protected string is evaluated? 96 unset Me 97 newestVar=$(_upls ${array2[1]}) 98 echo $newestVar 99 100 # Just gone, no hints, no runs, no errors. 101 102 # Why in the world? 103 # Setting the contents of a string variable containing character 104 #+ sequences that have a meaning in Bash is a general problem in 105 #+ script programming. 106 # 107 # This problem is now solved in eight lines of code 108 #+ (and four pages of description). 109 110 # Where is all this going? 111 # Dynamic content Web pages as an array of Bash strings. 112 # Content set per request by a Bash 'eval' command 113 #+ on the stored page template. 114 # Not intended to replace PHP, just an interesting thing to do. 115 ### 116 # Don't have a webserver application? 117 # No problem, check the example directory of the Bash source; 118 #+ there is a Bash script for that also. 119 120 # _UnProtect_Literal_String_Test 121 # # # Remove the above "# " to disable this code. # # # 122 123 exit 0
This powerful script helps hunt down spammers.
Example A-27. Spammer Identification
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-27.txt
Another anti-spam script.
Example A-28. Spammer Hunt
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-28.txt
"Little Monster's"front end to wget.
Example A-29. Making wgeteasier to use
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-29.txt
Example A-30. A "podcasting"script
1 #!/bin/bash 2 3 # bashpodder.sh: 4 # By Linc 10/1/2004 5 # Find the latest script at http://linc.homeunix.org:8080/scripts/bashpodder 6 # Last revision 12/14/2004 - Many Contributors! 7 # If you use this and have made improvements or have comments 8 # drop me an email at linc dot fessenden at gmail dot com 9 # I'd appreciate it! 10 11 # ==> ABS Guide extra comments. 12 13 # ==> Author of this script has kindly granted permission 14 # ==>+ for inclusion in ABS Guide. 15 16 17 # ==> ################################################################ 18 # 19 # ==> What is "podcasting"? 20 21 # ==> It's broadcasting "radio shows" over the Internet. 22 # ==> These shows can be played on iPods and other music file players. 23 24 # ==> This script makes it possible. 25 # ==> See documentation at the script author's site, above. 26 27 # ==> ################################################################ 28 29 30 # Make script crontab friendly: 31 cd $(dirname $0) 32 # ==> Change to directory where this script lives. 33 34 # datadir is the directory you want podcasts saved to: 35 datadir=$(date +%Y-%m-%d) 36 # ==> Will create a directory with the name: YYYY-MM-DD 37 38 # Check for and create datadir if necessary: 39 if test ! -d $datadir 40 then 41 mkdir $datadir 42 fi 43 44 # Delete any temp file: 45 rm -f temp.log 46 47 # Read the bp.conf file and wget any url not already in the podcast.log file: 48 while read podcast 49 do # ==> Main action follows. 50 file=$(wget -q $podcast -O - | tr '\r' '\n' | tr \' \" | sed -n 's/.*url="\([^"]*\)".*/\1/p') 51 for url in $file 52 do 53 echo $url >> temp.log 54 if ! grep "$url" podcast.log > /dev/null 55 then 56 wget -q -P $datadir "$url" 57 fi 58 done 59 done < bp.conf 60 61 # Move dynamically created log file to permanent log file: 62 cat podcast.log >> temp.log 63 sort temp.log | uniq > podcast.log 64 rm temp.log 65 # Create an m3u playlist: 66 ls $datadir | grep -v m3u > $datadir/podcast.m3u 67 68 69 exit 0
To end this section, a review of the basics . . . and more.
Example A-31. Basics Reviewed
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-31.txt
Example A-32. An expanded cdcommand
https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-32.txt