99bottles.sh 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. #
  18. # Default transition mode
  19. #
  20. # 'standard' to use the standard form; 'careless' to have the
  21. # song consider effects of gravity on the bottles.
  22. #
  23. # Default is 'standard'.
  24. #
  25. NN_BOTTLES__MODE=${NN_BOTTLES__MODE:-standard}
  26. #
  27. # Starting bottle amount
  28. #
  29. # Initial amount of bottles specified as numeral. Numerals up
  30. # to 99 are supported.
  31. #
  32. # Default is 'ninety-nine'.
  33. #
  34. NN_STARTING_AMOUNT=${NN_STARTING_AMOUNT:-ninety-nine}
  35. pred() {
  36. #
  37. # Get predecessor to a number $1
  38. #
  39. local numeral=$1 # number in word form
  40. case $numeral in
  41. *nine) echo "${numeral%nine}eight" ;;
  42. *eight) echo "${numeral%eight}seven" ;;
  43. *seven) echo "${numeral%seven}six" ;;
  44. *six) echo "${numeral%six}five" ;;
  45. *five) echo "${numeral%five}four" ;;
  46. *four) echo "${numeral%four}three" ;;
  47. *three) echo "${numeral%three}two" ;;
  48. *two) echo "${numeral%two}one" ;;
  49. one) echo "zero" ;;
  50. *-one) echo "${numeral%-one}" ;;
  51. *one) echo "${numeral%one}" ;;
  52. ten) echo "nine" ;;
  53. eleven) echo "ten" ;;
  54. twelve) echo "eleven" ;;
  55. *teen) teenpred "$numeral" ;;
  56. *ty) tenspred "$numeral" ;;
  57. zero) echo ""; #to terminate
  58. esac
  59. }
  60. teenpred() {
  61. #
  62. # Get predecessor of a teen $1
  63. #
  64. local numeral=$1 # number in word form
  65. case $numeral in
  66. thirteen) echo twelve;;
  67. *) echo "$(crunchprefix "$(pred "$(uncrunchprefix "${numeral%teen}")")")teen" ;;
  68. esac
  69. }
  70. tenspred() {
  71. #
  72. # Get predecessor of a multiple of ten $1
  73. #
  74. local numeral=$1 # number in word form
  75. case $numeral in
  76. twenty) echo nineteen;;
  77. *) echo "$(crunchprefix --tens "$(pred "$(uncrunchprefix "${numeral%ty}")")")ty-nine";;
  78. esac
  79. }
  80. crunchprefix() {
  81. #
  82. # Crunch number prefix $1 to its conventional form
  83. #
  84. # ...such as `three` --> `thir`
  85. #
  86. # option `--tens` - multiples of ten are a bit different
  87. #
  88. local numeral # number in word form
  89. local tensop # 'true' if --tens option is active
  90. [ "$1" = --tens ] && { tensop=true; shift; }
  91. numeral=$1
  92. case $numeral in
  93. two) [ -n "$tensop" ] && echo twen || echo "$numeral";;
  94. three) echo thir;;
  95. four) [ -n "$tensop" ] && echo 'for' || echo "$numeral";;
  96. five) echo fif;;
  97. eight) [ -n "$tensop" ] && echo eigh || echo "$numeral";;
  98. *) echo "$numeral" ;;
  99. esac
  100. }
  101. uncrunchprefix() {
  102. #
  103. # Reverse crunchprefix $1
  104. #
  105. local prefix=$1 # numeral crunch-prefix
  106. case $prefix in
  107. twen) echo two;;
  108. thir) echo three;;
  109. 'for') echo four;;
  110. fif) echo five;;
  111. eigh) echo eight;;
  112. *) echo "$prefix";;
  113. esac
  114. }
  115. grammar() {
  116. #
  117. # Apply peculiarities of English grammar to text on stdin
  118. #
  119. local oneBottle=false # 'true' if this line affects the following line
  120. local line # every line
  121. while read -r line; do
  122. line="${line/one more bottles/one more bottle}"
  123. case "$line" in
  124. *"one of those bottles"*) line="$(
  125. [ $oneBottle = true ] \
  126. && echo "${line/one of those bottles/that lone bottle}" \
  127. || echo "$line"
  128. )"
  129. ;;
  130. *"one down"*) line="$(
  131. [ $oneBottle = true ] \
  132. && echo "${line/one down/it down}" \
  133. || echo "$line"
  134. )"
  135. ;;
  136. *bottles*) oneBottle=false;;
  137. *bottle*) oneBottle=true;;
  138. esac
  139. #Some say the twenties should have no hyphen
  140. line="${line/twenty-/twenty }"
  141. echo "$line"
  142. done
  143. }
  144. capitalize() {
  145. #
  146. # Fix capitalization of each line on stdin
  147. #
  148. local line # every line
  149. while read -r line; do
  150. echo -n "${line:0:1}" | tr '[:lower:]' '[:upper:]'
  151. echo "${line#?}"
  152. done
  153. }
  154. punctuate() {
  155. #
  156. # Add punctuation to each line on stdin
  157. #
  158. local line # every line
  159. while read -r line; do
  160. case "${line}" in
  161. [Ii]f*) echo "${line},";;
  162. '') echo;;
  163. *) echo "${line}.";;
  164. esac
  165. done
  166. }
  167. verse() {
  168. #
  169. # Write one verse with number $1
  170. #
  171. local nb=$1 # numeral
  172. echo "$nb bottles of beer on the wall"
  173. echo "$nb bottles of beer"
  174. if [ "$nb" = zero ]; then
  175. echo "Go to the store and buy some more"
  176. nb=ninety-nine
  177. else
  178. echo "$TransitionLine"
  179. nb=$(pred "$nb")
  180. fi
  181. echo "$nb bottles of beer on the wall"
  182. }
  183. poeticize() {
  184. #
  185. # Make text on stdin nice
  186. #
  187. local first # first word of a line
  188. local rest # remainder of a line
  189. local syl # number of syllables in the first word
  190. while read -r first rest; do
  191. case "$rest" in
  192. *beer*)
  193. first=${first/zero/no}
  194. syl=$(syllables "${first% *}")
  195. case $syl in #improve meter
  196. 1|2) echo "$first more $rest";;
  197. *) echo "$first $rest" ;;
  198. esac
  199. ;;
  200. *) echo "$first $rest"
  201. esac
  202. done
  203. }
  204. syllables() {
  205. #
  206. # Estimate number of syllables in word $1
  207. #
  208. local word=$1 # word (numeral) to do the estimation on
  209. local n=1
  210. case $word in
  211. eleven) n=2;; #sounds better if not considered 3
  212. *teen) n=2;;
  213. *ty) n=2;;
  214. *-*) n=3;; #don't care about more than 3
  215. esac
  216. case $word in
  217. *seven*) ((n = n+1)) ;;
  218. esac
  219. echo "$n"
  220. }
  221. main() {
  222. local TransitionLine # transition line between verses
  223. local togo=$NN_STARTING_AMOUNT # bottles to go
  224. local mode=$NN_BOTTLES__MODE # transition mode
  225. while true; do case $1 in
  226. --careless) mode=careless; shift ;;
  227. *) break ;;
  228. esac done
  229. TransitionLine=$(
  230. case $mode in
  231. standard) echo "Take one down and pass it around" ;;
  232. careless) echo "If one of those bottles should happen to fall" ;;
  233. esac
  234. )
  235. togo=ninety-nine
  236. while [ -n "$togo" ]; do
  237. verse "$togo"
  238. echo
  239. togo=$(pred "$togo")
  240. done \
  241. | poeticize \
  242. | grammar \
  243. | punctuate \
  244. | capitalize
  245. }
  246. main "$@"