#!/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 # 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 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 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 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 "$breakLine" 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 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*) let $((n = n+1));; esac echo "$n" } main() { local breakLine # bottle-break (or not) line local standardBreakLine # ^^ standard variant local wastefulBreakLine # ^^ wasteful (bottle breaks) variant local nb # beginning number standardBreakLine="Take one down and pass it around" wastefulBreakLine="If one of those bottles should happen to fall" breakLine=$( [ "$1" = --careless ] \ && echo "$wastefulBreakLine" \ || echo "$standardBreakLine" ) nb=ninety-nine while [ -n "$nb" ]; do verse "$nb" echo nb=$(pred "$nb") done | poeticize | grammar | punctuate | capitalize } main "$@"