99bottles.sh 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #!/bin/bash
  2. #
  3. # Bottles of Beer song in Bash
  4. #
  5. # A way-overcomplicated implementation of the bottles of beer song in bash.
  6. # Optional argument:
  7. #
  8. # * `--careless` - Generate the "happen to fall" version
  9. #
  10. # NOTE: This is an altered version of a "99 bottles of beer" song
  11. # Bash script by Bill Brown. The intent of the altered version is
  12. # to show the style, so the above text is preserved from [the
  13. # original][99b].
  14. #
  15. # [99b]: http://99-bottles-of-beer.net/language-bash-1831.html
  16. #
  17. pred() {
  18. #
  19. # Get predecessor to a number $1
  20. #
  21. local numeral=$1 # number in word form
  22. case $numeral in
  23. *nine) echo "${numeral%nine}eight" ;;
  24. *eight) echo "${numeral%eight}seven" ;;
  25. *seven) echo "${numeral%seven}six" ;;
  26. *six) echo "${numeral%six}five" ;;
  27. *five) echo "${numeral%five}four" ;;
  28. *four) echo "${numeral%four}three" ;;
  29. *three) echo "${numeral%three}two" ;;
  30. *two) echo "${numeral%two}one" ;;
  31. one) echo "zero" ;;
  32. *-one) echo "${numeral%-one}" ;;
  33. *one) echo "${numeral%one}" ;;
  34. ten) echo "nine" ;;
  35. eleven) echo "ten" ;;
  36. twelve) echo "eleven" ;;
  37. *teen) teenpred "$numeral" ;;
  38. *ty) tenspred "$numeral" ;;
  39. zero) echo ""; #to terminate
  40. esac
  41. }
  42. teenpred() {
  43. #
  44. # Get predecessor of a teen $1
  45. #
  46. local numeral=$1 # number in word form
  47. case $numeral in
  48. thirteen) echo twelve;;
  49. *) echo "$(crunchprefix "$(pred "$(uncrunchprefix "${numeral%teen}")")")teen" ;;
  50. esac
  51. }
  52. tenspred() {
  53. #
  54. # Get predecessor of a multiple of ten $1
  55. #
  56. local numeral=$1 # number in word form
  57. case $numeral in
  58. twenty) echo nineteen;;
  59. *) echo "$(crunchprefix --tens "$(pred "$(uncrunchprefix "${numeral%ty}")")")ty-nine";;
  60. esac
  61. }
  62. crunchprefix() {
  63. #
  64. # Crunch number prefix $1 to its conventional form
  65. #
  66. # ...such as `three` --> `thir`
  67. #
  68. # option `--tens` - multiples of ten are a bit different
  69. #
  70. local numeral # number in word form
  71. local tensop # 'true' if --tens option is active
  72. [ "$1" = --tens ] && { tensop=true; shift; }
  73. numeral=$1
  74. case $numeral in
  75. two) [ -n "$tensop" ] && echo twen || echo "$numeral";;
  76. three) echo thir;;
  77. four) [ -n "$tensop" ] && echo 'for' || echo "$numeral";;
  78. five) echo fif;;
  79. eight) [ -n "$tensop" ] && echo eigh || echo "$numeral";;
  80. *) echo "$numeral" ;;
  81. esac
  82. }
  83. uncrunchprefix() {
  84. #
  85. # Reverse crunchprefix $1
  86. #
  87. local prefix=$1 # numeral crunch-prefix
  88. case $prefix in
  89. twen) echo two;;
  90. thir) echo three;;
  91. 'for') echo four;;
  92. fif) echo five;;
  93. eigh) echo eight;;
  94. *) echo "$prefix";;
  95. esac
  96. }
  97. grammar() {
  98. #
  99. # Apply peculiarities of English grammar to text on stdin
  100. #
  101. local oneBottle=false # 'true' if this line affects the following line
  102. local line # every line
  103. while read line; do
  104. line="${line/one more bottles/one more bottle}"
  105. case "$line" in
  106. *"one of those bottles"*) line="$(
  107. [ $oneBottle = true ] \
  108. && echo "${line/one of those bottles/that lone bottle}" \
  109. || echo "$line"
  110. )"
  111. ;;
  112. *"one down"*) line="$(
  113. [ $oneBottle = true ] \
  114. && echo "${line/one down/it down}" \
  115. || echo "$line"
  116. )"
  117. ;;
  118. *bottles*) oneBottle=false;;
  119. *bottle*) oneBottle=true;;
  120. esac
  121. #Some say the twenties should have no hyphen
  122. line="${line/twenty-/twenty }"
  123. echo "$line"
  124. done
  125. }
  126. capitalize() {
  127. #
  128. # Fix capitalization of each line on stdin
  129. #
  130. local line # every line
  131. while read line; do
  132. echo -n "${line:0:1}" | tr '[:lower:]' '[:upper:]'
  133. echo "${line#?}"
  134. done
  135. }
  136. punctuate() {
  137. #
  138. # Add punctuation to each line on stdin
  139. #
  140. local line # every line
  141. while read line; do
  142. case "${line}" in
  143. [Ii]f*) echo "${line},";;
  144. '') echo;;
  145. *) echo "${line}.";;
  146. esac
  147. done
  148. }
  149. verse() {
  150. #
  151. # Write one verse with number $1
  152. #
  153. local nb=$1 # numeral
  154. echo "$nb bottles of beer on the wall"
  155. echo "$nb bottles of beer"
  156. if [ "$nb" = zero ]; then
  157. echo "Go to the store and buy some more"
  158. nb=ninety-nine
  159. else
  160. echo "$breakLine"
  161. nb=$(pred "$nb")
  162. fi
  163. echo "$nb bottles of beer on the wall"
  164. }
  165. poeticize() {
  166. #
  167. # Make text on stdin nice
  168. #
  169. local first # first word of a line
  170. local rest # remainder of a line
  171. local syl # number of syllables in the first word
  172. while read first rest; do
  173. case "$rest" in
  174. *beer*)
  175. first=${first/zero/no}
  176. syl=$(syllables "${first% *}")
  177. case $syl in #improve meter
  178. 1|2) echo "$first more $rest";;
  179. *) echo "$first $rest" ;;
  180. esac
  181. ;;
  182. *) echo "$first $rest"
  183. esac
  184. done
  185. }
  186. syllables() {
  187. #
  188. # Estimate number of syllables in word $1
  189. #
  190. local word=$1 # word (numeral) to do the estimation on
  191. local n=1
  192. case $word in
  193. eleven) n=2;; #sounds better if not considered 3
  194. *teen) n=2;;
  195. *ty) n=2;;
  196. *-*) n=3;; #don't care about more than 3
  197. esac
  198. case $word in
  199. *seven*) let $((n = n+1));;
  200. esac
  201. echo "$n"
  202. }
  203. main() {
  204. local breakLine # bottle-break (or not) line
  205. local standardBreakLine # ^^ standard variant
  206. local wastefulBreakLine # ^^ wasteful (bottle breaks) variant
  207. local nb # beginning number
  208. standardBreakLine="Take one down and pass it around"
  209. wastefulBreakLine="If one of those bottles should happen to fall"
  210. breakLine=$(
  211. [ "$1" = --careless ] \
  212. && echo "$wastefulBreakLine" \
  213. || echo "$standardBreakLine"
  214. )
  215. nb=ninety-nine
  216. while [ -n "$nb" ]; do
  217. verse "$nb"
  218. echo
  219. nb=$(pred "$nb")
  220. done | poeticize | grammar | punctuate | capitalize
  221. }
  222. main "$@"