├── 001-inpath.sh ├── 002-validalnum.sh ├── 003-normdate.sh ├── 004-nicenumber.sh ├── 005-validint.sh ├── 006-validfloat.sh ├── 007-valid-date.sh ├── 008-echon.sh ├── 009-scriptbc.sh ├── 010-filelock.sh ├── 011-colors.sh ├── 012-library-test.sh ├── 012-library.sh ├── 013-guessword.sh ├── 013-hilow.sh ├── 014-nfmt.sh ├── 014-ragged.txt ├── 014-ragged.txt.shp ├── 015-newrm.sh ├── 016-unrm.sh ├── 017-logrm.sh ├── 018-formatdir.sh ├── 019-locate.sh ├── 019-mklocatedb.sh ├── 020-DIR.sh ├── 021-findman.sh ├── 022-timein.sh ├── 023-remember.sh ├── 023-remindme.sh ├── 024-calc.sh ├── 025-checkspelling.sh ├── 026-shpell.sh ├── 027-spelldict.sh ├── 028-convertatemp.sh ├── 029-loancalc.sh ├── 030-addagenda.sh ├── 030-agenda.sh ├── 031-numberlines.sh ├── 032-showfile.sh ├── 033-toolong.sh ├── 034-quota.sh ├── 035-mysftp.sh ├── 036-cgrep.sh ├── 037-zcat.sh ├── 038-bestcompress.sh ├── 039-fquota.sh ├── 040-diskhogs.sh ├── 041-diskspace.sh ├── 042-newdf.sh ├── 043-mkslocate.sh ├── 043-slocate.sh ├── 044-adduser.sh ├── 045-suspenduser.sh ├── 046-deleteuser.sh ├── 047-validator.sh ├── 048-fixguest.sh ├── 049-findsuid.sh ├── 050-set-date.sh ├── 051-enabled.sh ├── 052-killall.sh ├── 053-verifycron.sh ├── 054-docron.sh ├── 055-rotatelogs.sh ├── 056-backup.sh ├── 057-archivedir.sh ├── 058-connecttime.sh ├── 059-ftpget.sh ├── 060-bbcnews.sh ├── 061-getlinks.sh ├── 062-define.sh ├── 063-weather.sh ├── 064-checklibrary.sh ├── 065-moviedata.sh ├── 066-exchangerate.sh ├── 066-getexchrate.sh ├── 067-getstock.sh ├── 067-portfolio.sh ├── 068-changetrack.sh ├── 069-showcgienv.sh ├── 070-logsearch.cgi ├── 070-yahoo-search.html ├── 071-getdope.sh ├── 071-kevin-and-kell.cgi ├── 072-contactus.cgi ├── 072-contactus.html ├── 073-photoalbum.cgi ├── 074-guestbook.cgi ├── 074-guestbook.txt ├── 075-counter.sh ├── 075-page-with-counter.html ├── 075-streamfile.cgi ├── 075-updatecounter.sh ├── 076-randomquote.sh ├── 076-ssi-sample.html ├── 077-checklinks.sh ├── 078-checkexternal.sh ├── 079-webspell.sh ├── 080-apm-footer.html ├── 080-apm.cgi ├── 080-htpasswd-b.pl ├── 081-ftpsyncup.sh ├── 082-ftpsyncdown.sh ├── 083-sftpsync.sh ├── 083-ssync.sh ├── 084-webaccess.sh ├── 085-enginehits.sh ├── 085-searchinfo.sh ├── 086-weberrors.sh ├── 087-remotebackup-filelist ├── 087-remotebackup.sh ├── 087-trimmailbox.sh ├── 088-unpacker.sh ├── 089-xferlog.sh ├── 090-getstats.sh ├── 090-netperf.sh ├── 091-renicename.sh ├── 091-watch-and-nice.sh ├── 092-addvirtual.sh ├── 093-listmacusers.sh ├── 094-addmacuser.sh ├── 095-addmacalias.sh ├── 096-titleterm.sh ├── 097-itunelist.sh ├── 098-open2.sh ├── 099-unscramble.sh ├── 100-hangman.sh ├── 101-states.sh ├── AllFiles.tgz ├── README.md ├── alice.txt.gz ├── big-word-list.txt.gz ├── how-many-commands.sh ├── long-words.txt.gz ├── palindrome.sh ├── ragged.txt ├── sample.crontab ├── state.capitals.txt ├── text.snippet.txt └── tinyscript.sh /001-inpath.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # inpath - verify that a specified program is either valid as-is, 3 | # or can be found in the PATH directory list. 4 | 5 | in_path() 6 | { 7 | # given a command and the PATH, try to find the command. Returns 8 | # 0 if found and executable, 1 if not. Note that this temporarily modifies 9 | # the the IFS (input field seperator), but restores it upon completion. 10 | 11 | cmd=$1 path=$2 retval=1 12 | oldIFS=$IFS IFS=":" 13 | 14 | for directory in $path 15 | do 16 | if [ -x $directory/$cmd ] ; then 17 | retval=0 # if we're here, we found $cmd in $directory 18 | fi 19 | done 20 | IFS=$oldIFS 21 | return $retval 22 | } 23 | 24 | checkForCmdInPath() 25 | { 26 | var=$1 27 | 28 | # The variable slicing notation in the following conditional 29 | # needs some explanation: ${var#expr} returns everything after 30 | # the match for 'expr' in the variable value (if any), and 31 | # ${var%expr} returns everything that doesn't match (in this 32 | # case just the very first character. You can also do this in 33 | # Bash with ${var:0:1} and you could use cut too: cut -c1 34 | 35 | if [ "$var" != "" ] ; then 36 | if [ "${var%${var#?}}" = "/" ] ; then 37 | if [ ! -x $var ] ; then 38 | return 1 39 | fi 40 | elif ! in_path $var $PATH ; then 41 | return 2 42 | fi 43 | fi 44 | } 45 | 46 | if [ $# -ne 1 ] ; then 47 | echo "Usage: $0 command" >&2 ; exit 1 48 | fi 49 | 50 | checkForCmdInPath "$1" 51 | case $? in 52 | 0 ) echo "$1 found in PATH" ;; 53 | 1 ) echo "$1 not found or not executable" ;; 54 | 2 ) echo "$1 not found in PATH" ;; 55 | esac 56 | 57 | exit 0 58 | -------------------------------------------------------------------------------- /002-validalnum.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # validalAlphaNum - Ensures that input only consists of alphabetical 3 | # and numeric characters. 4 | 5 | validAlphaNum() 6 | { 7 | # validate arg: returns 0 if all upper+lower+digits, 1 otherwise 8 | 9 | # Remove all unacceptable chars 10 | compressed="$(echo $1 | sed -e 's/[^[:alnum:]]//g')" 11 | 12 | if [ "$compressed" != "$input" ] ; then 13 | return 1 14 | else 15 | return 0 16 | fi 17 | } 18 | 19 | # Sample usage of this function in a script 20 | 21 | echo -n "Enter input: " 22 | read input 23 | 24 | if ! validAlphaNum "$input" ; then 25 | echo "Your input must consist of only letters and numbers." >&2 26 | exit 1 27 | else 28 | echo "Input is valid." 29 | fi 30 | 31 | exit 0 32 | -------------------------------------------------------------------------------- /003-normdate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # normdate - Normalizes month field in date specification 3 | # to three letters, first letter capitalized. A helper 4 | # function for hack #7, validdate. Exits w/ zero if no error. 5 | 6 | monthnoToName() 7 | { 8 | # sets the variable 'month' to the appropriate value 9 | case $1 in 10 | 1 ) month="Jan" ;; 2 ) month="Feb" ;; 11 | 3 ) month="Mar" ;; 4 ) month="Apr" ;; 12 | 5 ) month="May" ;; 6 ) month="Jun" ;; 13 | 7 ) month="Jul" ;; 8 ) month="Aug" ;; 14 | 9 ) month="Sep" ;; 10) month="Oct" ;; 15 | 11) month="Nov" ;; 12) month="Dec" ;; 16 | * ) echo "$0: Unknown numeric month value $1" >&2; exit 1 17 | esac 18 | return 0 19 | } 20 | 21 | ## Begin main script 22 | 23 | if [ $# -eq 1 ] ; then # try to compensate for / or - formats 24 | set -- $(echo $1 | sed 's/[\/\-]/ /g') 25 | fi 26 | 27 | if [ $# -ne 3 ] ; then 28 | echo "Usage: $0 month day year" >&2 29 | echo "Typical input formats are August 3 1962 and 8 3 2002" >&2 30 | exit 1 31 | fi 32 | 33 | if [ $3 -lt 99 ] ; then 34 | echo "$0: expected four-digit year value." >&2; exit 1 35 | fi 36 | 37 | if [ -z $(echo $1|sed 's/[[:digit:]]//g') ]; then 38 | monthnoToName $1 39 | else 40 | # normalize to first three letters, first upper, rest lowercase 41 | month="$(echo $1|cut -c1|tr '[:lower:]' '[:upper:]')" 42 | month="$month$(echo $1|cut -c2-3 | tr '[:upper:]' '[:lower:]')" 43 | fi 44 | 45 | echo $month $2 $3 46 | 47 | exit 0 48 | -------------------------------------------------------------------------------- /004-nicenumber.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # nicenumber - given a number, show it with comma separated values 4 | # expects DD and TD to be instantiated. instantiates nicenum 5 | # or, if a second arg is specified, the output is echoed to stdout 6 | 7 | nicenumber() 8 | { 9 | # Note that we use the '.' as the decimal separator for parsing 10 | # the INPUT value to this script. The output value is as specified 11 | # by the user with the -d flag, if different from a '.' 12 | 13 | integer=$(echo $1 | cut -d. -f1) # left of the decimal 14 | decimal=$(echo $1 | cut -d. -f2) # right of the decimal 15 | 16 | if [ $decimal != $1 ]; then 17 | # there's a fractional part, let's include it. 18 | result="${DD:="."}$decimal" 19 | fi 20 | 21 | thousands=$integer 22 | 23 | while [ $thousands -gt 999 ]; do 24 | remainder=$(($thousands % 1000)) # three least significant digits 25 | 26 | while [ ${#remainder} -lt 3 ] ; do # force leading zeroes as needed 27 | remainder="0$remainder" 28 | done 29 | 30 | thousands=$(($thousands / 1000)) # to left of remainder, if any 31 | result="${TD:=","}${remainder}${result}" # builds right-to-left 32 | done 33 | 34 | nicenum="${thousands}${result}" 35 | if [ ! -z $2 ] ; then 36 | echo $nicenum 37 | fi 38 | } 39 | 40 | DD="." # decimal point delimiter, between integer & fractional value 41 | TD="," # thousands delimiter, separates every three digits 42 | 43 | while getopts "d:t:" opt; do 44 | case $opt in 45 | d ) DD="$OPTARG" ;; 46 | t ) TD="$OPTARG" ;; 47 | esac 48 | done 49 | 50 | shift $(($OPTIND - 1)) 51 | 52 | if [ $# -eq 0 ] ; then 53 | cat << "EOF" >&2 54 | Usage: $(basename $0) [-d c] [-t c] numeric value 55 | -d specifies the decimal point delimiter (default '.') 56 | -t specifies the thousands delimiter (default ',') 57 | EOF 58 | exit 1 59 | fi 60 | 61 | nicenumber $1 1 # second arg forces this to 'echo' output 62 | 63 | exit 0 64 | -------------------------------------------------------------------------------- /005-validint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # validint - validate integer input, allow negative ints too 3 | 4 | validint() 5 | { 6 | # validate first field. Optionally test against min value $2 and/or 7 | # max value $3: if you'd rather skip these tests, send "" as values. 8 | # returns 1 for error, 0 for success. 9 | 10 | number="$1"; min="$2"; max="$3" 11 | 12 | if [ -z $number ] ; then 13 | echo "You didn't enter anything. Unacceptable." >&2 ; return 1 14 | fi 15 | 16 | if [ "${number%${number#?}}" = "-" ] ; then # first char '-' ? 17 | testvalue="${number#?}" # all but first character 18 | else 19 | testvalue="$number" 20 | fi 21 | 22 | nodigits="$(echo $testvalue | sed 's/[[:digit:]]//g')" 23 | 24 | if [ ! -z $nodigits ] ; then 25 | echo "Invalid number format! Only digits, no commas, spaces, etc." >&2 26 | return 1 27 | fi 28 | 29 | if [ ! -z $min ] ; then 30 | if [ "$number" -lt "$min" ] ; then 31 | echo "Your value is too small: smallest acceptable value is $min" >&2 32 | return 1 33 | fi 34 | fi 35 | if [ ! -z $max ] ; then 36 | if [ "$number" -gt "$max" ] ; then 37 | echo "Your value is too big: largest acceptable value is $max" >&2 38 | return 1 39 | fi 40 | fi 41 | return 0 42 | } 43 | 44 | # uncomment these lines to test, but beware that it'll break Hack #6 45 | # because Hack #6 wants to source this file to get the validint() 46 | # function. :-) 47 | 48 | # if validint "$1" "$2" "$3" ; then 49 | # echo "That input is a valid integer value within your constraints" 50 | # fi 51 | -------------------------------------------------------------------------------- /006-validfloat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # validfloat - test whether a number is a valid floating point value. 4 | # Note that this cannot accept scientific (1.304e5) notation. 5 | 6 | # To test whether an entered value is a valid floating point number, we 7 | # need to split the value at the decimal point, then test the first part 8 | # to see if it's a valid integer, then the second part to see if it's a 9 | # valid >=0 integer, so -30.5 is valid, but -30.-8 isn't. 10 | 11 | . 005-validint.sh # source the validint function 12 | 13 | validfloat() 14 | { 15 | fvalue="$1" 16 | 17 | if [ ! -z $(echo $fvalue | sed 's/[^.]//g') ] ; then 18 | 19 | decimalPart="$(echo $fvalue | cut -d. -f1)" 20 | fractionalPart="$(echo $fvalue | cut -d. -f2)" 21 | 22 | if [ ! -z $decimalPart ] ; then 23 | if ! validint "$decimalPart" "" "" ; then 24 | return 1 25 | fi 26 | fi 27 | 28 | if [ "${fractionalPart%${fractionalPart#?}}" = "-" ] ; then 29 | echo "Invalid floating point number: '-' not allowed \ 30 | after decimal point" >&2 31 | return 1 32 | fi 33 | if [ "$fractionalPart" != "" ] ; then 34 | if ! validint "$fractionalPart" "0" "" ; then 35 | return 1 36 | fi 37 | fi 38 | 39 | if [ "$decimalPart" = "-" -o -z $decimalPart ] ; then 40 | if [ -z $fractionalPart ] ; then 41 | echo "Invalid floating point format." >&2 ; return 1 42 | fi 43 | fi 44 | 45 | else 46 | if [ "$fvalue" = "-" ] ; then 47 | echo "Invalid floating point format." >&2 ; return 1 48 | fi 49 | 50 | if ! validint "$fvalue" "" "" ; then 51 | return 1 52 | fi 53 | fi 54 | 55 | return 0 56 | } 57 | 58 | if validfloat $1 ; then 59 | echo "$1 is a valid floating point value" 60 | fi 61 | 62 | exit 0 63 | -------------------------------------------------------------------------------- /007-valid-date.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # valid-date - validate date, taking into account leap year rules 3 | 4 | normdate="./003-normdate.sh" # hack #3 for normalizing month name 5 | 6 | exceedsDaysInMonth() 7 | { 8 | # given a month name, return 0 if the specified day value is 9 | # less than or equal to the max days in the month, 1 otherwise 10 | 11 | case $(echo $1|tr '[:upper:]' '[:lower:]') in 12 | jan* ) days=31 ;; feb* ) days=28 ;; 13 | mar* ) days=31 ;; apr* ) days=30 ;; 14 | may* ) days=31 ;; jun* ) days=30 ;; 15 | jul* ) days=31 ;; aug* ) days=31 ;; 16 | sep* ) days=30 ;; oct* ) days=31 ;; 17 | nov* ) days=30 ;; dec* ) days=31 ;; 18 | * ) echo "$0: Unknown month name $1" >&2; exit 1 19 | esac 20 | 21 | if [ $2 -lt 1 -o $2 -gt $days ] ; then 22 | return 1 23 | else 24 | return 0 # all is well 25 | fi 26 | } 27 | 28 | isLeapYear() 29 | { 30 | # this function returns 0 if a leap year, 1 otherwise 31 | # The formula for checking whether a year is a leap year is: 32 | # 1. years divisible by four are leap years, unless.. 33 | # 2. years also divisible by 100 are not leap years, except... 34 | # 3. years divisible by 400 are leap years 35 | 36 | year=$1 37 | if [ "$((year % 4))" -ne 0 ] ; then 38 | return 1 # nope, not a leap year 39 | elif [ "$((year % 400))" -eq 0 ] ; then 40 | return 0 # yes, it's a leap year 41 | elif [ "$((year % 100))" -eq 0 ] ; then 42 | return 1 43 | else 44 | return 0 45 | fi 46 | } 47 | 48 | ## Begin main script 49 | 50 | if [ $# -ne 3 ] ; then 51 | echo "Usage: $0 month day year" >&2 52 | echo "Typical input formats are August 3 1962 and 8 3 2002" >&2 53 | exit 1 54 | fi 55 | 56 | # normalize date and split back out returned values 57 | 58 | newdate="$($normdate "$@")" 59 | 60 | if [ $? -eq 1 ] ; then 61 | exit 1 # error condition already reported by normdate 62 | fi 63 | 64 | month="$(echo $newdate | cut -d\ -f1)" 65 | day="$(echo $newdate | cut -d\ -f2)" 66 | year="$(echo $newdate | cut -d\ -f3)" 67 | 68 | # Now that we have a normalized date, let's check to see if the 69 | # day value is logical 70 | 71 | if ! exceedsDaysInMonth $month "$2" ; then 72 | if [ "$month" = "Feb" -a $2 -eq 29 ] ; then 73 | if ! isLeapYear $3 ; then 74 | echo "$0: $3 is not a leap year, so Feb doesn't have 29 days" >&2 75 | exit 1 76 | fi 77 | else 78 | echo "$0: bad day value: $month doesn't have $2 days" >&2 79 | exit 1 80 | fi 81 | fi 82 | 83 | echo "Valid date: $newdate" 84 | 85 | exit 0 86 | -------------------------------------------------------------------------------- /008-echon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # echon - a script to emulate the -n flag functionality with 'echo' 4 | # for Unix systems that don't have that available. 5 | 6 | echon() 7 | { 8 | echo "$*" | tr -d '\n' 9 | } 10 | 11 | echon "this is a test: " 12 | read answer 13 | 14 | echon this is a test too " " 15 | read answer2 16 | -------------------------------------------------------------------------------- /009-scriptbc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # scriptbc - wrapper for 'bc' that returns the value of a formula 4 | 5 | if [ $1 = "-p" ] ; then 6 | precision=$2 7 | shift 2 8 | else 9 | precision=2 # default 10 | fi 11 | 12 | bc -q -l << EOF 13 | scale=$precision 14 | $* 15 | quit 16 | EOF 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /010-filelock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # filelock - a flexible file locking mechanism 4 | 5 | retries="10" # default number of retries: 10 6 | action="lock" # default action 7 | nullcmd="/bin/true" # null command for lockf 8 | 9 | while getopts "lur:" opt; do 10 | case $opt in 11 | l ) action="lock" ;; 12 | u ) action="unlock" ;; 13 | r ) retries="$OPTARG" ;; 14 | esac 15 | done 16 | shift $(($OPTIND - 1)) 17 | 18 | if [ $# -eq 0 ] ; then 19 | cat << EOF >&2 20 | Usage: $0 [-l|-u] [-r retries] lockfilename 21 | Where -l requests a lock (the default), -u requests an unlock, -r X 22 | specifies a maximum number of retries before it fails (default = $retries). 23 | EOF 24 | exit 1 25 | fi 26 | 27 | # ascertain whether we have lockf or lockfile system apps 28 | 29 | if [ -z "$(which lockfile | grep -v '^no ')" ] ; then 30 | echo "$0 failed: 'lockfile' utility not found in PATH." >&2 31 | exit 1 32 | fi 33 | 34 | if [ "$action" = "lock" ] ; then 35 | if ! lockfile -1 -r $retries "$1" 2> /dev/null; then 36 | echo "$0: Failed: Couldn't create lockfile in time" >&2 37 | exit 1 38 | fi 39 | else # action = unlock 40 | if [ ! -f "$1" ] ; then 41 | echo "$0: Warning: lockfile $1 doesn't exist to unlock" >&2 42 | exit 1 43 | fi 44 | rm -f "$1" 45 | fi 46 | 47 | exit 0 48 | -------------------------------------------------------------------------------- /011-colors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ANSI Color -- use these variables to easily have different color 4 | # and format output. Make sure to output the reset sequence after 5 | # colors (f = foreground, b = background), and use the 'off' 6 | # feature for anything you turn on. 7 | 8 | initializeANSI() 9 | { 10 | esc="" 11 | 12 | blackf="${esc}[30m"; redf="${esc}[31m"; greenf="${esc}[32m" 13 | yellowf="${esc}[33m" bluef="${esc}[34m"; purplef="${esc}[35m" 14 | cyanf="${esc}[36m"; whitef="${esc}[37m" 15 | 16 | blackb="${esc}[40m"; redb="${esc}[41m"; greenb="${esc}[42m" 17 | yellowb="${esc}[43m" blueb="${esc}[44m"; purpleb="${esc}[45m" 18 | cyanb="${esc}[46m"; whiteb="${esc}[47m" 19 | 20 | boldon="${esc}[1m"; boldoff="${esc}[22m" 21 | italicson="${esc}[3m"; italicsoff="${esc}[23m" 22 | ulon="${esc}[4m"; uloff="${esc}[24m" 23 | invon="${esc}[7m"; invoff="${esc}[27m" 24 | 25 | reset="${esc}[0m" 26 | } 27 | 28 | # note in this first use that switching colors doesn't require a reset 29 | # first - the new color overrides the old one. 30 | 31 | initializeANSI 32 | 33 | cat << EOF 34 | ${yellowf}This is a phrase in yellow${redb} and red${reset} 35 | ${boldon}This is bold${ulon} this is italics${reset} bye bye 36 | ${italicson}This is italics${italicsoff} and this is not 37 | ${ulon}This is ul${uloff} and this is not 38 | ${invon}This is inv${invoff} and this is not 39 | ${yellowf}${redb}Warning I${yellowb}${redf}Warning II${reset} 40 | EOF 41 | -------------------------------------------------------------------------------- /012-library-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Script to demonstrate use of the shell function library 4 | 5 | . 012-library.sh 6 | 7 | initializeANSI 8 | 9 | echon "First off, do you have echo in your path? (1=yes, 2=no) " 10 | read answer 11 | while ! validint $answer 1 2 ; do 12 | echon "${boldon}Try again${boldoff}. Do you have echo " 13 | echon "in your path? (1=yes, 2=no) " 14 | read answer 15 | done 16 | 17 | if ! checkForCmdInPath "echo" ; then 18 | echo "Nope, can't find the echo command." 19 | else 20 | echo "The echo command is in the PATH." 21 | fi 22 | 23 | echo "" 24 | echon "Enter a year you think might be a leap year: " 25 | read year 26 | 27 | while ! validint $year 1 9999 ; do 28 | echon "Please enter a year in the ${boldon}correct${boldoff} format: " 29 | read year 30 | done 31 | 32 | if isLeapYear $year ; then 33 | echo "${greenf}You're right! $year was a leap year.${reset}" 34 | else 35 | echo "${redf}Nope, that's not a leap year.${reset}" 36 | fi 37 | 38 | exit 0 39 | -------------------------------------------------------------------------------- /012-library.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # inpath - verify that a specified program is either valid as-is, 4 | # or can be found in the PATH directory list. 5 | 6 | in_path() 7 | { 8 | # given a command and the PATH, try to find the command. Returns 9 | # 0 if found and executable, 1 if not. Note that this temporarily modifies 10 | # the the IFS (input field seperator), but restores it upon completion. 11 | # return variable 'directory' contains the directory where the 12 | # command was found. 13 | 14 | cmd=$1 path=$2 retval=1 15 | oldIFS=$IFS IFS=":" 16 | 17 | for directory in $path 18 | do 19 | if [ -x $directory/$cmd ] ; then 20 | retval=0 # if we're here, we found $cmd in $directory 21 | fi 22 | done 23 | IFS=$oldIFS 24 | return $retval 25 | } 26 | 27 | checkForCmdInPath() 28 | { 29 | var=$1 30 | 31 | # The variable slicing notation in the following conditional 32 | # needs some explanation: ${var#expr} returns everything after 33 | # the match for 'expr' in the variable value (if any), and 34 | # ${var%expr} returns everything that doesn't match (in this 35 | # case just the very first character. You can also do this in 36 | # Bash with ${var:0:1} and you could use cut too: cut -c1 37 | 38 | if [ "$var" != "" ] ; then 39 | if [ "${var%${var#?}}" = "/" ] ; then 40 | if [ ! -x $var ] ; then 41 | return 1 42 | fi 43 | elif ! in_path $var $PATH ; then 44 | return 2 45 | fi 46 | fi 47 | return 0 48 | } 49 | 50 | # cnvalidate - Ensures that input only consists of alphabetical 51 | # and numeric characters. 52 | 53 | cnvalidate() 54 | { 55 | # validate arg: returns 0 if all upper+lower+digits, 1 otherwise 56 | 57 | # Remove all unacceptable chars 58 | compressed="$(echo $1 | sed -e 's/[^[:alnum:]]//g')" 59 | 60 | if [ "$compressed" != "$input" ] ; then 61 | return 1 62 | else 63 | return 0 64 | fi 65 | } 66 | 67 | monthnoToName() 68 | { 69 | # sets the variable 'month' to the appropriate value 70 | case $1 in 71 | 1 ) month="Jan" ;; 2 ) month="Feb" ;; 72 | 3 ) month="Mar" ;; 4 ) month="Apr" ;; 73 | 5 ) month="May" ;; 6 ) month="Jun" ;; 74 | 7 ) month="Jul" ;; 8 ) month="Aug" ;; 75 | 9 ) month="Sep" ;; 10) month="Oct" ;; 76 | 11) month="Nov" ;; 12) month="Dec" ;; 77 | * ) echo "$0: Unknown numeric month value $1" >&2; exit 1 78 | esac 79 | return 0 80 | } 81 | 82 | # nicenumber - given a number, show it with comma separated values 83 | # expects DD and TD to be instantiated. instantiates nicenum 84 | # if arg2 is specified, this function echoes output, rather than 85 | # sending it back as a variable 86 | 87 | nicenumber() 88 | { 89 | # Note that we use the '.' as the decimal separator for parsing 90 | # the INPUT value to this script. The output value is as specified 91 | # by the user with the -d flag, if different from a '.' 92 | 93 | integer=$(echo $1 | cut -d. -f1) # left of the decimal 94 | decimal=$(echo $1 | cut -d. -f2) # right of the decimal 95 | 96 | if [ $decimal != $1 ]; then 97 | # there's a fractional part, let's include it. 98 | result="${DD:="."}$decimal" 99 | fi 100 | 101 | thousands=$integer 102 | 103 | while [ $thousands -gt 999 ]; do 104 | remainder=$(($thousands % 1000)) # three least significant digits 105 | 106 | while [ ${#remainder} -lt 3 ] ; do # force leading zeroes as needed 107 | remainder="0$remainder" 108 | done 109 | 110 | thousands=$(($thousands / 1000)) # to left of remainder, if any 111 | result="${TD:=","}${remainder}${result}" # builds right-to-left 112 | done 113 | 114 | nicenum="${thousands}${result}" 115 | 116 | if [ ! -z $2 ] ; then 117 | echo $nicenum 118 | fi 119 | } 120 | 121 | # validint - validate integer input, allow negative ints too 122 | 123 | validint() 124 | { 125 | # validate first field. Optionally test against min value $2 and/or 126 | # max value $3: if you'd rather skip these tests, send "" as values. 127 | # returns 1 for error, 0 for success 128 | 129 | number="$1"; min="$2"; max="$3" 130 | 131 | if [ -z "$number" ] ; then 132 | echo "You didn't enter anything. Unacceptable." >&2 ; return 1 133 | fi 134 | 135 | if [ "${number%${number#?}}" = "-" ] ; then # first char '-' ? 136 | testvalue="${number#?}" # all but first character 137 | else 138 | testvalue="$number" 139 | fi 140 | 141 | nodigits="$(echo $testvalue | sed 's/[[:digit:]]//g')" 142 | 143 | if [ ! -z "$nodigits" ] ; then 144 | echo "Invalid number format! Only digits, no commas, spaces, etc." >&2 145 | return 1 146 | fi 147 | 148 | if [ ! -z "$min" ] ; then 149 | if [ "$number" -lt "$min" ] ; then 150 | echo "Your value is too small: smallest acceptable value is $min" >&2 151 | return 1 152 | fi 153 | fi 154 | if [ ! -z "$max" ] ; then 155 | if [ "$number" -gt "$max" ] ; then 156 | echo "Your value is too big: largest acceptable value is $max" >&2 157 | return 1 158 | fi 159 | fi 160 | return 0 161 | } 162 | 163 | # validfloat - test whether a number is a valid floating point value. 164 | # Note that this cannot accept scientific (1.304e5) notation. 165 | 166 | # To test whether an entered value is a valid floating point number, we 167 | # need to split the value at the decimal point, then test the first part 168 | # to see if it's a valid integer, then the second part to see if it's a 169 | # valid >=0 integer, so -30.5 is valid, but -30.-8 isn't. Returns 0 on 170 | # success, 1 on failure. 171 | 172 | validfloat() 173 | { 174 | fvalue="$1" 175 | 176 | if [ ! -z "$(echo $fvalue | sed 's/[^.]//g')" ] ; then 177 | 178 | decimalPart="$(echo $fvalue | cut -d. -f1)" 179 | fractionalPart="$(echo $fvalue | cut -d. -f2)" 180 | 181 | if [ ! -z "$decimalPart" ] ; then 182 | if ! validint "$decimalPart" "" "" ; then 183 | return 1 184 | fi 185 | fi 186 | 187 | if [ "${fractionalPart%${fractionalPart#?}}" = "-" ] ; then 188 | echo "Invalid floating point number: '-' not allowed \ 189 | after decimal point" >&2 190 | return 1 191 | fi 192 | if [ "$fractionalPart" != "" ] ; then 193 | if ! validint "$fractionalPart" "0" "" ; then 194 | return 1 195 | fi 196 | fi 197 | 198 | if [ "$decimalPart" = "-" -o -z "$decimalPart" ] ; then 199 | if [ -z "$fractionalPart" ] ; then 200 | echo "Invalid floating point format." >&2 ; return 1 201 | fi 202 | fi 203 | 204 | else 205 | if [ "$fvalue" = "-" ] ; then 206 | echo "Invalid floating point format." >&2 ; return 1 207 | fi 208 | 209 | if ! validint "$fvalue" "" "" ; then 210 | return 1 211 | fi 212 | fi 213 | 214 | return 0 215 | } 216 | 217 | exceedsDaysInMonth() 218 | { 219 | # given a month name, return 0 if the specified day value is 220 | # less than or equal to the max days in the month, 1 otherwise 221 | 222 | case $(echo $1|tr '[:upper:]' '[:lower:]') in 223 | jan* ) days=31 ;; feb* ) days=28 ;; 224 | mar* ) days=31 ;; apr* ) days=30 ;; 225 | may* ) days=31 ;; jun* ) days=30 ;; 226 | jul* ) days=31 ;; aug* ) days=31 ;; 227 | sep* ) days=30 ;; oct* ) days=31 ;; 228 | nov* ) days=30 ;; dec* ) days=31 ;; 229 | * ) echo "$0: Unknown month name $1" >&2; exit 1 230 | esac 231 | 232 | if [ $2 -lt 1 -o $2 -gt $days ] ; then 233 | return 1 234 | else 235 | return 0 # all is well 236 | fi 237 | } 238 | 239 | isLeapYear() 240 | { 241 | # this function returns 0 if a leap year, 1 otherwise 242 | # The formula for checking whether a year is a leap year is: 243 | # 1. years divisible by four are leap years, unless.. 244 | # 2. years also divisible by 100 are not leap years, except... 245 | # 3. years divisible by 400 are leap years 246 | 247 | year=$1 248 | if [ "$((year % 4))" -ne 0 ] ; then 249 | return 1 # nope, not a leap year 250 | elif [ "$((year % 400))" -eq 0 ] ; then 251 | return 0 # yes, it's a leap year 252 | elif [ "$((year % 100))" -eq 0 ] ; then 253 | return 1 254 | else 255 | return 0 256 | fi 257 | } 258 | 259 | validdate() 260 | { 261 | # expects three values, month, day and year. Returns 0 if success. 262 | 263 | newdate="$(normdate "$@")" 264 | 265 | if [ $? -eq 1 ] ; then 266 | exit 1 # error condition already reported by normdate 267 | fi 268 | 269 | month="$(echo $newdate | cut -d\ -f1)" 270 | day="$(echo $newdate | cut -d\ -f2)" 271 | year="$(echo $newdate | cut -d\ -f3)" 272 | 273 | # Now that we have a normalized date, let's check to see if the 274 | # day value is logical 275 | 276 | if ! exceedsDaysInMonth $month "$2" ; then 277 | if [ "$month" = "Feb" -a $2 -eq 29 ] ; then 278 | if ! isLeapYear $3 ; then 279 | echo "$0: $3 is not a leap year, so Feb doesn't have 29 days" >&2 280 | exit 1 281 | fi 282 | else 283 | echo "$0: bad day value: $month doesn't have $2 days" >&2 284 | exit 1 285 | fi 286 | fi 287 | return 0 288 | } 289 | 290 | echon() 291 | { 292 | echo "$*" | tr -d '\n' 293 | } 294 | 295 | initializeANSI() 296 | { 297 | esc="" 298 | 299 | blackf="${esc}[30m"; redf="${esc}[31m"; greenf="${esc}[32m" 300 | yellowf="${esc}[33m" bluef="${esc}[34m"; purplef="${esc}[35m" 301 | cyanf="${esc}[36m"; whitef="${esc}[37m" 302 | 303 | blackb="${esc}[40m"; redb="${esc}[41m"; greenb="${esc}[42m" 304 | yellowb="${esc}[43m" blueb="${esc}[44m"; purpleb="${esc}[45m" 305 | cyanb="${esc}[46m"; whiteb="${esc}[47m" 306 | 307 | boldon="${esc}[1m"; boldoff="${esc}[22m" 308 | italicson="${esc}[3m"; italicsoff="${esc}[23m" 309 | ulon="${esc}[4m"; uloff="${esc}[24m" 310 | invon="${esc}[7m"; invoff="${esc}[27m" 311 | 312 | reset="${esc}[0m" 313 | } 314 | -------------------------------------------------------------------------------- /013-guessword.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # guessword - a simple word guessing game a la hangman 3 | blank=".................." # must be longer than longest word 4 | 5 | getword() 6 | { 7 | case $(( $$ % 8 )) in 8 | 0 ) echo "pizzazz" ;; 1 ) echo "delicious" ;; 9 | 2 ) echo "gargantuan" ;; 3 ) echo "minaret" ;; 10 | 4 ) echo "paparazzi" ;; 5 ) echo "delinquent" ;; 11 | 6 ) echo "zither" ;; 7 ) echo "cuisine" ;; 12 | esac 13 | } 14 | 15 | addLetterToWord() 16 | { 17 | # This function replaces all '.' in template with guess 18 | # then updates remaining to be the number of empty slots left 19 | 20 | letter=1 21 | while [ $letter -le $letters ] ; do 22 | if [ "$(echo $word | cut -c$letter)" = "$guess" ] ; then 23 | before="$(( $letter - 1 ))"; after="$(( $letter + 1 ))" 24 | if [ $before -gt 0 ] ; then 25 | tbefore="$(echo $template | cut -c1-$before)" 26 | else 27 | tbefore="" 28 | fi 29 | if [ $after -gt $letters ] ; then 30 | template="$tbefore$guess" 31 | else 32 | template="$tbefore$guess$(echo $template | cut -c$after-$letters)" 33 | fi 34 | fi 35 | letter=$(( $letter + 1 )) 36 | done 37 | 38 | remaining=$(echo $template|sed 's/[^\.]//g'|wc -c|sed 's/[[:space:]]//g') 39 | remaining=$(( $remaining - 1 )) # fix to ignore '\n' 40 | } 41 | 42 | word=$(getword) 43 | letters=$(echo $word | wc -c | sed 's/[[:space:]]//g') 44 | letters=$(( $letters - 1 )) # fix character count to ignore \n 45 | template="$(echo $blank | cut -c1-$letters)" 46 | remaining=$letters ; guessed="" ; guesses=0; badguesses=0 47 | 48 | echo "** You're trying to guess a word with $letters letters **" 49 | 50 | while [ $remaining -gt 0 ] ; do 51 | echo -n "Word is: $template Try what letter next? " ; read guess 52 | guesses=$(( $guesses + 1 )) 53 | if echo $guessed | grep -i $guess > /dev/null ; then 54 | echo "You've already guessed that letter. Try again!" 55 | elif ! echo $word | grep -i $guess > /dev/null ; then 56 | echo "Sorry, the letter \"$guess\" is not in the word." 57 | guessed="$guessed$guess" 58 | badguesses=$(( $badguesses + 1 )) 59 | else 60 | echo "Good going! The letter $guess is in the word!" 61 | addLetterToWord $guess 62 | fi 63 | done 64 | 65 | echo -n "Congratulations! You guessed $word in $guesses guesses" 66 | echo " with $badguesses bad guesses" 67 | 68 | exit 0 69 | -------------------------------------------------------------------------------- /013-hilow.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # hilow - a simple number guessing game 3 | 4 | biggest=100 # maximum number possible 5 | guess=0 # guessed by player 6 | guesses=0 # number of guesses made 7 | number=$(( $$ % $biggest )) # random number, 1 .. $biggest 8 | 9 | while [ $guess -ne $number ] ; do 10 | echo -n "Guess? " ; read guess 11 | if [ "$guess" -lt $number ] ; then 12 | echo "... bigger!" 13 | elif [ "$guess" -gt $number ] ; then 14 | echo "... smaller!" 15 | fi 16 | guesses=$(( $guesses + 1 )) 17 | done 18 | 19 | echo "Right!! Guessed $number in $guesses guesses." 20 | 21 | exit 0 22 | -------------------------------------------------------------------------------- /014-nfmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # nfmt - A version of fmt, using nroff. Adds two useful flags: -w X for 4 | # line width and -h to enable hyphenation for better fills. 5 | 6 | while getopts "hw:" opt; do 7 | case $opt in 8 | h ) hyph=1 ;; 9 | w ) width="$OPTARG" ;; 10 | esac 11 | done 12 | shift $(($OPTIND - 1)) 13 | 14 | nroff << EOF 15 | .ll ${width:-72} 16 | .na 17 | .hy ${hyph:-0} 18 | .pl 1 19 | $(cat "$@") 20 | EOF 21 | 22 | exit 0 23 | -------------------------------------------------------------------------------- /014-ragged.txt: -------------------------------------------------------------------------------- 1 | So she sat on, with closed eyes, and half believed herrself in 2 | Wonderland, though she knew she had but to open them again, and 3 | all would change to dull reality--the grass would be only rustling in the wind, and the pool rippling to the waving of the reeds--the 4 | rattling teacups would change to tinkling sheep- bells, and the 5 | Queen's shrill cries to the voice of the shepherd boy--and the 6 | sneeze 7 | of the baby, the shriek of the Gryphon, and all thy other queer noises, would change (she knew) 8 | to the confused clamour of the busy farm-yard--while the lowing of 9 | the cattle in 10 | the distance would take the place of the Mock Turtle's heavy sobs. 11 | -------------------------------------------------------------------------------- /014-ragged.txt.shp: -------------------------------------------------------------------------------- 1 | So she sat on, with closed eyes, and half believed herrself in 2 | Wonderland, though she knew she had but to open them again, and 3 | all would change to dull reality--the grass would be only rustling in the wind, and the pool reippling to the waving of the reeds--the 4 | rattling teacups would change to tinkling sheep- bells, and the 5 | Queen's shrill cries to the voice of the shepherd boy--and the 6 | sneeze 7 | of the baby, the shriek of the Gryphon, and all thy other queer noises, would change (she knew) 8 | to the confused clamour of the busy farm-yard--while the lowing of 9 | the cattle in 10 | the distance would take the place of the Mock Turtle's heavy sobs. 11 | -------------------------------------------------------------------------------- /015-newrm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # newrm - a replacement for the existing rm command that allows a 4 | # rudimentary unremove capability through utilizing a newly created 5 | # directory in the user's home directory. It can handle directories 6 | # of content as well as individual files, and if the user specifies 7 | # the -f flag, files are NOT archived, but removed. 8 | 9 | # Big Important Warning: you'll want a cron job or similar to keep the 10 | # individual trash directories tamed, otherwise nothing will ever 11 | # actually be deleted on the system and you'll run out of disk space! 12 | 13 | mydir="$HOME/.deleted-files" 14 | realrm="/bin/rm " 15 | copy="/bin/cp -R" 16 | 17 | if [ $# -eq 0 ] ; then # let 'rm' ouptut the usage error 18 | exec $realrm # our shell dies and is replaced by /bin/rm 19 | fi 20 | 21 | # parse all options looking for '-f' 22 | 23 | flags="" 24 | 25 | while getopts "dfiPRrvW" opt 26 | do 27 | case $opt in 28 | f ) exec $realrm "$@" ;; # exec lets us exit this script directly. 29 | * ) flags="$flags -$opt" ;; # other flags are for 'rm', not us 30 | esac 31 | done 32 | shift $(( $OPTIND - 1 )) 33 | 34 | # make sure that the $mydir exists 35 | 36 | if [ ! -d $mydir ] ; then 37 | if [ ! -w $HOME ] ; then 38 | echo "$0 failed: can't create $mydir in $HOME" >&2 39 | exit 1 40 | fi 41 | mkdir $mydir 42 | chmod 700 $mydir # a little bit of privacy, please 43 | fi 44 | 45 | for arg 46 | do 47 | newname="$mydir/$(date "+%S.%M.%H.%d.%m").$(basename "$arg")" 48 | if [ -f "$arg" ] ; then 49 | $copy "$arg" "$newname" 50 | elif [ -d "$arg" ] ; then 51 | $copy "$arg" "$newname" 52 | fi 53 | done 54 | 55 | exec $realrm $flags "$@" # our shell is replaced by realrm 56 | -------------------------------------------------------------------------------- /016-unrm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # unrm - search the deleted files archive for the specified file. If 4 | # there is more than one match, show a list ordered by timestamp, and 5 | # let the user specify which they want restored. 6 | 7 | # Big Important Warning: you'll want a cron job or similar to keep the 8 | # individual trash directories tamed, otherwise nothing will ever 9 | # actually be deleted on the system and you'll run out of disk space! 10 | 11 | mydir="$HOME/.deleted-files" 12 | realrm="/bin/rm" 13 | move="/bin/mv" 14 | 15 | dest=$(pwd) 16 | 17 | if [ ! -d $mydir ] ; then 18 | echo "$0: No deleted files directory: nothing to unrm" >&2 ; exit 1 19 | fi 20 | 21 | cd $mydir 22 | 23 | if [ $# -eq 0 ] ; then # no args, just show listing 24 | echo "Contents of your deleted files archive (sorted by date):" 25 | # ls -FC | sed -e 's/[[:digit:]][[:digit:]]\.//g' -e 's/^/ /' 26 | ls -FC | sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \ 27 | -e 's/^/ /' 28 | exit 0 29 | fi 30 | 31 | # Otherwise we must have a pattern to work with. Let's see if the 32 | # user-specified pattern matches more than one file or directory 33 | # in the archive. 34 | 35 | matches="$(ls *"$1" 2> /dev/null | wc -l)" 36 | 37 | if [ $matches -eq 0 ] ; then 38 | echo "No match for \"$1\" in the deleted file archive." >&2 39 | exit 1 40 | fi 41 | 42 | if [ $matches -gt 1 ] ; then 43 | echo "More than one file or directory match in the archive:" 44 | index=1 45 | for name in $(ls -td *"$1") 46 | do 47 | datetime="$(echo $name | cut -c1-14| \ 48 | awk -F. '{ print $5"/"$4" at "$3":"$2":"$1 }')" 49 | if [ -d $name ] ; then 50 | size="$(ls $name | wc -l | sed 's/[^0-9]//g')" 51 | echo " $index) $1 (contents = ${size} items, deleted = $datetime)" 52 | else 53 | size="$(ls -sdk1 $name | awk '{print $1}')" 54 | echo " $index) $1 (size = ${size}Kb, deleted = $datetime)" 55 | fi 56 | index=$(( $index + 1)) 57 | done 58 | 59 | echo "" 60 | echo -n "Which version of $1 do you want to restore ('0' to quit)? [1] : " 61 | read desired 62 | 63 | if [ ${desired:=1} -ge $index ] ; then 64 | echo "$0: Restore cancelled by user: index value too big." >&2 65 | exit 1 66 | fi 67 | 68 | if [ $desired -lt 1 ] ; then 69 | echo "$0: restore cancelled by user." >&2 ; exit 1 70 | fi 71 | 72 | restore="$(ls -td1 *"$1" | sed -n "${desired}p")" 73 | 74 | if [ -e "$dest/$1" ] ; then 75 | echo "\"$1\" already exists in this directory. Cannot overwrite." >&2 76 | exit 1 77 | fi 78 | 79 | echo -n "Restoring file \"$1\" ..." 80 | $move "$restore" "$dest/$1" 81 | echo "done." 82 | 83 | echo -n "Delete the additional copies of this file? [y] " 84 | read answer 85 | 86 | if [ ${answer:=y} = "y" ] ; then 87 | $realrm -rf *"$1" 88 | echo "deleted." 89 | else 90 | echo "additional copies retained." 91 | fi 92 | else 93 | if [ -e "$dest/$1" ] ; then 94 | echo "\"$1\" already exists in this directory. Cannot overwrite." >&2 95 | exit 1 96 | fi 97 | 98 | restore="$(ls -d *"$1")" 99 | 100 | echo -n "Restoring file \"$1\" ... " 101 | $move "$restore" "$dest/$1" 102 | echo "done." 103 | fi 104 | 105 | exit 0 106 | -------------------------------------------------------------------------------- /017-logrm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # logrm - log all file deletion requests unless "-s" flag is used 3 | 4 | removelog="/tmp/removelog.log" 5 | 6 | if [ $# -eq 0 ] ; then 7 | echo "Usage: $0 [-s] list of files or directories" >&2 8 | exit 1 9 | fi 10 | 11 | if [ "$1" = "-s" ] ; then 12 | # silent operation requested... don't log 13 | shift 14 | else 15 | echo "$(date): ${USER}: $@" >> $removelog 16 | fi 17 | 18 | /bin/rm "$@" 19 | 20 | exit 0 21 | -------------------------------------------------------------------------------- /018-formatdir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # formatdir - output a directory listing in a friendly and useful format 4 | 5 | gmk() 6 | { 7 | # given input in Kb, output in Kb, Mb or Gb for best output format 8 | if [ $1 -ge 1000000 ] ; then 9 | echo "$(scriptbc -p 2 $1 / 1000000)Gb" 10 | elif [ $1 -ge 1000 ] ; then 11 | echo "$(scriptbc -p 2 $1 / 1000)Mb" 12 | else 13 | echo "${1}Kb" 14 | fi 15 | } 16 | 17 | if [ $# -gt 1 ] ; then 18 | echo "Usage: $0 [dirname]" >&2; exit 1 19 | elif [ $# -eq 1 ] ; then 20 | cd "$@" 21 | fi 22 | 23 | for file in * 24 | do 25 | if [ -d "$file" ] ; then 26 | size=$(ls "$file" | wc -l | sed 's/[^[:digit:]]//g') 27 | if [ $size -eq 1 ] ; then 28 | echo "$file ($size entry)|" 29 | else 30 | echo "$file ($size entries)|" 31 | fi 32 | else 33 | size="$(ls -sk "$file" | awk '{print $1}')" 34 | echo "$file ($(gmk $size))|" 35 | fi 36 | done | \ 37 | sed 's/ /^^^/g' | \ 38 | xargs -n 2 | \ 39 | sed 's/\^\^\^/ /g' | \ 40 | awk -F\| '{ printf "%-39s %-39s\n", $1, $2 }' 41 | 42 | exit 0 43 | -------------------------------------------------------------------------------- /019-locate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # locate - search the locate database for the specified pattern 4 | 5 | locatedb="/var/locate.db" 6 | 7 | exec grep -i "$@" $locatedb 8 | -------------------------------------------------------------------------------- /019-mklocatedb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # mklocatedb - build the locate database using find. Must be root to run this 4 | 5 | locatedb="/var/locate.db" 6 | 7 | if [ "$(whoami)" != "root" ] ; then 8 | echo "Must be root to run this command." >&2 9 | exit 1 10 | fi 11 | 12 | find / -print > $locatedb 13 | 14 | exit 0 15 | -------------------------------------------------------------------------------- /020-DIR.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # DIR - pretend we're the DIR command in DOS and display the contents 3 | # of the specified file, accepting some of the standard DIR flags 4 | 5 | usage() 6 | { 7 | cat << EOF >&2 8 | Usage: $0 [DOS flags] directory or directories 9 | Where: 10 | /D sort by columns 11 | /H show help for this shell script 12 | /N show long listing format with filenames on right 13 | /OD sort by oldest to newest 14 | /O-D sort by newest to oldest 15 | /P pause after each screenful of information 16 | /Q show owner of the file 17 | /S recursive listing 18 | /W use wide listing format 19 | EOF 20 | exit 0 21 | } 22 | 23 | postcmd="" 24 | flags="" 25 | 26 | while [ $# -gt 0 ] 27 | do 28 | case $1 in 29 | /D ) flags="$flags -x" ;; 30 | /H ) usage ;; 31 | /[NQW] ) flags="$flags -l" ;; 32 | /OD ) flags="$flags -rt" ;; 33 | /O-D ) flags="$flags -t" ;; 34 | /P ) postcmd="more" ;; 35 | /S ) flags="$flags -s" ;; 36 | * ) # unknown flag: probably a dir specifier 37 | break; # so let's get outta the while loop 38 | esac 39 | shift # processed flag, let's see if there's another 40 | done 41 | 42 | # done processing flags, now the command itself: 43 | 44 | if [ ! -z "$postcmd" ] ; then 45 | ls $flags "$@" | $postcmd 46 | else 47 | ls $flags "$@" 48 | fi 49 | 50 | exit 0 51 | -------------------------------------------------------------------------------- /021-findman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # findman -- given a pattern and a man section, show all the matches 4 | # for that pattern from within all relevant man pages. 5 | 6 | match1="/tmp/$0.1.$$" 7 | matches="/tmp/$0.$$" 8 | manpagelist="" 9 | 10 | trap "rm -f $match1 $matches" EXIT 11 | 12 | case $# 13 | in 14 | 3 ) section="$1" cmdpat="$2" manpagepat="$3" ;; 15 | 2 ) section="" cmdpat="$1" manpagepat="$2" ;; 16 | * ) echo "Usage: $0 [section] cmdpattern manpagepattern" >&2 17 | exit 1 18 | esac 19 | 20 | if ! man -k "$cmdpat" | grep "($section" > $match1 ; then 21 | echo "No matches to pattern \"$cmdpat\". Try something broader?"; exit 1 22 | fi 23 | 24 | cut -d\( -f1 < $match1 > $matches # command names only 25 | cat /dev/null > $match1 # clear the file... 26 | 27 | for manpage in $(cat $matches) 28 | do 29 | manpagelist="$manpagelist $manpage" 30 | man $manpage | col -b | grep -i $manpagepat | \ 31 | sed "s/^/${manpage}: /" | tee -a $match1 32 | done 33 | 34 | if [ ! -s $match1 ] ; then 35 | cat << EOF 36 | Command pattern "$cmdpat" had matches, but within those there were no 37 | matches to your man page pattern "$manpagepat" found in that set. 38 | Man pages checked:$manpagelist 39 | EOF 40 | fi 41 | 42 | exit 0 43 | -------------------------------------------------------------------------------- /022-timein.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # timein - show the current time in the specified timezone or 4 | # geographic zone. Without any argument, show UTC/GMT. Use 5 | # the word "list" to see a list of known geographic regions 6 | # Note that it's possible to match a zone directory (a region) 7 | # but that only timezone files are valid specifications. 8 | 9 | # Timezone database ref: http://www.twinsun.com/tz/tz-link.htm 10 | 11 | zonedir="/usr/share/zoneinfo" 12 | 13 | if [ ! -d $zonedir ] ; then 14 | echo "No timezone database at $zonedir." >&2 ; exit 1 15 | fi 16 | 17 | if [ -d "$zonedir/posix" ] ; then 18 | zonedir=$zonedir/posix # modern Linux systems 19 | fi 20 | 21 | if [ $# -eq 0 ] ; then 22 | timezone="UTC" 23 | mixedzone="UTC" 24 | elif [ "$1" = "list" ] ; then 25 | ( echo "All known timezones and regions defined on this system:" 26 | cd $zonedir 27 | find * -type f -print | xargs -n 2 | \ 28 | awk '{ printf " %-38s %-38s\n", $1, $2 }' 29 | ) | more 30 | exit 0 31 | else 32 | 33 | region="$(dirname $1)" 34 | zone="$(basename $1)" 35 | 36 | # Is it a direct match? If so, we're good to go. Otherwise we need 37 | # to dig around a bit to find things. Start by just counting matches 38 | 39 | matchcnt="$(find $zonedir -name $zone -type f -print | 40 | wc -l | sed 's/[^[:digit:]]//g' )" 41 | 42 | if [ "$matchcnt" -gt 0 ] ; then # at least one file matches 43 | if [ $matchcnt -gt 1 ] ; then # more than one file match 44 | echo "\"$zone\" matches more than one possible time zone record." >&2 45 | echo "Please use 'list' to see all known regions and timezones" >&2 46 | exit 1 47 | fi 48 | match="$(find $zonedir -name $zone -type f -print)" 49 | mixedzone="$zone" 50 | else 51 | # Normalize to first upper, rest of word lowercase for region + zone 52 | mixedregion="$(echo ${region%${region#?}} | tr '[[:lower:]]' '[[:upper:]]')\ 53 | $(echo ${region#?} | tr '[[:upper:]]' '[[:lower:]]')" 54 | mixedzone="$(echo ${zone%${zone#?}} | tr '[[:lower:]]' '[[:upper:]]')\ 55 | $(echo ${zone#?} | tr '[[:upper:]]' '[[:lower:]]')" 56 | 57 | if [ "$mixedregion" != "." ] ; then 58 | # only look for specified zone in specified region 59 | # to let users specify unique matches when there's more than one 60 | # possibility (e.g., "Atlantic") 61 | match="$(find $zonedir/$mixedregion -type f -name $mixedzone -print)" 62 | else 63 | match="$(find $zonedir -name $mixedzone -type f -print)" 64 | fi 65 | 66 | if [ -z "$match" ] ; then # no file matches specified pattern 67 | if [ ! -z $(find $zonedir -name $mixedzone -type d -print) ] ; then 68 | echo \ 69 | "The region \"$1\" has more than one timezone. Please use 'list'" >&2 70 | else # just not a match at all 71 | echo "Can't find an exact match for \"$1\". Please use 'list'" >&2 72 | fi 73 | echo "to see all known regions and timezones." >&2 74 | exit 1 75 | fi 76 | fi 77 | timezone="$match" 78 | fi 79 | 80 | nicetz=$(echo $timezone | sed "s|$zonedir/||g") # pretty up the output 81 | 82 | echo It\'s $(TZ=$timezone date '+%A, %B %e, %Y, at %l:%M %p') in $nicetz 83 | 84 | exit 0 85 | -------------------------------------------------------------------------------- /023-remember.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # remember - an easy command-line based memory pad 4 | # search the results with 'remindme' 5 | 6 | rememberfile="$HOME/.remember" 7 | 8 | if [ $# -eq 0 ] ; then 9 | echo "Enter note, end with ^D: " 10 | cat - >> $rememberfile 11 | else 12 | echo "$@" >> $rememberfile 13 | fi 14 | 15 | exit 0 16 | -------------------------------------------------------------------------------- /023-remindme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # remindme - search a datafile for matching lines, or show the contents 4 | # of the datafile if no arg is specified 5 | 6 | rememberfile="$HOME/.remember" 7 | 8 | if [ $# -eq 0 ] ; then 9 | more $rememberfile 10 | else 11 | grep -i "$@" $rememberfile | ${PAGER:-more} 12 | fi 13 | 14 | exit 0 15 | -------------------------------------------------------------------------------- /024-calc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # calc - a command-line calculator that acts as a front-end to bc 4 | 5 | scale=2 6 | 7 | show_help() 8 | { 9 | cat << EOF 10 | In addition to standard math functions, calc also supports 11 | 12 | a % b remainder of a/b 13 | a ^ b exponential: a raised to the b power 14 | s(x) sine of x, x in radians 15 | c(x) cosine of x, x in radians 16 | a(x) arctangent of x, returns radians 17 | l(x) natural log of x 18 | e(x) exponential log of raising e to the x 19 | j(n,x) bessel function of integer order n of x 20 | scale N show N fractional digits (default = 2) 21 | EOF 22 | } 23 | 24 | if [ $# -gt 0 ] ; then 25 | exec scriptbc "$@" 26 | fi 27 | 28 | echo "Calc - a simple calculator. Use 'help' for help, 'quit' to quit." 29 | 30 | echo -n "calc> " 31 | 32 | while read command args 33 | do 34 | case $command 35 | in 36 | quit|exit) exit 0 ;; 37 | help|\?) show_help ;; 38 | scale) scale=$args ;; 39 | *) scriptbc -p $scale "$command" "$args" ;; 40 | esac 41 | 42 | echo -n "calc> " 43 | done 44 | 45 | echo "" 46 | 47 | exit 0 48 | -------------------------------------------------------------------------------- /025-checkspelling.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # checkspelling - check the spelling of a word 4 | 5 | spell="ispell -l" # if you have ispell installed instead 6 | # if not, just define spell=spell or 7 | # equivalent. 8 | 9 | if [ $# -lt 1 ] ; then 10 | echo "Usage: $0 word or words" >&2 11 | exit 1 12 | fi 13 | 14 | for word 15 | do 16 | if [ -z $(echo $word | $spell) ] ; then 17 | echo "$word: spelled correctly." 18 | else 19 | echo "$word: misspelled." 20 | fi 21 | done 22 | 23 | exit 0 24 | -------------------------------------------------------------------------------- /026-shpell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shpell - An interactive spell checking program that lets you step 4 | # through all known spelling errors in a document, indicate which 5 | # ones you'd like to fix (and the correction), then applies them 6 | # all to the file. The original version of the file is saved with a 7 | # .shp suffix and the new version replaces the old. 8 | # 9 | # Note that you need a standard 'spell' command for this to work, which 10 | # might involve you installing aspell, ispell, or pspell on your system. 11 | 12 | tempfile="/tmp/$0.$$" 13 | changerequests="/tmp/$0.$$.sed" 14 | spell="ispell -l" # modify as neede for your own spell 15 | 16 | trap "rm -f $tempfile $changerequests" EXIT HUP INT QUIT TERM 17 | 18 | # include the ansi color sequence definitions 19 | 20 | . 012-library.sh 21 | initializeANSI 22 | 23 | getfix() 24 | { 25 | # asks for a correction. Keeps track of nesting, 26 | # and only level 1 can output "replacing" message. 27 | 28 | word=$1 29 | filename=$2 30 | misspelled=1 31 | 32 | while [ $misspelled -eq 1 ] 33 | do 34 | echo ""; echo "${boldon}Misspelled word ${word}:${boldoff}" 35 | grep -n $word $filename | 36 | sed -e 's/^/ /' -e "s/$word/$boldon$word$boldoff/g" 37 | echo -n "i)gnore, q)uit, or type replacement: " 38 | read fix 39 | if [ "$fix" = "q" -o "$fix" = "quit" ] ; then 40 | echo "Exiting without applying any fixes."; exit 0 41 | elif [ "${fix%${fix#?}}" = "!" ] ; then 42 | misspelled=0 # once we see spaces, we stop checking 43 | echo "s/$word/${fix#?}/g" >> $changerequests 44 | elif [ "$fix" = "i" -o -z "$fix" ] ; then 45 | misspelled=0 46 | else 47 | if [ ! -z "$(echo $fix | sed 's/[^ ]//g')" ] ; then 48 | misspelled=0 # once we see spaces, we stop checking 49 | echo "s/$word/$fix/g" >> $changerequests 50 | else 51 | # it's a single word replacement, let's spell check that too 52 | if [ ! -z "$(echo $fix | $spell)" ] ; then 53 | echo "" 54 | echo "*** Your suggested replacement $fix is misspelled." 55 | echo "*** Prefix the word with '!' to force acceptance." 56 | else 57 | misspelled=0 # suggested replacement word is acceptable 58 | echo "s/$word/$fix/g" >> $changerequests 59 | fi 60 | fi 61 | fi 62 | done 63 | } 64 | 65 | ### beginning of actual script body 66 | 67 | if [ $# -lt 1 ] ; then 68 | echo "Usage: $0 filename" >&2 ; exit 1 69 | fi 70 | 71 | if [ ! -r $1 ] ; then 72 | echo "$0: Cannot read file $1 to check spelling" >&2 ; exit 1 73 | fi 74 | 75 | # note that the following invocation fills $tempfile along the way 76 | 77 | errors="$($spell < $1 | tee $tempfile | wc -l | sed 's/[^[:digit:]]//g')" 78 | 79 | if [ $errors -eq 0 ] ; then 80 | echo "There are no spelling errors in $1."; exit 0 81 | fi 82 | 83 | echo "We need to fix $errors misspellings in the document. Remember that the" 84 | echo "default answer to the spelling prompt is 'ignore', if you're lazy." 85 | 86 | touch $changerequests 87 | 88 | for word in $(cat $tempfile) 89 | do 90 | getfix $word $1 1 91 | done 92 | 93 | if [ $(wc -l < $changerequests) -gt 0 ] ; then 94 | sed -f $changerequests $1 > $1.new 95 | mv $1 $1.shp 96 | mv $1.new $1 97 | echo Done. Made $(wc -l < $changerequests) changes. 98 | fi 99 | 100 | exit 0 101 | -------------------------------------------------------------------------------- /027-spelldict.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # spelldict - use the 'aspell' feature and some filtering to allow easy 4 | # command-line spell checking of a given input (file) 5 | 6 | okaywords="$HOME/.okaywords" 7 | tempout="/tmp/spell.tmp.$$" 8 | spell="virtual aspell" # tweak as needed 9 | 10 | trap "/bin/rm -f $tempout" EXIT 11 | 12 | if [ -z "$1" ] ; then 13 | echo "Usage: spell file|URL" >&2; exit 1 14 | elif [ ! -f $okaywords ] ; then 15 | echo "No personal dictionary found. Create one and rerun this command" >&2 16 | echo "Your dictionary file: $okaywords" >&2 17 | exit 1 18 | fi 19 | 20 | for filename 21 | do 22 | $spell -a < $filename | \ 23 | grep -v '@(#)' | sed "s/\'//g" | \ 24 | awk '{ if (length($0) > 15 && length($2) > 2) print $2 }' | \ 25 | grep -vif $okaywords | \ 26 | grep '[[:lower:]]' | grep -v '[[:digit:]]' | sort -u | \ 27 | sed 's/^/ /' > $tempout 28 | 29 | if [ -s $tempout ] ; then 30 | sed "s/^/${filename}: /" $tempout 31 | fi 32 | done 33 | 34 | exit 0 35 | -------------------------------------------------------------------------------- /028-convertatemp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # CONVERTATEMP - Temperature conversion script that lets the user enter 4 | # a temperature in any of Fahrenheit, Celsius or Kelvin and receive the 5 | # equivalent temperature in the other two units as the output. 6 | 7 | if uname | grep 'SunOS'>/dev/null ; then 8 | echo "Yep, SunOS, let\'s fix this baby" 9 | PATH="/usr/xpg4/bin:$PATH" 10 | fi 11 | 12 | if [ $# -eq 0 ] ; then 13 | cat << EOF >&2 14 | Usage: $0 temperature[F|C|K] 15 | where the suffix: 16 | F indicates input is in Fahrenheit (default) 17 | C indicates input is in Celsius 18 | K indicates input is in Kelvin 19 | EOF 20 | exit 1 21 | fi 22 | 23 | unit="$(echo $1|sed -e 's/[-[[:digit:]]*//g' | tr '[:lower:]' '[:upper:]' )" 24 | temp="$(echo $1|sed -e 's/[^-[[:digit:]]*//g')" 25 | 26 | case ${unit:=F} 27 | in 28 | F ) # Fahrenheit to Celsius formula: Tc = (F -32 ) / 1.8 29 | farn="$temp" 30 | cels="$(echo "scale=2;($farn - 32) / 1.8" | bc)" 31 | kelv="$(echo "scale=2;$cels + 273.15" | bc)" 32 | ;; 33 | 34 | C ) # Celsius to Fahrenheit formula: Tf = (9/5)*Tc+32 35 | cels=$temp 36 | kelv="$(echo "scale=2;$cels + 273.15" | bc)" 37 | farn="$(echo "scale=2;((9/5) * $cels) + 32" | bc)" 38 | ;; 39 | 40 | K ) # Celsius = Kelvin + 273.15, then use Cels -> Fahr formula 41 | kelv=$temp 42 | cels="$(echo "scale=2; $kelv - 273.15" | bc)" 43 | farn="$(echo "scale=2; ((9/5) * $cels) + 32" | bc)" 44 | esac 45 | 46 | echo "Fahrenheit = $farn" 47 | echo "Celsius = $cels" 48 | echo "Kelvin = $kelv" 49 | 50 | exit 0 51 | -------------------------------------------------------------------------------- /029-loancalc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # mortgage - given a principal loan amount, interest rate, and 4 | # duration of loan (years), calculate the per-payment amount. 5 | 6 | # formula is: M = P * ( J / (1 - (1 + J) ** -N)) 7 | # where P = principal, J = monthly interest rate, N = duration (months) 8 | # 9 | # users typically enter P, I (annual interest rate) and L (length, years) 10 | 11 | . 012-library.sh 12 | 13 | if [ $# -ne 3 ] ; then 14 | echo "Usage: $0 principal interest loan-duration-years" >&2 15 | exit 1 16 | fi 17 | 18 | P=$1 I=$2 L=$3 19 | J="$(scriptbc -p 8 $I / \( 12 \* 100 \) )" 20 | N="$(( $L * 12 ))" 21 | M="$(scriptbc -p 8 $P \* \( $J / \(1 - \(1 + $J\) \^ -$N\) \) )" 22 | 23 | # now a little prettying up of the value: 24 | 25 | dollars="$(echo $M | cut -d. -f1)" 26 | cents="$(echo $M | cut -d. -f2 | cut -c1-2)" 27 | 28 | cat << EOF 29 | A $L year loan at $I% interest with a principal amount of $(nicenumber $P 1 ) 30 | results in a payment of \$$dollars.$cents each month for the duration of 31 | the loan ($N payments). 32 | EOF 33 | 34 | exit 0 35 | -------------------------------------------------------------------------------- /030-addagenda.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # addagenda - prompt the user to add a new event for the Agenda script 4 | 5 | agendafile="$HOME/.agenda" 6 | 7 | isDayName() 8 | { 9 | # return = 0 if all is well, 1 on error 10 | 11 | case $(echo $1 | tr '[[:upper:]]' '[[:lower:]]') in 12 | sun*|mon*|tue*|wed*|thu*|fri*|sat*) retval=0 ;; 13 | * ) retval=1 ;; 14 | esac 15 | return $retval 16 | } 17 | 18 | isMonthName() 19 | { 20 | case $(echo $1 | tr '[[:upper:]]' '[[:lower:]]') in 21 | jan*|feb*|mar*|apr*|may*|jun*) return 0 ;; 22 | jul*|aug*|sep*|oct*|nov*|dec*) return 0 ;; 23 | * ) return 1 ;; 24 | esac 25 | } 26 | 27 | normalize() 28 | { 29 | # return string with first char uppercase, next two lowercase 30 | echo -n $1 | cut -c1 | tr '[[:lower:]]' '[[:upper:]]' 31 | echo $1 | cut -c2-3| tr '[[:upper:]]' '[[:lower:]]' 32 | } 33 | 34 | if [ ! -w $HOME ] ; then 35 | echo "$0: cannot write in your home directory ($HOME)" >&2 36 | exit 1 37 | fi 38 | 39 | echo "Agenda: The Unix Reminder Service" 40 | echo -n "Date of event (day mon, day month year, or dayname): " 41 | read word1 word2 word3 junk 42 | 43 | if isDayName $word1 ; then 44 | if [ ! -z "$word2" ] ; then 45 | echo "Bad dayname format: just specify the day name by itself." >&2 46 | exit 1 47 | fi 48 | date="$(normalize $word1)" 49 | 50 | else 51 | 52 | if [ -z "$word2" ] ; then 53 | echo "Bad dayname format: unknown day name specified" >&2 54 | exit 1 55 | fi 56 | 57 | if [ ! -z "$(echo $word1|sed 's/[[:digit:]]//g')" ] ; then 58 | echo "Bad date format: please specify day first, by day number" >&2 59 | exit 1 60 | fi 61 | 62 | if [ "$word1" -lt 1 -o "$word1" -gt 31 ] ; then 63 | echo "Bad date format: day number can only be in range 1-31" >&2 64 | exit 1 65 | fi 66 | 67 | if ! isMonthName $word2 ; then 68 | echo "Bad date format: unknown month name specified." >&2 69 | exit 1 70 | fi 71 | 72 | word2="$(normalize $word2)" 73 | 74 | if [ -z "$word3" ] ; then 75 | date="$word1$word2" 76 | else 77 | if [ ! -z "$(echo $word3|sed 's/[[:digit:]]//g')" ] ; then 78 | echo "Bad date format: third field should be year." >&2 79 | exit 1 80 | elif [ $word3 -lt 2000 -o $word3 -gt 2500 ] ; then 81 | echo "Bad date format: year value should be 2000-2500" >&2 82 | exit 1 83 | fi 84 | date="$word1$word2$word3" 85 | fi 86 | fi 87 | 88 | echo -n "One line description: " 89 | read description 90 | 91 | # ready to write to datafile 92 | 93 | echo "$(echo $date|sed 's/ //g')|$description" >> $agendafile 94 | 95 | exit 0 96 | -------------------------------------------------------------------------------- /030-agenda.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # agenda - scan through the user's .agenda file to see if there 4 | # are any matches for the current or next day 5 | 6 | agendafile="$HOME/.agenda" 7 | 8 | checkDate() 9 | { 10 | # create the possible default values that'll match today 11 | weekday=$1 day=$2 month=$3 year=$4 12 | format1="$weekday" format2="$day$month" format3="$day$month$year" 13 | 14 | # and step through the file comparing dates... 15 | 16 | IFS="|" # The reads will naturally split at the IFS 17 | 18 | echo "On the Agenda for today:" 19 | 20 | while read date description ; do 21 | if [ "$date" = "$format1" -o "$date" = "$format2" -o "$date" = "$format3" ] 22 | then 23 | echo " $description" 24 | fi 25 | done < $agendafile 26 | } 27 | 28 | if [ ! -e $agendafile ] ; then 29 | echo "$0: You don't seem to have an .agenda file. " >&2 30 | echo "To remedy this, please use 'addagenda' to add events" >&2 31 | exit 1 32 | fi 33 | 34 | # now let's get today's date... 35 | 36 | eval $(date "+weekday=\"%a\" month=\"%b\" day=\"%e\" year=\"%G\"") 37 | 38 | day="$(echo $day|sed 's/ //g')" # remove possible leading space 39 | 40 | checkDate $weekday $day $month $year 41 | 42 | exit 0 43 | -------------------------------------------------------------------------------- /031-numberlines.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # numberlines - a simple alternative to cat -n, etc 4 | 5 | for filename 6 | do 7 | linecount="1" 8 | (while read line 9 | do 10 | echo "${linecount}: $line" 11 | linecount="$(( $linecount + 1 ))" 12 | done) < $filename 13 | done 14 | 15 | exit 0 16 | -------------------------------------------------------------------------------- /032-showfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # showfile - show the contents of a file, including additional useful info 3 | 4 | width=72 5 | 6 | for input 7 | do 8 | lines="$(wc -l < $input | sed 's/ //g')" 9 | chars="$(wc -c < $input | sed 's/ //g')" 10 | owner="$(ls -ld $input | awk '{print $3}')" 11 | echo "-----------------------------------------------------------------" 12 | echo "File $input ($lines lines, $chars characters, owned by $owner):" 13 | echo "-----------------------------------------------------------------" 14 | while read line 15 | do 16 | if [ ${#line} -gt $width ] ; then 17 | echo "$line" | fmt | sed -e '1s/^/ /' -e '2,$s/^/+ /' 18 | else 19 | echo " $line" 20 | fi 21 | done < $input 22 | 23 | echo "-----------------------------------------------------------------" 24 | 25 | done | more 26 | 27 | exit 0 28 | -------------------------------------------------------------------------------- /033-toolong.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # toolong - only feed the fmt command those lines in the input stream 3 | # that are longer than the specified length 4 | 5 | width=72 6 | 7 | if [ ! -r "$1" ] ; then 8 | echo "Usage: $0 filename" >&2; exit 1 9 | fi 10 | 11 | while read input 12 | do 13 | if [ ${#input} -gt $width ] ; then 14 | echo "$input" | fmt 15 | else 16 | echo "$input" 17 | fi 18 | done < $1 19 | 20 | exit 0 21 | 22 | -------------------------------------------------------------------------------- /034-quota.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # newquota - a front-end to quota that works with fullword flags a la GNU 3 | 4 | # quota has three possible flags: -g, -v and -q and in this script 5 | # we allow them to be '--group' '--verbose' and '--quiet' too: 6 | 7 | flags="" 8 | realquota="/usr/bin/quota" 9 | 10 | while [ $# -gt 0 ] 11 | do 12 | echo checking flag $1 13 | case $1 14 | in 15 | --help ) echo "Usage: $0 [--group --verbose --quiet -gvq]" >&2 16 | exit 1 ;; 17 | --group | -group) flags="$flags -g"; shift ;; 18 | --verbose | -verbose) flags="$flags -v"; shift ;; 19 | --quiet | -quiet) flags="$flags -q"; shift ;; 20 | -- ) shift; break ;; 21 | * ) break; # done with 'while' loop! 22 | esac 23 | done 24 | 25 | exec $realquota $flags "$@" 26 | -------------------------------------------------------------------------------- /035-mysftp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # mysftp - make sftp start up more like ftp 4 | 5 | echo -n "User account: " 6 | read account 7 | 8 | if [ -z "$account" ] ; then 9 | exit 0; # changed their mind, presumably 10 | fi 11 | 12 | if [ -z "$1" ] ; then 13 | echo -n "Remote host: " 14 | read host 15 | if [ -z $host ] ; then 16 | exit 0 17 | fi 18 | else 19 | host=$1 20 | fi 21 | 22 | # echo sftp -C $account@$host 23 | 24 | exec /usr/bin/sftp -C $account@$host 25 | -------------------------------------------------------------------------------- /036-cgrep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # cgrep - grep with context display and highlighted pattern matches 4 | 5 | context=0 6 | esc="" 7 | bOn="${esc}[1m" bOff="${esc}[22m" 8 | sedscript="/tmp/cgrep.sed.$$" 9 | tempout="/tmp/cgrep.$$" 10 | 11 | showMatches() 12 | { 13 | matches=0 14 | 15 | echo "s/$pattern/${bOn}$pattern${bOff}/g" > $sedscript 16 | 17 | for lineno in $(grep -n "$pattern" $1 | cut -d: -f1) 18 | do 19 | if [ $context -gt 0 ] ; then 20 | prev="$(( $lineno - $context ))" 21 | if [ "$(echo $prev | cut -c1)" = "-" ] ; then 22 | prev="0" 23 | fi 24 | next="$(( $lineno + $context ))" 25 | 26 | if [ $matches -gt 0 ] ; then 27 | echo "${prev}i\\" >> $sedscript 28 | echo "----" >> $sedscript 29 | fi 30 | echo "${prev},${next}p" >> $sedscript 31 | else 32 | echo "${lineno}p" >> $sedscript 33 | fi 34 | matches="$(( $matches + 1 ))" 35 | done 36 | 37 | if [ $matches -gt 0 ] ; then 38 | sed -n -f $sedscript $1 | uniq | more 39 | fi 40 | } 41 | 42 | trap "/bin/rm -f $tempout $sedscript" EXIT 43 | 44 | if [ -z "$1" ] ; then 45 | echo "Usage: $0 [-c X] pattern {filename}" >&2; exit 0 46 | fi 47 | 48 | if [ "$1" = "-c" ] ; then 49 | context="$2" 50 | shift; shift 51 | elif [ "$(echo $1|cut -c1-2)" = "-c" ] ; then 52 | context="$(echo $1 | cut -c3-)" 53 | shift 54 | fi 55 | 56 | pattern="$1"; shift 57 | 58 | if [ $# -gt 0 ] ; then 59 | for filename ; do 60 | echo "----- $filename -----" 61 | showMatches $filename 62 | done 63 | else 64 | cat - > $tempout # save stream to a temp file 65 | showMatches $tempout 66 | fi 67 | 68 | exit 0 69 | -------------------------------------------------------------------------------- /037-zcat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # zcat, zmore, and zgrep - this script should be either symbolically 4 | # linked or hard linked to all three names - it allows users to work 5 | # with compressed files transparently. 6 | 7 | Z="compress"; unZ="uncompress" ; Zlist="" 8 | gz="gzip" ; ungz="gunzip" ; gzlist="" 9 | bz="bzip2" ; unbz="bunzip2" ; bzlist="" 10 | 11 | # First step is to try and isolate the filenames in the command line 12 | # we'll do this lazily by stepping through each argument testing to 13 | # see if it's a filename or not. If it is, and it has a compression 14 | # suffix, we'll uncompress the file, rewrite the filename, and proceed. 15 | # When done, we'll recompress everything that was uncompressed. 16 | 17 | for arg 18 | do 19 | if [ -f "$arg" ] ; then 20 | case $arg in 21 | *.Z) $unZ "$arg" 22 | arg="$(echo $arg | sed 's/\.Z$//')" 23 | Zlist="$Zlist \"$arg\"" 24 | ;; 25 | 26 | *.gz) $ungz "$arg" 27 | arg="$(echo $arg | sed 's/\.gz$//')" 28 | gzlist="$gzlist \"$arg\"" 29 | ;; 30 | 31 | *.bz2) $unbz "$arg" 32 | arg="$(echo $arg | sed 's/\.bz2$//')" 33 | bzlist="$bzlist \"$arg\"" 34 | ;; 35 | 36 | esac 37 | fi 38 | newargs="${newargs:-""} \"$arg\"" 39 | done 40 | 41 | case $0 in 42 | *zcat* ) eval cat $newargs ;; 43 | *zmore* ) eval more $newargs ;; 44 | *zgrep* ) eval grep $newargs ;; 45 | * ) echo "$0: unknown base name. Can't proceed." >&2; exit 1 46 | esac 47 | 48 | # now recompress everything 49 | 50 | if [ ! -z "$Zlist" ] ; then 51 | eval $Z $Zlist 52 | fi 53 | if [ ! -z "$gzlist" ] ; then 54 | eval $gz $gzlist 55 | fi 56 | if [ ! -z "$bzlist" ] ; then 57 | eval $bz $bzlist 58 | fi 59 | 60 | # and done 61 | 62 | exit 0 63 | -------------------------------------------------------------------------------- /038-bestcompress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # bestcompress - given a file, try compressing it with all the available 4 | # compression tools and keep the compressed file that's smallest, reporting 5 | # the result to the user. If '-a' isn't specified, it skips compressed 6 | # files in the input stream. 7 | 8 | Z="compress" 9 | gz="gzip" 10 | bz="bzip2" 11 | Zout="/tmp/bestcompress.$$.Z" 12 | gzout="/tmp/bestcompress.$$.gz" 13 | bzout="/tmp/bestcompress.$$.bz" 14 | skipcompressed=1 15 | 16 | if [ "$1" = "-a" ] ; then 17 | skipcompressed=0 ; shift 18 | fi 19 | 20 | if [ $# -eq 0 ]; then 21 | echo "Usage: $0 [-a] file or files to optimally compress" >&2; exit 1 22 | fi 23 | 24 | trap "/bin/rm -f $Zout $gzout $bzout" EXIT 25 | 26 | for name 27 | do 28 | if [ ! -f "$name" ] ; then 29 | echo "$0: file $name not found. Skipped." >&2 30 | continue 31 | fi 32 | 33 | if [ "$(echo $name | egrep '(\.Z$|\.gz$|\.bz2$)')" != "" ] ; then 34 | if [ $skipcompressed -eq 1 ] ; then 35 | echo "Skipped file ${name}: it's already compressed." 36 | continue 37 | else 38 | echo "Warning: Trying to double-compress $name" 39 | fi 40 | fi 41 | 42 | $Z < "$name" > $Zout & 43 | $gz < "$name" > $gzout & 44 | $bz < "$name" > $bzout & 45 | 46 | wait # run compressions in parallel for speed. Wait until all are done 47 | 48 | smallest="$(ls -l "$name" $Zout $gzout $bzout | \ 49 | awk '{print $5"="NR}' | sort -n | cut -d= -f2 | head -1)" 50 | 51 | case "$smallest" in 52 | 1 ) echo "No space savings by compressing $name. Left as-is." 53 | ;; 54 | 2 ) echo Best compression is with compress. File renamed ${name}.Z 55 | mv $Zout "${name}.Z" ; rm -f "$name" 56 | ;; 57 | 3 ) echo Best compression is with gzip. File renamed ${name}.gz 58 | mv $gzout "${name}.gz" ; rm -f "$name" 59 | ;; 60 | 4 ) echo Best compression is with bzip2. File renamed ${name}.bz2 61 | mv $bzout "${name}.bz2" ; rm -f "$name" 62 | esac 63 | 64 | done 65 | 66 | exit 0 67 | -------------------------------------------------------------------------------- /039-fquota.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # FQUOTA - Disk quota analysis tool for Unix. 4 | # Assumes that all user accounts are >= UID 100. 5 | 6 | MAXDISKUSAGE=20 7 | 8 | for name in $(cut -d: -f1,3 /etc/passwd | awk -F: '$2 > 99 { print $1 }') 9 | do 10 | echo -n "User $name exceeds disk quota. Disk usage is: " 11 | 12 | find / /usr /var /Users -user $name -xdev -type f -ls | \ 13 | awk '{ sum += $7 } END { print sum / (1024*1024) " Mbytes" }' 14 | 15 | done | awk "\$9 > $MAXDISKUSAGE { print \$0 }" 16 | 17 | exit 0 18 | 19 | -------------------------------------------------------------------------------- /040-diskhogs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # DISKHOGS - Disk quota analysis tool for Unix, assumes all user 4 | # accounts are >= UID 100. Emails message to each violating user 5 | # and reports a summary to the screen 6 | 7 | MAXDISKUSAGE=20 8 | violators="/tmp/diskhogs0.$$" 9 | 10 | trap "/bin/rm -f $violators" 0 11 | 12 | for name in $(cut -d: -f1,3 /etc/passwd | awk -F: '$2 > 99 { print $1 }') 13 | do 14 | echo -n "$name " 15 | 16 | find / /usr /var /Users -user $name -xdev -type f -ls | \ 17 | awk '{ sum += $7 } END { print sum / (1024*1024) }' 18 | 19 | done | awk "\$2 > $MAXDISKUSAGE { print \$0 }" > $violators 20 | 21 | if [ ! -s $violators ] ; then 22 | echo "No users exceed the disk quota of ${MAXDISKUSAGE}MB" 23 | cat $violators 24 | exit 0 25 | fi 26 | 27 | while read account usage ; do 28 | 29 | cat << EOF | fmt | mail -s "Warning: $account Exceeds Quota" $account 30 | Your disk usage is ${usage}MB but you have only been allocated 31 | ${MAXDISKUSAGE}MB. This means that either you need to delete some of 32 | your files, compress your files (see 'gzip' or 'bzip2' for powerful and 33 | easy-to-use compression programs), or talk with us about increasing 34 | your disk allocation. 35 | 36 | Thanks for your cooperation on this matter. 37 | 38 | Dave Taylor @ x554 39 | EOF 40 | 41 | echo "Account $account has $usage MB of disk space. User notified." 42 | 43 | done < $violators 44 | 45 | exit 0 46 | -------------------------------------------------------------------------------- /041-diskspace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # diskspace - summarize available disk space and present in a logical 4 | # and readable fashion 5 | 6 | tempfile="/tmp/available.$$" 7 | 8 | trap "rm -f $tempfile" EXIT 9 | 10 | cat << 'EOF' > $tempfile 11 | { sum += $4 } 12 | END { mb = sum / 1024 13 | gb = mb / 1024 14 | printf "%.0f MB (%.2fGB) of available disk space\n", mb, gb 15 | } 16 | EOF 17 | 18 | df -k | awk -f $tempfile 19 | 20 | exit 0 21 | -------------------------------------------------------------------------------- /042-newdf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # newdf - a friendlier version of df 4 | 5 | sedscript="/tmp/newdf.$$" 6 | 7 | trap "rm -f $sedscript" EXIT 8 | 9 | cat << 'EOF' > $sedscript 10 | function showunit(size) 11 | { mb = size / 1024; prettymb=(int(mb * 100)) / 100; 12 | gb = mb / 1024; prettygb=(int(gb * 100)) / 100; 13 | 14 | if ( substr(size,1,1) !~ "[0-9]" || 15 | substr(size,2,1) !~ "[0-9]" ) { return size } 16 | else if ( mb < 1) { return size "K" } 17 | else if ( gb < 1) { return prettymb "M" } 18 | else { return prettygb "G" } 19 | } 20 | 21 | BEGIN { 22 | printf "%-27s %7s %7s %7s %8s %-s\n", 23 | "Filesystem", "Size", "Used", "Avail", "Capacity", "Mounted" 24 | } 25 | 26 | !/Filesystem/ { 27 | 28 | size=showunit($2); 29 | used=showunit($3); 30 | avail=showunit($4); 31 | 32 | printf "%-27s %7s %7s %7s %8s %-s\n", 33 | $1, size, used, avail, $5, $6 34 | } 35 | EOF 36 | 37 | df -k | awk -f $sedscript 38 | 39 | exit 0 40 | -------------------------------------------------------------------------------- /043-mkslocate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # mkslocatedb - build the central, public locate database as user nobody, 4 | # and simultaneously step through each home directory to find those 5 | # that contain a .slocatedb file. If found, an additional, private 6 | # version of the locate database will be created for that user. 7 | 8 | locatedb="/var/locate.db" 9 | slocatedb=".slocatedb" 10 | 11 | if [ "$(whoami)" != "root" ] ; then 12 | echo "$0: Error: You must be root to run this command." >&2 13 | exit 1 14 | fi 15 | 16 | if [ "$(grep '^nobody:' /etc/passwd)" = "" ] ; then 17 | echo "$0: Error: you must have an account for user 'nobody'" >&2 18 | echo "to create the default slocate database." >&2; exit 1 19 | fi 20 | 21 | cd / # sidestep post-su pwd permission problems 22 | 23 | # first, create or update the public database 24 | su -fm nobody -c "find / -print" > $locatedb 2>/dev/null 25 | echo "building default slocate database (user = nobody)" 26 | echo ... result is $(wc -l < $locatedb) lines long. 27 | 28 | 29 | # now step through the user accounts on the system to see who has 30 | # a $slocatedb file in their home directory.... 31 | 32 | for account in $(cut -d: -f1 /etc/passwd) 33 | do 34 | homedir="$(grep "^${account}:" /etc/passwd | cut -d: -f6)" 35 | 36 | if [ "$homedir" = "/" ] ; then 37 | continue # refuse to build one for root dir 38 | elif [ -e $homedir/$slocatedb ] ; then 39 | echo "building slocate database for user $account" 40 | su -fm $account -c "find / -print" > $homedir/$slocatedb \ 41 | 2>/dev/null 42 | chmod 600 $homedir/$slocatedb 43 | chown $account $homedir/$slocatedb 44 | echo ... result is $(wc -l < $homedir/$slocatedb) lines long. 45 | fi 46 | done 47 | 48 | exit 0 49 | -------------------------------------------------------------------------------- /043-slocate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # slocate - Try to search the user's secure locate database for the 4 | # specified pattern. If none exists, output a warning and create 5 | # one. If secure locate db is empty, use system one instead. 6 | 7 | locatedb="/var/locate.db" 8 | slocatedb="$HOME/.slocatedb" 9 | 10 | if [ "$1" = "--explain" ] ; then 11 | cat << "EOF" >&2 12 | Warning: Secure locate keeps a private database for each user, and your 13 | database hasn't yet been created. Until it is (probably late tonight) 14 | I'll just use the public locate database, which will show you all 15 | publicly accessible matches, rather than those explicitly available to 16 | account ${USER:-$LOGNAME}. 17 | EOF 18 | if [ "$1" = "--explain" ] ; then 19 | exit 0 20 | fi 21 | 22 | # before we go, create a .slocatedb so that cron will fill it 23 | # the next time the mkslocatedb script is run 24 | 25 | touch $slocatedb # mkslocatedb will build it next time through 26 | chmod 600 $slocatedb # start on the right foot with permissions 27 | 28 | elif [ -s $slocatedb ] ; then 29 | locatedb=$slocatedb 30 | else 31 | echo "Warning: using public database. Use \"$0 --explain\" for details." >&2 32 | fi 33 | 34 | if [ -z "$1" ] ; then 35 | echo "Usage: $0 pattern" >&2; exit 1 36 | fi 37 | 38 | exec grep -i "$1" $locatedb 39 | -------------------------------------------------------------------------------- /044-adduser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ADDUSER - add a new user to the system, including building their 4 | # home directory, copying in default config data, etc. 5 | # For a standard Unix/Linux system, not Mac OS X 6 | 7 | pwfile="/etc/passwd" shadowfile="/etc/shadow" 8 | gfile="/etc/group" 9 | hdir="/home" 10 | 11 | if [ "$(whoami)" != "root" ] ; then 12 | echo "Error: You must be root to run this command." >&2 13 | exit 1 14 | fi 15 | 16 | echo "Add new user account to $(hostname)" 17 | echo -n "login: " ; read login 18 | 19 | # adjust '5000' to match the top end of your user account namespace 20 | # because some system accounts have uid's like 65535 and similar. 21 | 22 | uid="$(awk -F: '{ if (big < $3 && $3 < 5000) big=$3 } END { print big + 1 }' $pwfile)" 23 | homedir=$hdir/$login 24 | 25 | # we are giving each user their own group, so gid=uid 26 | gid=$uid 27 | 28 | echo -n "full name: " ; read fullname 29 | echo -n "shell: " ; read shell 30 | 31 | echo "Setting up account $login for $fullname..." 32 | 33 | echo ${login}:x:${uid}:${gid}:${fullname}:${homedir}:$shell >> $pwfile 34 | echo ${login}:*:11647:0:99999:7::: >> $shadowfile 35 | 36 | echo "${login}:x:${gid}:$login" >> $gfile 37 | 38 | mkdir $homedir 39 | cp -R /etc/skel/.[a-zA-Z]* $homedir 40 | chmod 755 $homedir 41 | find $homedir -print | xargs chown ${login}:$login 42 | 43 | # setting an initial password 44 | passwd $login 45 | 46 | exit 0 47 | -------------------------------------------------------------------------------- /045-suspenduser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## Suspend - suspend a user account for the indefinite future 4 | 5 | homedir="/home" # home directory for users 6 | secs=10 # seconds before user is logged out 7 | 8 | if [ -z $1 ] ; then 9 | echo "Usage: $0 account" >&2 ; exit 1 10 | elif [ "$(whoami)" != "root" ] ; then 11 | echo "Error. You must be 'root' to run this command." >&2; exit 1 12 | fi 13 | 14 | echo "Please change account $1 password to something new." 15 | passwd $1 16 | 17 | # Now, let's see if they're logged in, and if so, boot 'em 18 | 19 | if [ ! -z $(who | grep $1) ] ; then 20 | 21 | tty="$(who | grep $1 | tail -1 | awk '{print $2}')" 22 | 23 | cat << "EOF" > /dev/$tty 24 | 25 | ************************************************************* 26 | URGENT NOTICE FROM THE ADMINISTRATOR: 27 | 28 | This account is being suspended at the request of management. 29 | You are going to be logged out in $secs seconds. Please immediately 30 | shut down any processes you have running and log out. 31 | 32 | If you have any questions, please contact your supervisor or 33 | John Doe, Director of Information Technology. 34 | ************************************************************* 35 | EOF 36 | 37 | echo "(Warned $1, now sleeping $secs seconds)" 38 | 39 | sleep $secs 40 | 41 | killall -s HUP -u $1 # send hangup sig to their processes 42 | sleep 1 # give it a second... 43 | killall -s KILL -u $1 # and kill anything left 44 | 45 | echo "$(date): $1 was logged in. Just logged them out." 46 | fi 47 | 48 | # Finally, let's close off their home directory from prying eyes: 49 | 50 | chmod 000 $homedir/$1 51 | 52 | echo "Account $1 has been suspended." 53 | 54 | exit 0 55 | -------------------------------------------------------------------------------- /046-deleteuser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## Delete - delete a user account without a trace... 4 | # Not for use with Mac OS X 5 | 6 | homedir="/home" 7 | pwfile="/etc/passwd" shadow="/etc/shadow" 8 | newpwfile="/etc/passwd.new" newshadow="/etc/shadow.new" 9 | suspend="echo suspending " 10 | locker="/etc/passwd.lock" 11 | 12 | if [ -z $1 ] ; then 13 | echo "Usage: $0 account" >&2; exit 1 14 | elif [ "$(whoami)" != "root" ] ; then 15 | echo "Error: you must be 'root' to run this command.">&2; exit 1 16 | fi 17 | 18 | # $suspend $1 # suspend their account while we do the dirty work 19 | 20 | uid="$(grep -E "^${1}:" $pwfile | cut -d: -f3)" 21 | 22 | if [ -z $uid ] ; then 23 | echo "Error: no account $1 found in $pwfile" >&2; exit 1 24 | fi 25 | 26 | # remove from the password and shadow files 27 | grep -vE "^${1}:" $pwfile > $newpwfile 28 | grep -vE "^${1}:" $shadow > $newshadow 29 | 30 | lockcmd="$(which lockfile)" # find it in the path 31 | if [ ! -z $lockcmd ] ; then # let's use the system lockfile 32 | eval $lockcmd -r 15 $locker 33 | else # ulp, let's do it ourselves 34 | while [ -e $locker ] ; do 35 | echo "waiting for the password file" ; sleep 1 36 | done 37 | touch $locker # created a file-based lock 38 | fi 39 | 40 | mv $newpwfile $pwfile 41 | mv $newshadow $shadow 42 | rm -f $locker # click! unlocked again 43 | 44 | chmod 644 $pwfile 45 | chmod 400 $shadow 46 | 47 | # now remove home directory and list anything left... 48 | rm -rf $homedir/$1 49 | 50 | echo "Files still left to remove (if any):" 51 | find / -uid $uid -print 2>/dev/null | sed 's/^/ /' 52 | 53 | echo "" 54 | echo "Account $1 (uid $uid) has been deleted, and their home directory " 55 | echo "($homedir/$1) has been removed." 56 | 57 | exit 0 58 | -------------------------------------------------------------------------------- /047-validator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # VALIDATOR - Checks to ensure that all environment variables are valid 3 | # looks at SHELL, HOME, PATH, EDITOR, MAIL, and PAGER 4 | 5 | errors=0 6 | 7 | in_path() 8 | { 9 | # given a command and the PATH, try to find the command. Returns 10 | # 1 if found, 0 if not. Note that this temporarily modifies the 11 | # the IFS input field seperator, but restores it upon completion. 12 | cmd=$1 path=$2 retval=0 13 | 14 | oldIFS=$IFS; IFS=":" 15 | 16 | for directory in $path 17 | do 18 | if [ -x $directory/$cmd ] ; then 19 | retval=1 # if we're here, we found $cmd in $directory 20 | fi 21 | done 22 | IFS=$oldIFS 23 | return $retval 24 | } 25 | 26 | validate() 27 | { 28 | varname=$1 varvalue=$2 29 | 30 | if [ ! -z $varvalue ] ; then 31 | if [ "${varvalue%${varvalue#?}}" = "/" ] ; then 32 | if [ ! -x $varvalue ] ; then 33 | echo "** $varname set to $varvalue, but I cannot find executable." 34 | errors=$(( $errors + 1 )) 35 | fi 36 | else 37 | if in_path $varvalue $PATH ; then 38 | echo "** $varname set to $varvalue, but I cannot find it in PATH." 39 | errors=$(( $errors + 1 )) 40 | fi 41 | fi 42 | fi 43 | } 44 | 45 | ####### Beginning of actual shell script ####### 46 | 47 | if [ ! -x ${SHELL:?"Cannot proceed without SHELL being defined."} ] ; then 48 | echo "** SHELL set to $SHELL, but I cannot find that executable." 49 | errors=$(( $errors + 1 )) 50 | fi 51 | 52 | if [ ! -d ${HOME:?"You need to have your HOME set to your home directory"} ] 53 | then 54 | echo "** HOME set to $HOME, but it's not a directory." 55 | errors=$(( $errors + 1 )) 56 | fi 57 | 58 | # Our first interesting test: are all the paths in PATH valid? 59 | 60 | oldIFS=$IFS; IFS=":" # IFS is the field separator. We'll change to ':' 61 | 62 | for directory in $PATH 63 | do 64 | if [ ! -d $directory ] ; then 65 | echo "** PATH contains invalid directory $directory" 66 | errors=$(( $errors + 1 )) 67 | fi 68 | done 69 | 70 | IFS=$oldIFS # restore value for rest of script 71 | 72 | # The following can be undefined, and they can also be a progname, rather 73 | # than a fully qualified path. Add additional variables as necessary for 74 | # your site and user community. 75 | 76 | validate "EDITOR" $EDITOR 77 | validate "MAILER" $MAILER 78 | validate "PAGER" $PAGER 79 | 80 | # and, finally, a different ending depending on whether errors > 0 81 | 82 | if [ $errors -gt 0 ] ; then 83 | echo "Errors encountered. Please notify sysadmin for help." 84 | else 85 | echo "Your environment checks out fine." 86 | fi 87 | 88 | exit 0 89 | -------------------------------------------------------------------------------- /048-fixguest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # fixguest - Clean up the guest account during the logout process 4 | 5 | # don't trust environment variables: reference read-only sources 6 | 7 | iam="$(whoami)" 8 | myhome="$(grep "^${iam}:" /etc/passwd | cut -d: -f6)" 9 | 10 | # *** Do NOT run this script on a regular user account! 11 | 12 | if [ "$iam" != "guest" ] ; then 13 | echo "Error: you really don't want to run fixguest on this account." >&2 14 | exit 1 15 | fi 16 | 17 | if [ ! -d $myhome/..template ] ; then 18 | echo "$0: no template directory found for rebuilding." >&2 19 | exit 1 20 | fi 21 | 22 | # remove all files and directories in the home account 23 | 24 | cd $myhome 25 | 26 | rm -rf * $(find . -name ".[a-zA-Z0-9]*" -print) 27 | 28 | # now the only thing present should be the ..template directory 29 | 30 | cp -Rp ..template/* . 31 | 32 | exit 0 33 | -------------------------------------------------------------------------------- /049-findsuid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # findsuid - find all SUID files or programs on the system other 4 | # than those that live in /bin and /usr/bin, and 5 | # output the matches in a friendly and useful format. 6 | 7 | mtime="7" # how far back (in days) to check for modified cmds 8 | verbose=0 # by default, let's be quiet about things 9 | 10 | if [ "$1" = "-v" ] ; then 11 | verbose=1 12 | fi 13 | 14 | for match in $(find /bin /usr/bin -type f -perm +4000 -print) 15 | do 16 | if [ -x $match ] ; then 17 | 18 | owner="$(ls -ld $match | awk '{print $3}')" 19 | perms="$(ls -ld $match | cut -c5-10 | grep 'w')" 20 | 21 | if [ ! -z $perms ] ; then 22 | echo "**** $match (writeable and setuid $owner)" 23 | elif [ ! -z $(find $match -mtime -$mtime -print) ] ; then 24 | echo "**** $match (modified within $mtime days and setuid $owner)" 25 | elif [ $verbose -eq 1 ] ; then 26 | lastmod="$(ls -ld $match | awk '{print $6, $7, $8}')" 27 | echo " $match (setuid $owner, last modified $lastmod)" 28 | fi 29 | fi 30 | done 31 | 32 | exit 0 33 | 34 | -------------------------------------------------------------------------------- /050-set-date.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # setdate - friendly front-end to the date command 3 | 4 | # Date wants: [[[[[cc]yy]mm]dd]hh]mm[.ss] 5 | 6 | askvalue() 7 | { 8 | # $1 = field name, $2 = default value, $3 = max value, 9 | # $4 = required char/digit length 10 | 11 | echo -n "$1 [$2] : " 12 | read answer 13 | if [ ${answer:=$2} -gt $3 ] ; then 14 | echo "$0: $1 $answer is invalid"; exit 0 15 | elif [ "$(( $(echo $answer | wc -c) - 1 ))" -lt $4 ] ; then 16 | echo "$0: $1 $answer is too short: please specify $4 digits"; exit 0 17 | fi 18 | eval $1=$answer 19 | } 20 | 21 | eval $(date "+nyear=%Y nmon=%m nday=%d nhr=%H nmin=%M") 22 | 23 | askvalue year $nyear 3000 4 24 | askvalue month $nmon 12 2 25 | askvalue day $nday 31 2 26 | askvalue hour $nhr 24 2 27 | askvalue minute $nmin 59 2 28 | 29 | squished="$year$month$day$hour$minute" 30 | # or, if you're running a Linux system: 31 | # squished="$month$day$hour$minute$year" 32 | 33 | echo "Setting date to $squished. You might need to enter your sudo password:" 34 | sudo date $squished 35 | 36 | exit 0 37 | -------------------------------------------------------------------------------- /051-enabled.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # enabled - show what services are enabled with inetd and xinetd, 4 | # if they're available on the system. 5 | 6 | iconf="/etc/inetd.conf" 7 | xconf="/etc/xinetd.conf" 8 | xdir="/etc/xinetd.d" 9 | 10 | if [ -r $iconf ] ; then 11 | echo "Services enabled in $iconf are:" 12 | grep -v '^#' $iconf | awk '{print " " $1}' 13 | echo "" 14 | if [ "$(ps -aux | grep inetd | egrep -vE '(xinet|grep)')" = "" ] ; then 15 | echo "** warning: inetd does not appear to be running" 16 | fi 17 | fi 18 | 19 | if [ -r $xconf ] ; then 20 | # don't need to look in xinietd.conf, just know it exists 21 | echo "Services enabled in $xdir are:" 22 | 23 | for service in $xdir/* 24 | do 25 | if ! $(grep disable $service | grep 'yes' > /dev/null) ; then 26 | echo -n " " 27 | basename $service 28 | fi 29 | done 30 | 31 | if ! $(ps -aux | grep xinetd | grep -v 'grep' > /dev/null) ; then 32 | echo "** warning: xinetd does not appear to be running" 33 | fi 34 | fi 35 | 36 | exit 0 37 | -------------------------------------------------------------------------------- /052-killall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # killall - send the specified signal to all processes that match a 4 | # specific process name 5 | 6 | # By default it only kills processes owned by the same user, unless 7 | # you're root. Use -s SIGNAL to specify a signal to send, -u user to 8 | # specify user, -t tty to specify a tty, and -n to only show what'd 9 | # be done rather than doing it 10 | 11 | signal="-INT" # default signal 12 | user="" tty="" donothing=0 13 | 14 | while getopts "s:u:t:n" opt; do 15 | case "$opt" in 16 | # note the trick below: kill wants -SIGNAL but we're asking 17 | # for SIGNAL, so we slip the '-' in as part of the assignment 18 | s ) signal="-$OPTARG"; ;; 19 | u ) if [ ! -z "$tty" ] ; then 20 | echo "$0: error: -u and -t are mutually exclusive." >&2 21 | exit 1 22 | fi 23 | user=$OPTARG; ;; 24 | t ) if [ ! -z "$user" ] ; then 25 | echo "$0: error: -u and -t are mutually exclusive." >&2 26 | exit 1 27 | fi 28 | tty=$2; ;; 29 | n ) donothing=1; ;; 30 | ? ) echo "Usage: $0 [-s signal] [-u user|-t tty] [-n] pattern" >&2 31 | exit 1 32 | esac 33 | done 34 | 35 | shift $(( $OPTIND - 1 )) 36 | 37 | if [ $# -eq 0 ] ; then 38 | echo "Usage: $0 [-s signal] [-u user|-t tty] [-n] pattern" >&2 39 | exit 1 40 | fi 41 | 42 | if [ ! -z "$tty" ] ; then 43 | pids=$(ps cu -t $tty | awk "/ $1$/ { print \$2 }") 44 | elif [ ! -z "$user" ] ; then 45 | pids=$(ps cu -U $user | awk "/ $1$/ { print \$2 }") 46 | else 47 | pids=$(ps cu -U ${USER:-LOGNAME} | awk "/ $1$/ { print \$2 }") 48 | fi 49 | 50 | if [ -z "$pids" ] ; then 51 | echo "$0: no processes match pattern $1" >&2; exit 1 52 | fi 53 | 54 | for pid in $pids 55 | do 56 | # Sending signal $signal to process id $pid: kill might 57 | # still complain if the process has finished, user doesn't 58 | # have permission, etc, but that's okay. 59 | if [ $donothing -eq 1 ] ; then 60 | echo "kill $signal $pid" 61 | else 62 | kill $signal $pid 63 | fi 64 | done 65 | 66 | exit 0 67 | -------------------------------------------------------------------------------- /053-verifycron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # verifycron - script checks a crontab file to ensure that it's 4 | # formatted properly. Expects standard cron notation of 5 | # min hr dom mon dow CMD 6 | # where min is 0-59, hr 0-23, dom is 1-31, mon is 1-12 (or names) 7 | # and dow is 0-7 (or names). Fields can have ranges (a-e), lists 8 | # separated by commas (a,c,z), or an asterisk. Note that the step 9 | # value notation of Vixie cron is not supported (e.g., 2-6/2). 10 | 11 | 12 | validNum() 13 | { 14 | # return 0 if valid, 1 if not. Specify number and maxvalue as args 15 | num=$1 max=$2 16 | 17 | if [ "$num" = "X" ] ; then 18 | return 0 19 | elif [ ! -z $(echo $num | sed 's/[[:digit:]]//g') ] ; then 20 | return 1 21 | elif [ $num -lt 0 -o $num -gt $max ] ; then 22 | return 1 23 | else 24 | return 0 25 | fi 26 | } 27 | 28 | validDay() 29 | { 30 | # return 0 if a valid dayname, 1 otherwise 31 | 32 | case $(echo $1 | tr '[:upper:]' '[:lower:]') in 33 | sun*|mon*|tue*|wed*|thu*|fri*|sat*) return 0 ;; 34 | X) return 0 ;; # special case - it's an "*" 35 | *) return 1 36 | esac 37 | } 38 | 39 | validMon() 40 | { 41 | # return 0 if a valid month name, 1 otherwise 42 | 43 | case $(echo $1 | tr '[:upper:]' '[:lower:]') in 44 | jan*|feb*|mar*|apr*|may|jun*|jul*|aug*) return 0 ;; 45 | sep*|oct*|nov*|dec*) return 0 ;; 46 | X) return 0 ;; # special case, it's an "*" 47 | *) return 1 ;; 48 | esac 49 | } 50 | 51 | fixvars() 52 | { 53 | # translate all '*' into 'X' to bypass shell expansion hassles 54 | # save original as "sourceline" for error messages 55 | 56 | sourceline="$min $hour $dom $mon $dow $command" 57 | min=$(echo "$min" | tr '*' 'X') 58 | hour=$(echo "$hour" | tr '*' 'X') 59 | dom=$(echo "$dom" | tr '*' 'X') 60 | mon=$(echo "$mon" | tr '*' 'X') 61 | dow=$(echo "$dow" | tr '*' 'X') 62 | } 63 | 64 | if [ $# -ne 1 ] || [ ! -r $1 ] ; then 65 | echo "Usage: $0 usercrontabfile" >&2; exit 1 66 | fi 67 | 68 | lines=0 entries=0 totalerrors=0 69 | 70 | while read min hour dom mon dow command 71 | do 72 | lines="$(( $lines + 1 ))" 73 | errors=0 74 | 75 | if [ -z "$min" -o "${min%${min#?}}" = "#" ] ; then 76 | continue # nothing to check 77 | elif [ ! -z $(echo ${min%${min#?}} | sed 's/[[:digit:]]//') ] ; then 78 | continue # first char not digit: skip! 79 | fi 80 | 81 | entries="$(($entries + 1))" 82 | 83 | fixvars 84 | 85 | #### Broken into fields, all '*' replaced with 'X' 86 | # minute check 87 | 88 | for minslice in $(echo "$min" | sed 's/[,-]/ /g') ; do 89 | if ! validNum $minslice 60 ; then 90 | echo "Line ${lines}: Invalid minute value \"$minslice\"" 91 | errors=1 92 | fi 93 | done 94 | 95 | # hour check 96 | 97 | for hrslice in $(echo "$hour" | sed 's/[,-]/ /g') ; do 98 | if ! validNum $hrslice 24 ; then 99 | echo "Line ${lines}: Invalid hour value \"$hrslice\"" 100 | errors=1 101 | fi 102 | done 103 | 104 | # day of month check 105 | 106 | for domslice in $(echo $dom | sed 's/[,-]/ /g') ; do 107 | if ! validNum $domslice 31 ; then 108 | echo "Line ${lines}: Invalid day of month value \"$domslice\"" 109 | errors=1 110 | fi 111 | done 112 | 113 | # month check 114 | 115 | for monslice in $(echo "$mon" | sed 's/[,-]/ /g') ; do 116 | if ! validNum $monslice 12 ; then 117 | if ! validMon "$monslice" ; then 118 | echo "Line ${lines}: Invalid month value \"$monslice\"" 119 | errors=1 120 | fi 121 | fi 122 | done 123 | 124 | # day of week check 125 | 126 | for dowslice in $(echo "$dow" | sed 's/[,-]/ /g') ; do 127 | if ! validNum $dowslice 7 ; then 128 | if ! validDay $dowslice ; then 129 | echo "Line ${lines}: Invalid day of week value \"$dowslice\"" 130 | errors=1 131 | fi 132 | fi 133 | done 134 | 135 | if [ $errors -gt 0 ] ; then 136 | echo ">>>> ${lines}: $sourceline" 137 | echo "" 138 | totalerrors="$(( $totalerrors + 1 ))" 139 | fi 140 | done < $1 141 | 142 | echo "Done. Found $totalerrors errors in $entries crontab entries." 143 | 144 | exit 0 145 | -------------------------------------------------------------------------------- /054-docron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # DOCRON - simple script to run the daily, weekly and monthly 4 | # system cron jobs on a system where it's likely that 5 | # it'll be shut down at the usual time of day when 6 | # this would occur. 7 | 8 | rootcron="/etc/crontab" 9 | 10 | if [ $# -ne 1 ] ; then 11 | echo "Usage: $0 [daily|weekly|monthly]" >&2 12 | exit 1 13 | fi 14 | 15 | if [ "$(id -u)" -ne 0 ] ; then 16 | echo "$0: Command must be run as 'root'" >&2 17 | exit 1 18 | fi 19 | 20 | job="$(awk "NR > 6 && /$1/ { for (i=7;i<=NF;i++) print \$i }" $rootcron)" 21 | 22 | if [ -z $job ] ; then 23 | echo "$0: Error: no $1 job found in $rootcron" >&2 24 | exit 1 25 | fi 26 | 27 | SHELL=/bin/sh # to be consistent with cron's default 28 | 29 | eval $job 30 | -------------------------------------------------------------------------------- /055-rotatelogs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # rotatelogs - roll logfiles in /var/log for archival purposes. 4 | # uses a config file to allow customization of how frequently 5 | # each log should be rolled. That file is in 6 | # logfilename=duration 7 | # format, where duration is in days. If nothing is configured, 8 | # rotatelogs won't rotate more frequently than every seven days. 9 | 10 | logdir="/var/log" 11 | config="/var/log/rotatelogs.conf" 12 | mv="/bin/mv" 13 | default_duration=7 14 | count=0 15 | 16 | duration=$default_duration 17 | 18 | if [ ! -f $config ] ; then 19 | echo "$0: no config file found. Can't proceed." >&2; exit 1 20 | fi 21 | 22 | if [ ! -w $logdir -o ! -x $logdir ] ; then 23 | echo "$0: you don't have the appropriate permissions in $logdir" >&2 24 | exit 1 25 | fi 26 | 27 | cd $logdir 28 | 29 | # While we'd like to use ':digit:' with the find, many versions of 30 | # find don't support Posix character class identifiers, hence [0-9] 31 | 32 | for name in $(find . -type f -size +0c ! -name '*[0-9]*' \ 33 | ! -name '\.*' ! -name '*conf' -maxdepth 1 -print | sed 's/^\.\///') 34 | do 35 | 36 | count=$(( $count + 1 )) 37 | 38 | # grab this entry from the config file 39 | 40 | duration="$(grep "^${name}=" $config|cut -d= -f2)" 41 | 42 | if [ -z $duration ] ; then 43 | duration=$default_duration 44 | elif [ "$duration" = "0" ] ; then 45 | echo "Duration set to zero: skipping $name" 46 | continue 47 | fi 48 | 49 | back1="${name}.1"; back2="${name}.2"; 50 | back3="${name}.3"; back4="${name}.4"; 51 | 52 | # If the most recently rolled log file (back1) has been modified within 53 | # the specific quantum then it's not time to rotate it. 54 | 55 | if [ -f "$back1" ] ; then 56 | if [ -z $(find "$back1" -mtime +$duration -print 2>/dev/null) ] 57 | then 58 | echo "$name's most recent backup is more recent than $duration days: skipping" 59 | continue 60 | fi 61 | fi 62 | 63 | echo "Rotating log $name (using a $duration day schedule)" 64 | 65 | # rotate, starting with the oldest log 66 | if [ -f "$back3" ] ; then 67 | echo "... $back3 -> $back4" ; $mv -f "$back3" "$back4" 68 | fi 69 | if [ -f "$back2" ] ; then 70 | echo "... $back2 -> $back3" ; $mv -f "$back2" "$back3" 71 | fi 72 | if [ -f "$back1" ] ; then 73 | echo "... $back1 -> $back2" ; $mv -f "$back1" "$back2" 74 | fi 75 | if [ -f "$name" ] ; then 76 | echo "... $name -> $back1" ; $mv -f "$name" "$back1" 77 | fi 78 | touch "$name" 79 | chmod 0600 "$name" 80 | done 81 | 82 | if [ $count -eq 0 ] ; then 83 | echo "Nothing to do: no log files big enough or old enough to rotate" 84 | fi 85 | 86 | exit 0 87 | -------------------------------------------------------------------------------- /056-backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Backup - create either a full or incremental backup of a set of 4 | # defined directories on the system. By default, the output 5 | # file is saved in /tmp with a timestamped filename, compressed. 6 | # Otherwise, specify an output device (another disk, a removable). 7 | 8 | usageQuit() 9 | { 10 | cat << "EOF" >&2 11 | Usage: $0 [-o output] [-i|-f] [-n] 12 | -o lets you specify an alternative backup file/device 13 | -i is an incremental or -f is a full backup, and -n prevents 14 | updating the timestamp if an incremental backup is done. 15 | EOF 16 | exit 1 17 | } 18 | 19 | compress="bzip2" # change for your favorite compression app 20 | inclist="/tmp/backup.inclist.$(date +%d%m%y)" 21 | output="/tmp/backup.$(date +%d%m%y).bz2" 22 | tsfile="$HOME/.backup.timestamp" 23 | btype="incremental" # default to an incremental backup 24 | noinc=0 # and an update of the timestamp 25 | 26 | trap "/bin/rm -f $inclist" EXIT 27 | 28 | while getopts "o:ifn" opt; do 29 | case "$arg" in 30 | o ) output="$OPTARG"; ;; 31 | i ) btype="incremental"; ;; 32 | f ) btype="full"; ;; 33 | n ) noinc=1; ;; 34 | ? ) usageQuit ;; 35 | esac 36 | done 37 | 38 | shift $(( $OPTIND - 1 )) 39 | 40 | echo "Doing $btype backup, saving output to $output" 41 | 42 | timestamp="$(date +'%m%d%I%M')" 43 | 44 | if [ "$btype" = "incremental" ] ; then 45 | if [ ! -f $tsfile ] ; then 46 | echo "Error: can't do an incremental backup: no timestamp file" >&2 47 | exit 1 48 | fi 49 | find $HOME -depth -type f -newer $tsfile -user ${USER:-LOGNAME} | \ 50 | pax -w -x tar | $compress > $output 51 | failure="$?" 52 | else 53 | find $HOME -depth -type f -user ${USER:-LOGNAME} | \ 54 | pax -w -x tar | $compress > $output 55 | failure="$?" 56 | fi 57 | 58 | if [ "$noinc" = "0" -a "$failure" = "0" ] ; then 59 | touch -t $timestamp $tsfile 60 | fi 61 | 62 | exit 0 63 | -------------------------------------------------------------------------------- /057-archivedir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # archivedir - create a compressed archive of the specified directory 4 | 5 | maxarchivedir=10 # size, in blocks, of 'big' directory, to confirm 6 | compress=gzip # change to your favorite compress app 7 | progname=$(basename $0) 8 | 9 | if [ $# -eq 0 ] ; then 10 | echo "Usage: $progname directory" >&2 ;exit 1 11 | fi 12 | 13 | if [ ! -d $1 ] ; then 14 | echo "${progname}: can't find directory $1 to archive." >&2; exit 1 15 | fi 16 | 17 | if [ "$(basename $1)" != "$1" -o "$1" = "." ] ; then 18 | echo "${progname}: You must specify a subdirectory" >&2 19 | exit 1 20 | fi 21 | 22 | if [ ! -w . ] ; then 23 | echo "${progname}: cannot write archive file to current directory." >&2 24 | exit 1 25 | fi 26 | 27 | dirsize="$(du -s $1 | awk '{print $1}')" 28 | 29 | if [ $dirsize -gt $maxarchivedir ] ; then 30 | echo -n "Warning: directory $1 is $dirsize blocks. Proceed? [n] " 31 | read answer 32 | answer="$(echo $answer | tr '[:upper:]' '[:lower:]' | cut -c1)" 33 | if [ "$answer" != "y" ] ; then 34 | echo "${progname}: archive of directory $1 cancelled." >&2 35 | exit 0 36 | fi 37 | fi 38 | 39 | archivename="$(echo $1 | sed 's/$/.tgz/')" 40 | 41 | if tar cf - $1 | $compress > $archivename ; then 42 | echo "Directory $1 archived as $archivename" 43 | else 44 | echo "Warning: tar encountered errors archiving $1" 45 | fi 46 | 47 | exit 0 48 | -------------------------------------------------------------------------------- /058-connecttime.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # connecttime - reports cumulative connection time for month/year entries 4 | # found in the system log file. 5 | 6 | log="/var/log/system.log" 7 | tempfile="/tmp/$0.$$" 8 | 9 | trap "rm $tempfile" 0 10 | 11 | cat << 'EOF' > $tempfile 12 | BEGIN { 13 | lastmonth=""; sum = 0 14 | } 15 | { 16 | if ( $1 != lastmonth && lastmonth != "" ) { 17 | if (sum > 60) { total = sum/60 " hours" } 18 | else { total = sum " minutes" } 19 | print lastmonth ": " total 20 | sum=0 21 | } 22 | lastmonth=$1 23 | sum += $8 24 | } 25 | END { 26 | if (sum > 60) { total = sum/60 " hours" } 27 | else { total = sum " minutes" } 28 | print lastmonth ": " total 29 | } 30 | EOF 31 | 32 | grep "Connect time" $log | awk -f $tempfile 33 | 34 | exit 0 35 | -------------------------------------------------------------------------------- /059-ftpget.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ftpget - given an ftp: style URL, unwrap it, and try to obtain the file 4 | # using anonymous ftp. 5 | 6 | anonpass="$LOGNAME@$(hostname)" 7 | 8 | if [ $# -ne 1 ] ; then 9 | echo "Usage: $0 ftp://..." >&2 10 | exit 1 11 | fi 12 | 13 | # Typical URL: ftp://ftp.ncftp.com/2.7.1/ncftpd-2.7.1.tar.gz 14 | 15 | if [ "$(echo $1 | cut -c1-6)" != "ftp://" ] ; then 16 | echo "$0: Malformed url. I need it to start with ftp://" >&2; 17 | exit 1 18 | fi 19 | 20 | server="$(echo $1 | cut -d/ -f3)" 21 | filename="$(echo $1 | cut -d/ -f4-)" 22 | basefile="$(basename $filename)" 23 | 24 | echo ${0}: Downloading $basefile from server $server 25 | 26 | ftp -n << EOF 27 | open $server 28 | user ftp $anonpass 29 | get $filename $basefile 30 | quit 31 | EOF 32 | 33 | if [ $? -eq 0 ] ; then 34 | ls -l $basefile 35 | fi 36 | 37 | exit 0 38 | -------------------------------------------------------------------------------- /060-bbcnews.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # bbcnews - report the top stories on the BBC World Service 4 | 5 | url="http://news.bbc.co.uk/2/low/technology/default.stm" 6 | 7 | lynx -source $url | \ 8 | sed -n '/Last Updated:/,/newssearch.bbc.co.uk/p' | \ 9 | sed 's/\ 10 | />\ 11 | /g' | \ 12 | grep -v -E '(<|>)' | \ 13 | fmt | \ 14 | uniq 15 | -------------------------------------------------------------------------------- /061-getlinks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # getlinks - given a URL, return all of its internal and 4 | # external links 5 | 6 | if [ $# -eq 0 ] ; then 7 | echo "Usage: $0 [-d|-i|-x] url" >&2 8 | echo "-d=domains only, -i=internal refs only, -x=external only" >&2 9 | exit 1 10 | fi 11 | 12 | if [ $# -gt 1 ] ; then 13 | case "$1" in 14 | -d) lastcmd="cut -d/ -f3 | sort | uniq" 15 | shift 16 | ;; 17 | -i) basedomain="http://$(echo $2 | cut -d/ -f3)/" 18 | lastcmd="grep \"^$basedomain\" | sed \"s|$basedomain||g\" | sort | uniq" 19 | shift 20 | ;; 21 | -x) basedomain="http://$(echo $2 | cut -d/ -f3)/" 22 | lastcmd="grep -v \"^$basedomain\" | sort | uniq" 23 | shift 24 | ;; 25 | *) echo "$0: unknown option specified: $1" >&2; exit 1 26 | esac 27 | else 28 | lastcmd="sort | uniq" 29 | fi 30 | 31 | lynx -dump "$1" | \ 32 | sed -n '/^References$/,$p' | \ 33 | grep -E '[[:digit:]]+\.' | \ 34 | awk '{print $2}' | \ 35 | cut -d\? -f1 | \ 36 | eval $lastcmd 37 | 38 | exit 0 39 | -------------------------------------------------------------------------------- /062-define.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # define - given a word, return its definition from dictionary.com 4 | 5 | url="http://www.cogsci.princeton.edu/cgi-bin/webwn2.0?stage=1&word=" 6 | 7 | if [ $# -ne 1 ] ; then 8 | echo "Usage: $0 word" >&2 9 | exit 1 10 | fi 11 | 12 | lynx -source "$url$1" | \ 13 | grep -E '(^[[:digit:]]+\.| has [[:digit:]]+$)' | \ 14 | sed 's/<[^>]*>//g' | 15 | ( while read line 16 | do 17 | if [ "${line:0:3}" = "The" ] ; then 18 | part="$(echo $line | awk '{print $2}')" 19 | echo "" 20 | echo "The $part $1:" 21 | else 22 | echo "$line" | fmt | sed 's/^/ /g' 23 | fi 24 | done 25 | ) 26 | 27 | exit 0 28 | -------------------------------------------------------------------------------- /063-weather.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # weather - report weather forecast, including lat/long, for zip 4 | 5 | llurl="http://www.census.gov/cgi-bin/gazetteer?city=&state=&zip=" 6 | wxurl="http://wwwa.accuweather.com" 7 | wxurl="$wxurl/adcbin/public/local_index_print.asp?zipcode=" 8 | 9 | if [ "$1" = "-a" ] ; then 10 | size=999; shift 11 | else 12 | size=5 13 | fi 14 | 15 | if [ $# -eq 0 ] ; then 16 | echo "Usage: $0 [-a] zipcode" >&2 17 | exit 1 18 | fi 19 | 20 | if [ $size -eq 5 ] ; then 21 | echo "" 22 | 23 | # get some information on the zipcode from the Census Bureau 24 | 25 | lynx -source "${llurl}$1" | \ 26 | sed -n '/^
" 13 | env || printenv 14 | echo "" 15 | echo "
" 17 | cat - 18 | echo "(end of input stream)" 19 | 20 | exit 0 21 | -------------------------------------------------------------------------------- /070-logsearch.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # log Yahoo! search - given a search request, log the pattern, then 4 | # feed the entire sequence to the real Yahoo search system. 5 | 6 | logfile="/home/taylor/scripts/searchlog.txt" 7 | 8 | if [ ! -f $logfile ] ; then 9 | touch $logfile 10 | chmod a+rw $logfile 11 | fi 12 | 13 | if [ -w $logfile ] ; then 14 | echo "$(date): $QUERY_STRING" | sed 's/p=//g;s/+/ /g' >> $logfile 15 | fi 16 | 17 | # echo "Content-type: text/html" 18 | # echo "" 19 | 20 | echo "Location: http://search.yahoo.com/bin/search?$QUERY_STRING" 21 | echo "" 22 | 23 | exit 0 24 | -------------------------------------------------------------------------------- /070-yahoo-search.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /071-getdope.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Within cron set it up so that every Friday, grab the latest column 4 | # of 'The Straight Dope' and mail it out to the specified recipient 5 | 6 | now="$(date +%y%m%d)" 7 | url="http://www.straightdope.com/columns/${now}.html" 8 | to="taylor" 9 | 10 | ( cat << EOF 11 | Subject: The Straight Dope for $(date "+%A, %d %B, %Y") 12 | From: Cecil Adams
Bill Holbrook's Kevin & Kell |
---|
" 27 | echo "© Bill Holbrook. Please see " 28 | echo "kevinandkell.com" 29 | echo "for more strips, books, etc." 30 | echo " |
" 23 | count=1 24 | else 25 | echo " | "
26 | count=$(( $count + 1 ))
27 | fi
28 |
29 | nicename="$(echo $name | sed 's/.jpg//;s/-/ /g')"
30 |
31 | echo " " 33 | echo "$nicename" 34 | done 35 | 36 | echo " |
$name signed thusly: |
$comment |
Added $date"
67 | echo " |
10 | This page was last modified on 11 | 12 | according to the SSI LAST_MODIFIED variable. 13 |
14 | Finally, the random tagline for this load is: 15 |
Password Manager Actions | |||
---|---|---|---|
7 | 16 | | 17 | 26 | | 27 | 30 | | 31 | 34 | |