summaryrefslogtreecommitdiff
path: root/localwebstats
blob: a291e99d6a8c3c1f1007ce3c8942b2f003c61965 (plain)
  1. #!/bin/bash
  2. #
  3. # /usr/local/sbin/localwebstats
  4. # Copyright 2001-2002 Jonas Smedegaard <dr@jones.dk>
  5. #
  6. # $Id: localwebstats,v 1.32 2004-05-11 10:20:58 jonas Exp $
  7. #
  8. # Webstats maintenance script
  9. #
  10. # Example config file (/etc/local/webstats.conf).
  11. #
  12. # --- CUT --- CUT --- CUT ---
  13. # #!/bin/sh
  14. #
  15. # # * Websites are in /home/<uid>/websites/<fqdn>
  16. # # * Apache httpd.conf has this line added:
  17. # # Include /etc/apache/vhosts.d
  18. # # * Apache use /usr/local/bin/parselog into /var/log/apache-vhosts/
  19. # # * /usr/lib/apache/suexec is recompiled using /usr/local/bin/make-suexec-for-home
  20. # # * Each webhost has apache config in /etc/apache/vhosts.d/<fqdn>
  21. # # * /etc/apache/vhosts.d/<fqdn> has hints about host- and domain-part of fqdn:
  22. # # # webstats: hostname: <hostname>
  23. # # # webstats: domainname: <domainname>
  24. #
  25. # #WEBALIZER_OPTIONS="-Q"
  26. #
  27. # LOGROOT='/var/log/apache-vhosts'
  28. # WEBROOT='/home'
  29. #
  30. # function statsdir() { echo /home/jonas/websites/stats.$(dnsdomainname)/$3; }
  31. # function webdirs() { find /etc/apache/vhosts.d/ -type f -exec egrep '^#\W*webstats:' '{}' -l ';' | xargs basename; }
  32. # function host() { cat /etc/apache/vhosts.d/$3 | egrep '#\W*webstats:\W*hostname:\W*[\.[:alnum:]]+\W*$' | sed 's/^.*:\W\([\.[:alnum:]]\+\)\W*$/\1/'; }
  33. # function domain() { cat /etc/apache/vhosts.d/$3 | egrep '#\W*webstats:\W*domainname:\W*[\.[:alnum:]]+\W*$' | sed 's/^.*:\W\([\.[:alnum:]]\+\)\W*$/\1/'; }
  34. #
  35. # --- CUT --- CUT --- CUT ---
  36. #
  37. # TODO: Run as non-provoleged user
  38. #
  39. # halt on errors
  40. set -e
  41. function usage() {
  42. echo "Usage: $(basename $0) init|update|prelogrotate|postlogrotate|ignore <website> [<website>...]"
  43. echo " If no website is given, all are attempted"
  44. echo " Tip: Automagically runs when symlinked to /etc/cron.{daily,weekly,monthly}/"
  45. exit 1
  46. }
  47. function exit1() {
  48. echo "Error: $1"
  49. echo "Exiting..."
  50. exit 1
  51. }
  52. # automagically configure when run from cron dirs
  53. case $(dirname $0) in
  54. /etc/cron.daily)
  55. stamp=update
  56. ;;
  57. /etc/cron.weekly)
  58. stamp=ignore
  59. ;;
  60. /etc/cron.monthly)
  61. stamp=ignore
  62. ;;
  63. *)
  64. stamp=$1
  65. shift || usage
  66. ;;
  67. esac
  68. case "$stamp" in
  69. init|update|prelogrotate|postlogrotate|ignore)
  70. ;;
  71. *)
  72. usage
  73. ;;
  74. esac
  75. if [ "$stamp" = "ignore" ]; then
  76. [ $DEBUG ] && echo "Asked to ignore - exiting silently..."
  77. exit 0
  78. fi
  79. ROOT=1
  80. user=www-data
  81. group=www-data
  82. LOGROOT=/var/log/apache-vhosts
  83. WEBROOT=/var/www
  84. # Options: $1=LOGROOT, $2=WEBROOT, $3=WEBSITE
  85. function statsdir() { echo $2/VIRTUAL/stats.$(dnsdomainname)/www/$3; }
  86. function webdirs() { find $1 -type d -mindepth 1 -maxdepth 1 | grep '\.*\.' | sed 's!$1!!'; }
  87. #function logfiles() { $(ls -r $LOGDIR/*-access*.gz) $(ls -r $LOGDIR/access*.??.gz) $(ls -r $LOGDIR/access*.?.gz) $(ls -r $LOGDIR/access*.?); }
  88. function logcontentresolved() { for file in $(find $1/$3 -name '????.??.00.gz' -type f -mindepth 1 -maxdepth 1 -follow | sort); do zcat $file; done; for file in $(find $1/$3 -name '????.??.00' -type f -mindepth 1 -maxdepth 1 -follow | sort); do cat $file; done; }
  89. function logcontent() { for file in $(find $1/$3 -name '????.??.??.gz' ! -name '*00.gz' -type f -mindepth 1 -maxdepth 1 -follow | sort); do zcat $file; done; for file in $(find $1/$3 -name '????.??.??' ! -name '*00' -type f -mindepth 1 -maxdepth 1 -follow | sort); do cat $file; done; }
  90. function host() { cat $2/VIRTUAL/$3/hostname || exit1 "Unable to get hostname for virtual host."; }
  91. function domain() { cat $2/VIRTUAL/$3/domainname || exit1 "Unable to get domainname for virtual host."; }
  92. function analog_cfg() { echo /etc/analog_$3.conf; }
  93. function rmagic_cfg() { echo /etc/rmagic/rmagic_$3.conf; }
  94. function webalizer_cfg() { echo /etc/webalizer_$3.conf; }
  95. function modlogan_cfg() { echo /etc/modlogan/modlogan_$3.conf; }
  96. function pre_init() { true; }
  97. function post_init() { statsdir="`statsdir $1 $2 $3`"; test -f $statsdir/../COMMON/index.html && cp $statsdir/../COMMON/index.html $statsdir/; }
  98. function pre_update() { true; }
  99. function post_update() { true; }
  100. # The above can be overridden
  101. LOCALCONFIG=/etc/local/webstats.conf
  102. . $LOCALCONFIG || echo "WARNING: Unable to read config file $LOCALCONFIG"
  103. # variables and functions too boring to be configurable
  104. JDRESOLVE_BIN="/usr/bin/jdresolve"
  105. JDRESOLVE_OPTIONS="-r -t 5"
  106. JDRESOLVE_DB="/var/cache/jdresolve/hosts.db"
  107. JDRESOLVE_EXPIRY="48"
  108. LOGRESOLVE_BIN="/usr/sbin/logresolve"
  109. ANALOG_BIN="/usr/bin/analog"
  110. RMAGIC_BIN="/usr/bin/rmagic"
  111. WEBALIZER_BIN="/usr/bin/webalizer"
  112. MODLOGAN_BIN="/usr/bin/modlogan"
  113. AWSTATS_BIN="/usr/lib/cgi-bin/awstats.pl"
  114. function awstats_setlog() { sed -e "s!^\(LogFile=\).*\$!\\1$2!" $1 > $1.tmp; mv $1.tmp $1; }
  115. # Website/independent checks
  116. test -d $WEBROOT || exit1 "Webroot \"$WEBROOT\" doesn't exist"
  117. test -d $LOGROOT || exit1 "Logroot \"$LOGROOT\" doesn't exist"
  118. if [ -x $JDRESOLVE_BIN -a -n "$JDRESOLVE_DB" ]; then
  119. JDRESOLVE_OPTIONS="$JDRESOLVE_OPTIONS --database=\"$JDRESOLVE_DB\""
  120. test -d $(dirname "$JDRESOLVE_DB") || exit1 "Cache dir for jdresolve doesn't exist"
  121. if [ -n "$JDRESOLVE_EXPIRY" ]; then
  122. jdresolve $JDRESOLVE_OPTIONS --expiredb="$JDRESOLVE_EXPIRY"
  123. fi
  124. fi
  125. # Generate stats for websites from stdin or all default sites
  126. WEBSITES=$@
  127. if [ "$WEBSITES" = "" ]; then
  128. WEBSITES=$(webdirs $LOGROOT $WEBROOT $WEBSITE)
  129. fi
  130. for WEBSITE in $WEBSITES; do
  131. STATSDIR=$(statsdir $LOGROOT $WEBROOT $WEBSITE) || exit1 "Unable to resolve STATSDIR."
  132. # FIXME test -d $STATSDIR/.. || exit1 "Directory above STATSDIR doesn't exist."
  133. ANALOG_CFG=$(analog_cfg $LOGROOT $WEBROOT $WEBSITE) || exit1 "Unable to resolve ANALOG_CFG."
  134. RMAGIC_CFG=$(rmagic_cfg $LOGROOT $WEBROOT $WEBSITE) || exit1 "Unable to resolve RMAGIC_CFG."
  135. WEBALIZER_CFG=$(webalizer_cfg $LOGROOT $WEBROOT $WEBSITE) || exit1 "Unable to resolve WEBALIZER_CFG."
  136. MODLOGAN_CFG=$(modlogan_cfg $LOGROOT $WEBROOT $WEBSITE) || exit1 "Unable to resolve MODLOGAN_CFG."
  137. if [ $stamp = "init" ]; then
  138. [ $DEBUG ] && echo "Execute $WEBSITE PRE_INIT"
  139. pre_init $LOGROOT $WEBROOT $WEBSITE || exit1 "Error executing PRE_INIT."
  140. fi
  141. if [ $stamp = "update" ]; then
  142. [ $DEBUG ] && echo "Execute $WEBSITE PRE_UPDATE"
  143. pre_update $LOGROOT $WEBROOT $WEBSITE || exit1 "Error executing PRE_UPDATE."
  144. fi
  145. HOST=$(host $LOGROOT $WEBROOT $WEBSITE) || exit1 "Unable to get hostname for virtual host."
  146. DOMAIN=$(domain $LOGROOT $WEBROOT $WEBSITE) || exit1 "Unable to get domainname for virtual host."
  147. ANALOG_OPTIONS="-G +g/etc/analog.conf +g$ANALOG_CFG +A -a"
  148. RMAGIC_OPTIONS=""
  149. WEBALIZER_OPTIONS="-c /etc/webalizer.conf -c $WEBALIZER_CFG -o $STATSDIR/webalizer -f"
  150. MODLOGAN_OPTIONS="-c $MODLOGAN_CFG"
  151. MODLOGAN_TAIL="2>&1 | egrep -v '^(modlogan [\.0-9]+|main\.c\.[0-9]+ \(main\): startup - finished|[\. \[\]]*[0-9]*|.+: No such file or directory, first run \?)$' || true if [ $? \< 2 ]"
  152. AWSTATS_OPTIONS="-update"
  153. FQDN_ESC=`echo "$HOST.$DOMAIN" | sed -e 's/\./\\\./g'` # needed for awstats config
  154. if [ $DEBUG ]; then
  155. echo "Making stats for $WEBSITE in $STATSDIR:"
  156. JDRESOLVE_OPTIONS="$JDRESOLVE_OPTIONS -p"
  157. ANALOG_OPTIONS="$ANALOG_OPTIONS +q"
  158. RMAGIC_OPTIONS="$RMAGIC_OPTIONS"
  159. WEBALIZER_OPTIONS="$WEBALIZER_OPTIONS -T"
  160. MODLOGAN_OPTIONS="$MODLOGAN_OPTIONS"
  161. MODLOGAN_TAIL=""
  162. AWSTATS_OPTIONS="$AWSTATS_OPTIONS -showsteps"
  163. else
  164. JDRESOLVE_OPTIONS="$JDRESOLVE_OPTIONS -n"
  165. ANALOG_OPTIONS="$ANALOG_OPTIONS -q"
  166. RMAGIC_OPTIONS="$RMAGIC_OPTIONS -statistics_Verbose=NONE"
  167. WEBALIZER_OPTIONS="$WEBALIZER_OPTIONS -Q"
  168. MODLOGAN_OPTIONS="$MODLOGAN_OPTIONS"
  169. MODLOGAN_TAIL="$MODLOGAN_TAIL"
  170. AWSTATS_OPTIONS="$AWSTATS_OPTIONS"
  171. fi
  172. if [ $stamp = "init" ]; then
  173. [ $DEBUG ] && echo "$WEBSITE: Purge STATSDIR"
  174. rm -rf $STATSDIR
  175. mkdir $STATSDIR
  176. fi
  177. LOGDATARESOLVED="$STATSDIR/rawlog_old.txt"
  178. LOGDATA="$STATSDIR/rawlog_new.txt"
  179. LOGDATATMP="$STATSDIR/rawlog_incoming.txt"
  180. touch $LOGDATARESOLVED $LOGDATA $LOGDATATMP || exit1 "Couldn't touch LOGDATA files."
  181. #FIXME run init if folders doesn't exist
  182. if [ $stamp = "init" ]; then
  183. logcontentresolved $LOGROOT $WEBROOT $WEBSITE >> $LOGDATARESOLVED
  184. if [ -x $ANALOG_BIN ]; then
  185. [ $DEBUG ] && echo "$WEBSITE: Create/update analog config"
  186. echo "\
  187. # NB! This file is automatically generated. Do not edit directly!
  188. # Instead, put additions/overrides in $ANALOG_CFG.local
  189. HOSTNAME $HOST.$DOMAIN
  190. HOSTURL http://$HOST.$DOMAIN/
  191. BASEURL http://$HOST.$DOMAIN
  192. LANGUAGE DANISH
  193. "\
  194. > $ANALOG_CFG
  195. [ -s $ANALOG_CFG.local ] && cat $ANALOG_CFG.local >> $ANALOG_CFG
  196. [ -d $STATSDIR/analog ] || mkdir $STATSDIR/analog
  197. [ $DEBUG ] && echo "$WEBSITE: Create initial analog stats"
  198. rm -f $STATSDIR/analog/cache.data
  199. $ANALOG_BIN $ANALOG_OPTIONS -C"LOGFILE none" -C"LOGFILE $LOGDATARESOLVED" -C"CACHEOUTFILE $STATSDIR/analog/cache.data" -C"OUTFILE $STATSDIR/analog/index.html"
  200. fi
  201. if [ -x $RMAGIC_BIN -a -x $ANALOG_BIN ]; then
  202. [ $DEBUG ] && echo "$WEBSITE: Create/update Report Magic config"
  203. echo "\
  204. # NB! This file is automatically generated. Do not edit directly!
  205. # Instead, put additions/overrides in $RMAGIC_CFG.local
  206. [statistics]
  207. File_In = $STATSDIR/rmagic/report.dat
  208. Frame_File_Out = $STATSDIR/rmagic/index.html
  209. Language = en
  210. [reports]
  211. File_Out = $STATSDIR/rmagic/
  212. [QUICK]
  213. Rows = ALL
  214. [navigation]
  215. File_Out = navfile.html
  216. "\
  217. > $RMAGIC_CFG
  218. [ -s $RMAGIC_CFG.local ] && $RMAGIC_OPTIONS="$RMAGIC_OPTIONS -statistics_Include=$RMAGIC_CFG.local"
  219. [ -d $STATSDIR/rmagic ] || mkdir $STATSDIR/rmagic
  220. [ $DEBUG ] && echo "$WEBSITE: Create initial Report Magic stats"
  221. $ANALOG_BIN $ANALOG_OPTIONS -C"LOGFILE none" -C"CACHEFILE $STATSDIR/analog/cache.data" -C"LANGUAGE ENGLISH" -C"OUTPUT COMPUTER" -C"OUTFILE $STATSDIR/rmagic/report.dat"
  222. $RMAGIC_BIN $RMAGIC_OPTIONS $RMAGIC_CFG
  223. fi
  224. if [ -x $WEBALIZER_BIN ]; then
  225. [ $DEBUG ] && echo "$WEBSITE: Create/update Webalizer config"
  226. echo "\
  227. # NB! This file is automatically generated. Do not edit directly!
  228. # Instead, put additions/overrides in $WEBALIZER_CFG.local
  229. HostName $HOST.$DOMAIN
  230. HideSite *$DOMAIN
  231. HideReferrer $DOMAIN/
  232. "\
  233. > $WEBALIZER_CFG
  234. [ -s $WEBALIZER_CFG.local ] && cat $WEBALIZER_CFG.local >> $WEBALIZER_CFG
  235. [ -d $STATSDIR/webalizer ] || mkdir $STATSDIR/webalizer
  236. [ $DEBUG ] && echo "$WEBSITE: Create initial Webalizer stats"
  237. cat $LOGDATARESOLVED | $WEBALIZER_BIN $WEBALIZER_OPTIONS -N 0 - || true
  238. fi
  239. if [ -x $MODLOGAN_BIN ]; then
  240. [ $DEBUG ] && echo "$WEBSITE: Create/update ModLogAn config"
  241. echo "\
  242. # NB! This file is automatically generated. Do not edit directly!
  243. # Instead, put additions/overrides in $MODLOGAN_CFG.local
  244. [global]
  245. includepath = /etc/modlogan
  246. include = modlogan.def.conf,global
  247. loadplugin = input_clf
  248. loadplugin = processor_web
  249. loadplugin = output_modlogan
  250. loadplugin = output_template
  251. statedir=$STATSDIR/modlogan
  252. incremental = 0
  253. debug_level = 0
  254. enable_resolver = 0
  255. #read_ahead_limit = 1
  256. var(outputdir, \$statedir)
  257. [processor_web]
  258. include = group.url.conf,group_exploits
  259. include = modlogan.def.conf,processor_web
  260. debug_searchengines = 0
  261. debug_visits = 0
  262. hidereferrer = \"\.${DOMAIN//./\.}/\"
  263. [output_modlogan]
  264. include = modlogan.def.conf, output_modlogan
  265. hostname = $HOST.$DOMAIN
  266. outputdir=\$outputdir
  267. [output_template]
  268. include = modlogan.def.conf, output_template
  269. include = modlogan.def.conf, output_template_reports_web
  270. include = modlogan.def.conf, output_template_menu_web
  271. template_path = /usr/local/share/modlogan/themes/
  272. template_name = basic
  273. variable = HOSTNAME,$HOST.$DOMAIN
  274. variable = CHARSET,iso-8859-1
  275. variable = LANGUAGE,da
  276. hostname = $HOST.$DOMAIN
  277. outputdir=\$outputdir-test
  278. [input_clf]
  279. include = modlogan.def.conf,input_clf
  280. inputfile = -
  281. "\
  282. > $MODLOGAN_CFG
  283. [ -s $MODLOGAN_CFG.local ] && cat $MODLOGAN_CFG.local >> $MODLOGAN_CFG
  284. for dir in $STATSDIR/modlogan $STATSDIR/modlogan-test; do
  285. [ -d $dir ] || mkdir $dir
  286. [ $ROOT ] && chown $user:$group $dir
  287. done
  288. [ $DEBUG ] && echo "$WEBSITE: Create initial ModLogAn stats"
  289. if [ $ROOT ]; then
  290. cat $LOGDATARESOLVED | su $user -c "$MODLOGAN_BIN $MODLOGAN_OPTIONS $MODLOGAN_TAIL"
  291. else
  292. cat $LOGDATARESOLVED | $MODLOGAN_BIN $MODLOGAN_OPTIONS $MODLOGAN_TAIL
  293. fi
  294. fi
  295. if [ -x $AWSTATS_BIN ]; then
  296. [ $DEBUG ] && echo "$WEBSITE: Create/update AWStats config"
  297. echo "\
  298. # NB! This file is automatically generated. Do not edit directly!
  299. # Instead, put additions/overrides in $AWSTATS_CFG.local
  300. LogFile=\"$LOGDATATMP\"
  301. LogFormat=1
  302. DNSLookup=0
  303. DirData=\"$STATSDIR/awstats\"
  304. AllowToUpdateStatsFromBrowser=0
  305. DirCgi=\"http://cgi.jones.dk/cgi-bin\"
  306. DirIcons=\"http://stats.jones.dk/awstats-icon\"
  307. SiteDomain=\"$FQDN_ESC\"
  308. HostAliases=\"$FQDN_ESC\"
  309. Lang=\"dk\"
  310. DirLang=\"/usr/share/awstats/lang\"
  311. DefaultFile=\"index.html\"
  312. SkipHosts=\"\"
  313. SkipFiles=\"\"
  314. ShowLinksOnUrl=1
  315. ShowFlagLinks=0
  316. "\
  317. >/etc/awstats/awstats.$WEBSITE.conf
  318. [ -d $STATSDIR/awstats ] || mkdir $STATSDIR/awstats
  319. [ $DEBUG ] && echo "$WEBSITE: Create initial AWStats stats"
  320. awstats_setlog /etc/awstats/awstats.$WEBSITE.conf $LOGDATARESOLVED
  321. $AWSTATS_BIN -config=$WEBSITE $AWSTATS_OPTIONS -output > $STATSDIR/awstats/index.html
  322. awstats_setlog /etc/awstats/awstats.$WEBSITE.conf $LOGDATATMP
  323. fi
  324. [ $DEBUG ] && echo "$WEBSITE: Compress DNS-resolved logdata"
  325. gzip -9 $LOGDATARESOLVED
  326. fi
  327. if [ -x $JDRESOLVE_BIN ]; then
  328. [ $DEBUG ] && echo "$WEBSITE: DNS-resolve new logdata using jdresolve"
  329. logcontent $LOGROOT $WEBROOT $WEBSITE | $JDRESOLVE_BIN $JDRESOLVE_OPTIONS - >> $LOGDATA
  330. elif [ -x $LOGRESOLVE_BIN ]; then
  331. [ $DEBUG ] && echo "$WEBSITE: DNS-resolve new logdata using logresolve"
  332. logcontent $LOGROOT $WEBROOT $WEBSITE | $LOGRESOLVE_BIN >> $LOGDATA
  333. else
  334. [ $DEBUG ] && echo "$WEBSITE: Merging new logdata without DNS-resolving (no resolver found)"
  335. logcontent $LOGROOT $WEBROOT $WEBSITE >> $LOGDATA
  336. fi
  337. if [ -s $LOGDATA ]; then
  338. if [ -x $ANALOG_BIN ]; then
  339. [ $DEBUG ] && echo "$WEBSITE: Update analog stats"
  340. $ANALOG_BIN $ANALOG_OPTIONS -C"LOGFILE none" -C"CACHEFILE $STATSDIR/analog/cache.data" -C"LOGFILE $LOGDATA" -C"OUTFILE $STATSDIR/analog/index.html"
  341. fi
  342. if [ -x $RMAGIC_BIN -a -x $ANALOG_BIN ]; then
  343. [ $DEBUG ] && echo "$WEBSITE: Update Report Magic stats"
  344. $ANALOG_BIN $ANALOG_OPTIONS -C"LOGFILE none" -C"CACHEFILE $STATSDIR/analog/cache.data" -C"LOGFILE $LOGDATA" -C"LANGUAGE ENGLISH" -C"OUTPUT COMPUTER" -C"OUTFILE $STATSDIR/rmagic/report.dat"
  345. $RMAGIC_BIN $RMAGIC_OPTIONS $RMAGIC_CFG
  346. fi
  347. if [ -x $WEBALIZER_BIN ]; then
  348. [ $DEBUG ] && echo "$WEBSITE: Update Webalog stats"
  349. cat $LOGDATA | $WEBALIZER_BIN $WEBALIZER_OPTIONS -N 0 -
  350. fi
  351. if [ -x $MODLOGAN_BIN ]; then
  352. [ $DEBUG ] && echo "$WEBSITE: Update ModLogAn stats"
  353. if [ $ROOT ]; then
  354. cat $LOGDATA | su $user -c "$MODLOGAN_BIN $MODLOGAN_OPTIONS $MODLOGAN_TAIL"
  355. else
  356. cat $LOGDATA | $MODLOGAN_BIN $MODLOGAN_OPTIONS $MODLOGAN_TAIL
  357. fi
  358. fi
  359. if [ -x $AWSTATS_BIN ]; then
  360. [ $DEBUG ] && echo "$WEBSITE: Update AWStats stats"
  361. awstats_setlog /etc/awstats/awstats.$WEBSITE.conf $LOGDATA
  362. $AWSTATS_BIN -config=$WEBSITE $AWSTATS_OPTIONS -output > $STATSDIR/awstats/index.html
  363. awstats_setlog /etc/awstats/awstats.$WEBSITE.conf $LOGDATATMP
  364. fi
  365. gzip -f9 $LOGDATA
  366. fi
  367. if [ $stamp = "init" ]; then
  368. [ $DEBUG ] && echo "Execute $WEBSITE POST_INIT"
  369. post_init $LOGROOT $WEBROOT $WEBSITE || exit1 "Error executing POST_INIT."
  370. fi
  371. if [ $stamp = "update" ]; then
  372. [ $DEBUG ] && echo "Execute $WEBSITE POST_UPDATE"
  373. post_update $LOGROOT $WEBROOT $WEBSITE || exit1 "Error executing POST_UPDATE."
  374. fi
  375. done