#!/bin/bash # # Bottles of Beer song in Bash # # A way-overcomplicated implementation of the bottles of beer song in bash. # Optional argument: # # * `--careless` - Generate the "happen to fall" version # # NOTE: This is an altered version of a "99 bottles of beer" song # Bash script by Bill Brown. The intent of the altered version is # to show the style, so the above text is preserved from [the # original][99b]. # # [99b]: http://99-bottles-of-beer.net/language-bash-1831.html # # # Default transition mode # # 'standard' to use the standard form; 'careless' to have the # song consider effects of gravity on the bottles. # # Default is 'standard'. # NN_BOTTLES__MODE=${NN_BOTTLES__MODE:-standard} # # Starting bottle amount # # Initial amount of bottles specified as numeral. Numerals up # to 99 are supported. # # Default is 'ninety-nine'. # NN_STARTING_AMOUNT=${NN_STARTING_AMOUNT:-ninety-nine} pred() { # # Get predecessor to a number $1 # local numeral=$1 # number in word form case $numeral in *nine) echo "${numeral%nine}eight" ;; *eight) echo "${numeral%eight}seven" ;; *seven) echo "${numeral%seven}six" ;; *six) echo "${numeral%six}five" ;; *five) echo "${numeral%five}four" ;; *four) echo "${numeral%four}three" ;; *three) echo "${numeral%three}two" ;; *two) echo "${numeral%two}one" ;; one) echo "zero" ;; *-one) echo "${numeral%-one}" ;; *one) echo "${numeral%one}" ;; ten) echo "nine" ;; eleven) echo "ten" ;; twelve) echo "eleven" ;; *teen) teenpred "$numeral" ;; *ty) tenspred "$numeral" ;; zero) echo ""; #to terminate esac } teenpred() { # # Get predecessor of a teen $1 # local numeral=$1 # number in word form case $numeral in thirteen) echo twelve;; *) echo "$(crunchprefix "$(pred "$(uncrunchprefix "${numeral%teen}")")")teen" ;; esac } tenspred() { # # Get predecessor of a multiple of ten $1 # local numeral=$1 # number in word form case $numeral in twenty) echo nineteen;; *) echo "$(crunchprefix --tens "$(pred "$(uncrunchprefix "${numeral%ty}")")")ty-nine";; esac } crunchprefix() { # # Crunch number prefix $1 to its conventional form # # ...such as `three` --> `thir` # # option `--tens` - multiples of ten are a bit different # local numeral # number in word form local tensop # 'true' if --tens option is active [ "$1" = --tens ] && { tensop=true; shift; } numeral=$1 case $numeral in two) [ -n "$tensop" ] && echo twen || echo "$numeral";; three) echo thir;; four) [ -n "$tensop" ] && echo 'for' || echo "$numeral";; five) echo fif;; eight) [ -n "$tensop" ] && echo eigh || echo "$numeral";; *) echo "$numeral" ;; esac } uncrunchprefix() { # # Reverse crunchprefix $1 # local prefix=$1 # numeral crunch-prefix case $prefix in twen) echo two;; thir) echo three;; 'for') echo four;; fif) echo five;; eigh) echo eight;; *) echo "$prefix";; esac } grammar() { # # Apply peculiarities of English grammar to text on stdin # local oneBottle=false # 'true' if this line affects the following line local line # every line while read -r line; do line="${line/one more bottles/one more bottle}" case "$line" in *"one of those bottles"*) line="$( [ $oneBottle = true ] \ && echo "${line/one of those bottles/that lone bottle}" \ || echo "$line" )" ;; *"one down"*) line="$( [ $oneBottle = true ] \ && echo "${line/one down/it down}" \ || echo "$line" )" ;; *bottles*) oneBottle=false;; *bottle*) oneBottle=true;; esac #Some say the twenties should have no hyphen line="${line/twenty-/twenty }" echo "$line" done } capitalize() { # # Fix capitalization of each line on stdin # local line # every line while read -r line; do echo -n "${line:0:1}" | tr '[:lower:]' '[:upper:]' echo "${line#?}" done } punctuate() { # # Add punctuation to each line on stdin # local line # every line while read -r line; do case "${line}" in [Ii]f*) echo "${line},";; '') echo;; *) echo "${line}.";; esac done } verse() { # # Write one verse with number $1 # local nb=$1 # numeral echo "$nb bottles of beer on the wall" echo "$nb bottles of beer" if [ "$nb" = zero ]; then echo "Go to the store and buy some more" nb=ninety-nine else echo "$TransitionLine" nb=$(pred "$nb") fi echo "$nb bottles of beer on the wall" } poeticize() { # # Make text on stdin nice # local first # first word of a line local rest # remainder of a line local syl # number of syllables in the first word while read -r first rest; do case "$rest" in *beer*) first=${first/zero/no} syl=$(syllables "${first% *}") case $syl in #improve meter 1|2) echo "$first more $rest";; *) echo "$first $rest" ;; esac ;; *) echo "$first $rest" esac done } syllables() { # # Estimate number of syllables in word $1 # local word=$1 # word (numeral) to do the estimation on local n=1 case $word in eleven) n=2;; #sounds better if not considered 3 *teen) n=2;; *ty) n=2;; *-*) n=3;; #don't care about more than 3 esac case $word in *seven*) ((n = n+1)) ;; esac echo "$n" } main() { local TransitionLine # transition line between verses local togo=$NN_STARTING_AMOUNT # bottles to go local mode=$NN_BOTTLES__MODE # transition mode while true; do case $1 in --careless) mode=careless; shift ;; *) break ;; esac done TransitionLine=$( case $mode in standard) echo "Take one down and pass it around" ;; careless) echo "If one of those bottles should happen to fall" ;; esac ) togo=ninety-nine while [ -n "$togo" ]; do verse "$togo" echo togo=$(pred "$togo") done \ | poeticize \ | grammar \ | punctuate \ | capitalize } main "$@"