librunscript.sh 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. # librunscript.sh is a library of shell script functions that can be used in
  2. # EC-Earth run scripts.
  3. #
  4. # Usage: source ./librunscript.sh
  5. # Function info writes information to standard out.
  6. #
  7. # Usage: info MESSAGE ...
  8. #
  9. function info()
  10. {
  11. echo "*II* $@"
  12. }
  13. # Function error writes information to standard out and exits the script with
  14. # error code 1.
  15. #
  16. # Usage: error MESSAGE ...
  17. #
  18. function error()
  19. {
  20. echo "*EE* $@"
  21. exit 1
  22. }
  23. # Function has_config checks it's arguments for matches in the $config variable
  24. # and returns true (0) or false (1) accordingly. Optionally, the first argument
  25. # can be either "all" (all arguments must match) or "any" (at least one
  26. # argument must match). If the first argument is neither "all" nor "any", the
  27. # function behaves like "all" was given as the first argument.
  28. #
  29. # Usage: has_config [all|any] ARGS ...
  30. #
  31. # Syntax rules:
  32. #
  33. # The $config variable takes a list of names (typically software components),
  34. # separated by white spaces:
  35. #
  36. # config="foo bar baz" # Specifies three components: 'foo', 'bar', and 'baz'
  37. #
  38. # It is possible to add comma-separated lists of options to components. The
  39. # list is separated from the component by a colon:
  40. #
  41. # config="foo bar:qux,fred baz:plugh" # Adds the options 'qux' and 'fred' to
  42. # # component 'bar' as well as option
  43. # # 'plugh' to component 'baz'
  44. #
  45. # When using the has_config function to check the $config variable, it is
  46. # important to list every component-option pair separately. To check for both
  47. # the 'qux' and 'fred' options of component 'bar' in the above example, use:
  48. #
  49. # has_config bar:qux bar:fred && echo "Got it!"
  50. #
  51. function has_config()
  52. {
  53. # If called without arguments, return false
  54. (( $# )) || return 1
  55. # If $config unset or empty, return false
  56. [[ -z "${config:-}" ]] && return 1
  57. local __c
  58. local __m
  59. # If first argument is "any" then only one of the arguments needs to match
  60. # to return true. Return false otherwise
  61. if [ "$1" == "any" ]
  62. then
  63. shift
  64. for __c in "$@"
  65. do
  66. for __m in $config
  67. do
  68. [[ "$__m" =~ "${__c%:*}" ]] && [[ "$__m" =~ "${__c#*:}" ]] && return 0
  69. done
  70. done
  71. return 1
  72. fi
  73. # If first argument is "all", or neither "any" nor "all", all arguments
  74. # must match to return true. Return false otherwise.
  75. [[ "$1" == "all" ]] && shift
  76. local __f
  77. for __c in "$@"
  78. do
  79. __f=0
  80. for __m in $config
  81. do
  82. [[ "$__m" =~ "${__c%:*}" ]] && [[ "$__m" =~ "${__c#*:}" ]] && __f=1
  83. done
  84. (( __f )) || return 1
  85. done
  86. return 0
  87. }
  88. # Function leap days calculates the number of leap days (29th of Februrary) in
  89. # a time intervall between two dates.
  90. #
  91. # Usage leap_days START_DATE END_DATE
  92. function leap_days()
  93. {
  94. local ld=0
  95. local frstYYYY=$(date -ud "$1" +%Y)
  96. local lastYYYY=$(date -ud "$2" +%Y)
  97. set +e
  98. # Check first year for leap day between start and end date
  99. $(date -ud "${frstYYYY}-02-29" > /dev/null 2>&1) \
  100. && (( $(date -ud "$1" +%s) < $(date -ud "${frstYYYY}-03-01" +%s) )) \
  101. && (( $(date -ud "$2" +%s) > $(date -ud "${frstYYYY}-02-28" +%s) )) \
  102. && (( ld++ ))
  103. # Check intermediate years for leap day
  104. for (( y=(( ${frstYYYY}+1 )); y<=(( ${lastYYYY}-1 )); y++ ))
  105. do
  106. $(date -ud "$y-02-29" > /dev/null 2>&1) && (( ld++ ))
  107. done
  108. # Check last year (if different from first year) for leap day between start
  109. # and end date
  110. (( $lastYYYY > $frstYYYY )) \
  111. && $(date -ud "${lastYYYY}-02-29" > /dev/null 2>&1) \
  112. && (( $(date -ud "$1" +%s) < $(date -ud "${lastYYYY}-03-01" +%s) )) \
  113. && (( $(date -ud "$2" +%s) > $(date -ud "${lastYYYY}-02-28" +%s) )) \
  114. && (( ld++ ))
  115. set -e
  116. echo "$ld"
  117. }
  118. # Helper function for absolute_date_noleap().
  119. #
  120. # Recursively 'fixes' a relative date into an absolute date referenced to
  121. # the "noleap" calendar instead of Julian/Gregorian.
  122. #
  123. # Usage: __noleap_fixer base_date modifier
  124. #
  125. function __noleap_fixer ()
  126. {
  127. local base_date="$1"
  128. local modifier="$2"
  129. # Evaluate the base and the modified date.
  130. local t0=$(date -u -d "${base_date}" +'%Y%m%d %H')
  131. local t1=$(date -u -d "${base_date} ${modifier}" +'%Y%m%d %H')
  132. local nleap
  133. local op
  134. # Compute the number of leap days between the base date and the modified
  135. # date.
  136. if [[ $(date -u -d "${t0}" +%Y%j) -lt $(date -u -d "${t1}" +%Y%j) ]]; then
  137. nleap=$(leap_days "${t0}" "${t1}")
  138. else
  139. nleap=$(leap_days "${t1}" "${t0}")
  140. fi
  141. if [[ ${nleap} -eq 0 ]]; then
  142. # Return the modified date if there are no leap days to account for.
  143. echo "${t1}"
  144. else
  145. # Obtain the modifier symbol (either + or -), this is used to determine
  146. # the direction to apply corrections in.
  147. if echo "${modifier}" | grep '+' 2>&1 > /dev/null; then
  148. op='+'
  149. else
  150. op='-'
  151. fi
  152. if [[ $(date -ud "${t1}" +%m%d) == 0229 ]]; then
  153. # If the modified day is the leap day we shift it by one day in the
  154. # appropriate direction and subtract 1 from the number of leap days
  155. # we need to account for.
  156. t1=$(date -ud "${t1} ${op} 1 day" +'%Y%m%d %H')
  157. nleap=$(( nleap - 1 ))
  158. fi
  159. if [[ ${nleap} -eq 0 ]]; then
  160. # After correction there maybe no leap days left to account for, if
  161. # so we just return the corrected modified date.
  162. echo "${t1}"
  163. else
  164. # Otherwise we recurse, to apply our modification safely accounting
  165. # for leap days.
  166. __noleap_fixer "${t1}" "${op} ${nleap} days"
  167. fi
  168. fi
  169. }
  170. # Convert a relative date to an absolute date assuming the "noleap" calendar.
  171. #
  172. # A relative date is a base date with some modifier, such as
  173. # "2012-01-01 + 2 months".
  174. #
  175. # Usage: absolute_date_noleap date_specifier.
  176. function absolute_date_noleap ()
  177. {
  178. local input_date="$1"
  179. # Split the input into a date part and a modifier part.
  180. local regex='([a-z|A-Z|0-9|-|+|,|:|\s]*)\s*([+|-]\s*[0-9]+\s*(second|minute|hour|day)s{0,1}).*'
  181. local base_date=$(echo "$1" | sed -r "s/${regex}/\1^\2/" | cut -d "^" -f 1)
  182. local modifier=$(echo "$1" | sed -r "s/${regex}/\1^\2/" | cut -d "^" -f 2)
  183. if [ -z "${modifier}" ] || [ "${modifier}" = "${input_date}" ] || \
  184. ! echo "${modifier}" | egrep -i -e 'second|minute|hour|day' 2>&1 >/dev/null; then
  185. # Either there was no modifier or it performs calendar independent
  186. # modifications, and thus we can safely use the normal `date` tool.
  187. date -uRd "${input_date}"
  188. else
  189. # Special care is needed to make sure the modifier is applied within
  190. # the noleap calendar.
  191. date -uRd "$(__noleap_fixer "${base_date}" "${modifier}")"
  192. fi
  193. }
  194. # Functions to compute computationa performance according to CPMIP metrics
  195. #
  196. # cpmip_sypd: Computes Simulated Years Per Day (SYPD)
  197. # Needs two arguments:
  198. # $1 - length of leg in seconds (use $leg_length_sec)
  199. # $2 - run time of leg in seconds (use $(( t2 - t1 )))
  200. function cpmip_sypd ()
  201. {
  202. bc <<< "scale=2 ; $1 / ( $2 * 365 )"
  203. }
  204. # cpmip_chpsy: Computes Core Hours Per Simulated Years (CHPSY)
  205. # Needs three arguments:
  206. # $1 - length of leg in seconds (use $leg_length_sec)
  207. # $2 - run time of leg in seconds (use $(( t2 - t1 )))
  208. # $3 - overall number of cores
  209. function cpmip_chpsy ()
  210. {
  211. bc <<< "scale=0 ; 365 * 24 * $2 * $3 / $1"
  212. }