#!/usr/bin/env bash

# Purpose: Climatology script tailored to CESM'ish monthly input and E3SM output guidelines
# Produces (and optionally regrids) climatological monthly means, seasonal means, annual means, CMIP6-style timeseries, global-mean timeseries, diurnal cycles

# Copyright (C) 2015--present Charlie Zender
# This file is part of NCO, the netCDF Operators. NCO is free software.
# You may redistribute and/or modify NCO under the terms of the 
# 3-Clause BSD License.

# You are permitted to link NCO with the HDF, netCDF, OPeNDAP, and UDUnits
# libraries and to distribute the resulting executables under the terms 
# of the BSD, but in addition obeying the extra stipulations of the 
# HDF, netCDF, OPeNDAP, and UDUnits licenses.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
# See the 3-Clause BSD License for more details.

# The original author of this software, Charlie Zender, seeks to improve
# it with your suggestions, contributions, bug-reports, and patches.
# Please contact the NCO project at http://nco.sf.net or write to
# Charlie Zender
# Department of Earth System Science
# University of California, Irvine
# Irvine, CA 92697-3100

# Prerequisites: Bash, NCO
# Script could use other shells, e.g., dash (Debian default) after rewriting function definitions and loops
# Debug with 'bash -x ncclimo --dbg=dbg_lvl' where 0 <= dbg_lvl <= 5

# Source: https://github.com/nco/nco/tree/master/data/ncclimo
# Documentation: http://nco.sf.net/nco.html#ncclimo
# Additional Documentation:
# HowTo: https://acme-climate.atlassian.net/wiki/display/SIM/Generate%2C+Regrid%2C+and+Split+Climatologies+%28climo+files%29+with+ncclimo+and+ncremap
# E3SM Climatology Requirements: https://acme-climate.atlassian.net/wiki/display/ATM/Climo+Files+-+v0.3+AMIP+runs

# Insta-install:
# scp ~/nco/data/ncclimo zender1@acme1.llnl.gov:bin
# scp ~/nco/data/ncclimo andes.olcf.ornl.gov:bin_andes
# scp ~/nco/data/ncclimo blues.lcrc.anl.gov:bin_blues
# scp ~/nco/data/ncclimo cheyenne.ucar.edu:bin
# scp ~/nco/data/ncclimo chrysalis.lcrc.anl.gov:bin_chrysalis
# scp ~/nco/data/ncclimo compy.pnl.gov:bin
# scp ~/nco/data/ncclimo cooley.alcf.anl.gov:bin
# scp ~/nco/data/ncclimo cori.nersc.gov:bin_cori
# scp ~/nco/data/ncclimo dust.ess.uci.edu:bin
# scp ~/nco/data/ncclimo e3sm.ess.uci.edu:bin
# scp ~/nco/data/ncclimo frazil.ess.uci.edu:bin
# scp ~/nco/data/ncclimo skyglow.ess.uci.edu:bin
# scp ~/nco/data/ncclimo theta.alcf.anl.gov:bin_theta
# scp dust.ess.uci.edu:bin/ncclimo ~/bin
# scp dust.ess.uci.edu:bin/ncclimo ${MY_BIN_DIR}
# scp zender@dust.ess.uci.edu:bin/ncclimo ${MY_BIN_DIR}

# Set script name, directory, PID, run directory
drc_pwd=${PWD}
# Security: Explicitly unset IFS before wordsplitting, so Bash uses default IFS=<space><tab><newline>
unset IFS
# Set these before 'module' command which can overwrite ${BASH_SOURCE[0]}
# NB: dash supports $0 syntax, not ${BASH_SOURCE[0]} syntax
# http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in
spt_src="${BASH_SOURCE[0]}"
[[ -z "${spt_src}" ]] && spt_src="${0}" # Use ${0} when BASH_SOURCE is unavailable (e.g., dash)
while [ -h "${spt_src}" ]; do # Recursively resolve ${spt_src} until file is no longer a symlink
  drc_spt="$( cd -P "$( dirname "${spt_src}" )" && pwd )"
  spt_src="$(readlink "${spt_src}")"
  [[ ${spt_src} != /* ]] && spt_src="${drc_spt}/${spt_src}" # If ${spt_src} was relative symlink, resolve it relative to path where symlink file was located
done
cmd_ln="${spt_src} ${@}"
drc_spt="$( cd -P "$( dirname "${spt_src}" )" && pwd )"
spt_nm=$(basename ${spt_src}) # [sng] Script name (unlike $0, ${BASH_SOURCE[0]} works well with 'source <script>')
spt_pid=$$ # [nbr] Script PID (process ID)

# 20200110 Qi Tang reports his batch scripts on Cori that use netCDF4 map-files fail unless he sets this
if [ -z "${HDF5_USE_FILE_LOCKING}" ]; then
    export HDF5_USE_FILE_LOCKING='FALSE'
fi # HDF5_USE_FILE_LOCKING

# Configure paths at High-Performance Computer Centers (HPCCs) based on ${HOSTNAME}
if [ -z "${HOSTNAME}" ]; then
    if [ -f /bin/hostname ] && [ -x /bin/hostname ]; then
	export HOSTNAME=`/bin/hostname`
    elif [ -f /usr/bin/hostname ] && [ -x /usr/bin/hostname ]; then
	export HOSTNAME=`/usr/bin/hostname`
    fi # !hostname
fi # HOSTNAME
# Default input and output directory is ${DATA}
if [ -z "${DATA}" ]; then
    case "${HOSTNAME}" in 
	andes* | titan* ) DATA="/gpfs/alpine/world-shared/cli115/${USER}" ; ;; # OLCF andes compute nodes named andesNNN, 256 GB/node
	blues* | blogin* | b[0123456789][0123456789][0123456789] ) DATA="/lcrc/project/ACME/${USER}" ; ;; # ANL/LCRC blues compute nodes named bNNN, 36|64 cores|GB/node 
	chrysalis* | chrlogin* | chr-[0123456789][0123456789][0123456789][0123456789] ) DATA="/lcrc/project/ACME/${USER}" ; ;; # ANL/LCRC chrysalis compute nodes named chr-NNNN, 64|256 cores|GB/node 
	*cheyenne* ) DATA="/glade/p/work/${USER}" ; ;; # NCAR cheyenne compute nodes named, e.g., r8i0n8, r5i3n16, r12i5n29 ... 18|(64/128) cores|GB/node (cheyenne login nodes 256 GB)
	compy* ) DATA="/qfs/people/${USER}/data" ; ;; # PNNL compy compute nodes all named nNNNN, 40|192 cores|GB/node (compy login nodes also 192 GB)
	constance* | node* ) DATA='/scratch' ; ;; # PNNL
	cooley* | cc[0123456789][0123456789][0123456789] | mira* ) DATA="/projects/OceanClimate_2/${USER}" ; ;; # ALCF cooley compute nodes named ccNNN, 384 GB/node 
	cori* ) DATA="${SCRATCH}" ; ;; # NERSC cori compute nodes named nidNNNNN (Haswell/KNL) or cmemNN (AMD) with (32/68/64)|(96/128/1024) cores|GB/node (knl/haswell/amd) (login nodes 512 GB)
	theta* ) DATA="/projects/ClimateEnergy_3/${USER}" ; ;; # ALCF theta compute nodes named fxm, 64|192 cores|GB/node 
	* ) DATA='/tmp' ; ;; # Other
    esac # !HOSTNAME
fi # DATA
# 20190423 Speed-up OpenMP processes on Cori KNL Intel builds (and possibly others)
# Environmental settings (e.g., OMP_PROC_BIND=spread or KMP_PROC_BIND=intel) may place all threads on same hardware core
# Problem only known to manifest when multiple instances of NCO are spawned on single node
OMP_PROC_BIND=false

# Ensure batch jobs access correct 'mpirun' (or, with SLURM, 'srun') command, netCDF library, and NCO executables and library
# 20170914 Entire block is identical between ncclimo and ncremap---keep it that way!
# hrd_pth could be a command-line option to control environment if this block placed below getopt() block (not trivial)
# 20190421 Change override default from opt-out to opt-in
# Leave NCO_PATH_OVERRIDE unset or set to 'No' to prevent NCO from executing next block that overrides PATH
# Set NCO_PATH_OVERRIDE to 'Yes' in environment to cause NCO to execute next block and to override PATH:
# export NCO_PATH_OVERRIDE='Yes'
hrd_pth='Yes' # [sng] Hard-code machine-dependent paths/modules if HOSTNAME in database
if [ "${hrd_pth}" = 'Yes' ] && [ "${NCO_PATH_OVERRIDE}" = 'Yes' ]; then
    # If HOSTNAME is not in database, change hrd_pth_fnd to 'No' in case-statement default fall-through
    hrd_pth_fnd='Yes' # [sng] Machine-dependent paths/modules for HOSTNAME found in database
    case "${HOSTNAME}" in 
	acme1* )
	    export PATH='/home/zender1/bin:/usr/local/e3sm_unified/envs/base/envs/e3sm_unified_latest/bin'\:${PATH}
            export LD_LIBRARY_PATH='/home/zender1/lib:/usr/local/e3sm_unified/envs/base/envs/e3sm_unified_latest/lib'\:${LD_LIBRARY_PATH} ; ;;
	andes* )
	    # 20190827: Must guarantee finding mpirun
	    source ${MODULESHOME}/init/sh # 20150607: PMC Ensures find module commands will be found
	    if [ ${spt_nm} = 'ncremap' ]; then
		module load esmf
	    fi # !ncremap
            export PATH='/ccs/home/zender/bin_andes'\:${PATH}
	    export LD_LIBRARY_PATH='/ccs/home/zender/lib_andes:/ccs/proj/cli900/sw/andes/e3sm-unified/base/envs/e3sm_unified_latest/lib'\:${LD_LIBRARY_PATH} ; ;;
	blues* | blogin* | b[0123456789][0123456789][0123456789] )
	    export PATH='/home/zender/bin_blues'\:${PATH}
	    export LD_LIBRARY_PATH='/home/zender/lib_blues'\:${LD_LIBRARY_PATH} ; ;;
	chrysalis* | chrlogin* | chr-[0123456789][0123456789][0123456789][0123456789] )
	    export PATH='/gpfs/fs1/soft/chrysalis/spack/opt/spack/linux-centos8-x86_64/gcc-9.2.0/netcdf-c-4.7.4-a4uk6zy/bin:/home/zender/bin_chrysalis'\:${PATH}
	    export LD_LIBRARY_PATH='/gpfs/fs1/soft/chrysalis/spack/opt/spack/linux-centos8-x86_64/gcc-9.2.0/netcdf-c-4.7.4-a4uk6zy/lib:/home/zender/lib_chrysalis'\:${LD_LIBRARY_PATH} ; ;;
	*cheyenne* )
	    # 20180112: Cheyenne support not yet tested in batch mode
	    if [ ${spt_nm} = 'ncremap' ]; then
		# On cheyenne, module load ncl installs ERWG in /glade/u/apps/ch/opt/ncl/6.4.0/intel/17.0.1/bin (i.e., ${NCARG_ROOT}/bin)
		module load ncl
	    fi # !ncremap
	    if [ -n "${NCARG_ROOT}" ]; then
		export PATH="${PATH}:/glade/u/apps/ch/opt/ncl/6.6.2/gnu/8.3.0/bin"
	    fi # !NCARG_ROOT
            export PATH='/glade/u/home/zender/bin'\:${PATH}
            export LD_LIBRARY_PATH='/glade/u/apps/ch/opt/netcdf/4.6.3/gnu/9.1.0/lib:/glade/u/apps/ch/opt/udunits/2.2.26/gnu/9.1.0/lib:/glade/u/apps/ch/opt/gsl/2.4/gnu/6.3.0/lib:/glade/u/home/zender/lib'\:${LD_LIBRARY_PATH} ; ;;
	compy* )
	    export PATH='/qfs/people/zender/bin'\:${PATH}
	    export LD_LIBRARY_PATH='/qfs/people/zender/lib'\:${LD_LIBRARY_PATH} ; ;;
	cooley* | cc[0123456789][0123456789][0123456789] )
	    # 20160421: Split cooley from mira binary locations to allow for different system libraries
	    # http://www.mcs.anl.gov/hs/software/systems/softenv/softenv-intro.html
	    soft add +mvapich2 
            export PBS_NUM_PPN=12 # Spoof PBS on Soft (which knows nothing about node capabilities)
	    export PATH='/home/zender/bin_cooley'\:${PATH}
	    export LD_LIBRARY_PATH='/home/zender/lib_cooley'\:${LD_LIBRARY_PATH} ; ;;
	cori* )
	    # 20191023 NERSC incident INC0143724: ncl/6.5.0 provides broken ERWG, use 6.4.0 
	    if [ ${spt_nm} = 'ncremap' ]; then
		module load ncl/6.5.0
		MOAB_ROOT=/project/projectdirs/e3sm/software/moab
		TEMPESTREMAP_ROOT=/project/projectdirs/e3sm/software/tempestremap
	    fi # !ncremap
	    if [ -n "${NCARG_ROOT}" ]; then
		export PATH="${PATH}:${NCARG_ROOT}/bin"
	    fi # !NCARG_ROOT
	    if [ -n "${MOAB_ROOT}" ]; then
		export PATH="${PATH}:${MOAB_ROOT}/bin"
	    fi # !MOAB_ROOT
	    if [ -n "${TEMPESTREMAP_ROOT}" ]; then
		export PATH="${PATH}:${TEMPESTREMAP_ROOT}/bin"
	    fi # !TEMPESTREMAP_ROOT
	    export PATH='/global/homes/z/zender/bin_cori'\:${PATH}
            export LD_LIBRARY_PATH='/global/homes/z/zender/lib_cori'\:${LD_LIBRARY_PATH} ; ;;
	mira* )
	    export PATH='/home/zender/bin_mira'\:${PATH}
	    export LD_LIBRARY_PATH='/soft/libraries/netcdf/current/library:/home/zender/lib_mira'\:${LD_LIBRARY_PATH} ; ;;
	theta* )
	    export PATH='/opt/cray/pe/netcdf/4.6.1.2/gnu/7.1/bin'\:${PATH}
	    export LD_LIBRARY_PATH='/opt/cray/pe/netcdf/4.6.1.2/gnu/7.1/lib'\:${LD_LIBRARY_PATH} ; ;;
	titan* )
	    source ${MODULESHOME}/init/sh # 20150607: PMC Ensures find module commands will be found
	    module load gcc
	    if [ ${spt_nm} = 'ncremap' ]; then
		# 20170831: Use module load ncl (6.3.0 lacks ERWG)
		module load ncl # 20170916 OK
	    fi # !ncremap
	    if [ -n "${NCARG_ROOT}" ]; then
		export PATH="${PATH}:${NCARG_ROOT}/bin"
	    fi # !NCARG_ROOT
            export PATH='/ccs/home/zender/bin_titan'\:${PATH}
	    export LD_LIBRARY_PATH='/opt/cray/netcdf/4.4.1.1/GNU/49/lib:/sw/xk6/udunits/2.1.24/sl_gcc4.5.3/lib:/ccs/home/zender/lib_titan'\:${LD_LIBRARY_PATH} ; ;;
	* ) # Default fall-through
	    hrd_pth_fnd='No' ; ;;
    esac # !HOSTNAME
fi # !hrd_pth && !NCO_PATH_OVERRIDE

# Test cases ($DATA/[grids/maps] refers to ~zender/data/[grids/maps] on Charlie's test machines)
# Production usage:
# ncclimo -c B1850C5_ne30_v0.4 -s 1 -e 59 -i /gpfs/alpine/cli115/proj-shared/mbranst/B1850C5_ne30_v0.4/atm/hist -o ${DATA}/ne30/clm
# ncclimo -c b1850c5_m2a -s 1 -e 127 -i /gpfs/alpine/cli115/proj-shared/b1850_acmev0_highres/atm/hist/fullfiles -o ${DATA}/ne120/clm

# Incremental climo testing:
# ncclimo -v FSNT,AODVIS -c famipc5_ne30_v0.3_00003 -s 1980 -e 1981 -i ${DATA}/ne30/raw -o ${DATA}/ne30/prv -r ${DATA}/maps/map_ne30np4_to_fv129x256_aave.20150901.nc
# ncclimo -v FSNT,AODVIS -c famipc5_ne30_v0.3_00003 -s 1982 -e 1983 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -r ${DATA}/maps/map_ne30np4_to_fv129x256_aave.20150901.nc -x ${DATA}/ne30/prv -X ${DATA}/ne30/xtn -S 1980
# Binary climo testing:
# ncclimo -v FSNT,AODVIS -c famipc5_ne30_v0.3_00003 -S 1980 -E 1981 -x ${DATA}/ne30/prv -s 1982 -e 1983 -i ${DATA}/ne30/clm -X ${DATA}/ne30/xtn

# Annual climo testing:
# ncclimo -C ann -m cism -h h -c b.e10.BG20TRCN.f09_g16.002 -s 1851 -e 2006 -i /gpfs/alpine/cli115/proj-shared/4ue/data/for_charlie -o ${DATA}/ne30/clm
# ncclimo -C ann -m cism -h h -c b.e10.BG20TRCN.f09_g16.002 -s 1851 -e 1852 -i /gpfs/alpine/cli115/proj-shared/4ue/data/for_charlie -o ${DATA}/ne30/clm > ~/ncclimo.out 2>&1 &
# ncclimo -C ann -m mpas -s 1851 -e 1852 -i ${DATA}/hdf/mpasLIoutput.nc -o ${DATA}/mpas/clm > ~/ncclimo.out 2>&1 &

# High-frequency climos:
# drc_in=${DATA}/ne30/raw
# caseid=famipc5_ne30_v0.3_00007.cam.h4 # 3-hour resolution
# cd ${drc_in};ls ${caseid}*.nc | ncclimo --var=PRECT --clm_md=hfc --caseid=hfc --yr_srt=2001 --yr_end=2002 --drc_out=${HOME} --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc # Multi-file input
# ncclimo --var=PRECT --clm_md=hfc --caseid=hfc --yr_srt=2001 --yr_end=2002 --drc_out=${HOME} --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc ~/PRECT_200101_200212.nc # Single file input

# Debugging and Benchmarking:
# ncclimo -v FSNT,AODVIS,TREFHT -c 20180129.DECKv1b_piControl.ne30_oEC.edison -s 1 -e 2 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -O ${DATA}/ne30/rgr -r ${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc
# ncclimo --var_lst=FSNT,AODVIS,TREFHT --caseid=20180129.DECKv1b_piControl.ne30_oEC.edison --yr_srt=1 --yr_end=2 --drc_in=${DATA}/ne30/raw --drc_out=${DATA}/ne30/clm --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc
# ncclimo -v TOTEXTTAU -c merra2_198001.nc4 -s 1980 -e 2015 -i ${DATA}/merra2/raw -o ${DATA}/merra2/clm
# ncclimo > ~/ncclimo.out 2>&1 &
# ncclimo -c B1850C5e1_ne30 -s 2 -e 199 > ~/ncclimo.out 2>&1 &
# ncclimo -c ne30_gx1.B1850c5d -s 6 -e 7 > ~/ncclimo.out 2>&1 &
# ncclimo -d 2 -v FSNT -m cam2 -c essgcm14 -s 1 -e 20 -i ${DATA}/essgcm14 -o ${DATA}/anl > ~/ncclimo.out 2>&1 &
# MPAS: Prior to running ncclimo on MPAS output, annotate missing values of input with, e.g.,
# for fl in `ls hist.*` ; do
#  ncatted -O -t -a _FillValue,,o,d,-9.99999979021476795361e+33 ${fl}
# done
# New MPAS filename conventions (as of ~201612):
# ncclimo --dec_md=sdd --vars=timeMonthly_avg_iceAreaCell --yr_srt=2 --yr_end=3 --mdl=mpasseaice --d2f --drc_in=/p/user_pub/work/E3SM/1_0/piControl/1deg_atm_60-30km_ocean/sea-ice/native/model-output/mon/ens1/v1 --map=${DATA}/maps/map_oEC60to30v3_to_cmip6_180x360_aave.20181001.nc --drc_out=${DATA}/ne30/clm --drc_rgr=${DATA}/ne30/rgr > ~/ncclimo.out 2>&1 &
# ncclimo --dec_md=sdd --vars=timeMonthly_avg_activeTracers_temperature --yr_srt=2 --yr_end=3 --mdl=mpaso --d2f --dpt_fl=${DATA}/grids/mpas_refBottomDepth_60lyr.nc --drc_in=/p/user_pub/work/E3SM/1_0/piControl/1deg_atm_60-30km_ocean/ocean/native/model-output/mon/ens1/v1 --map=${DATA}/maps/map_oEC60to30v3_to_cmip6_180x360_aave.20181001.nc --drc_out=${DATA}/ne30/clm --drc_rgr=${DATA}/ne30/rgr > ~/ncclimo.out 2>&1 &
# Old MPAS filename conventions (until ~201609)::
# ncclimo -v temperature -c hist -s 2 -e 3 -m ocn -i /gpfs/alpine/cli112/proj-shared/golaz/ACME_simulations/20160121.A_B2000ATMMOD.ne30_oEC.titan.a00/run -r ${DATA}/maps/map_oEC60to30_to_t62_bilin.20160301.nc -o ${DATA}/mpas/clm > ~/ncclimo.out 2>&1 &
# ncclimo -v iceAreaCell -c hist -s 2 -e 3 -m ice -i /gpfs/alpine/cli112/proj-shared/golaz/ACME_simulations/20160121.A_B2000ATMMOD.ne30_oEC.titan.a00/run -r ${DATA}/maps/map_oEC60to30_to_t62_bilin.20160301.nc -o ${DATA}/mpas/clm > ~/ncclimo.out 2>&1 &
# Split pipe:
# cd ${DATA}/ne30/raw;ls *000[12]-??*.nc | ncclimo --dbg=1 -s 1 -e 2 --var=FSNT,AODVIS --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc --drc_out=${DATA}/ne30/clm
# Split redirection:
# cd ${DATA}/ne30/raw;ls *000[12]-??*.nc > ~/foo;ncclimo --dbg=1 -s 1 -e 2 --var=FSNT,AODVIS --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc --drc_out=${DATA}/ne30/clm < ~/foo
# Split stdin switch:
# cd ${DATA}/ne30/raw;ls *000[12]-??*.nc | ncclimo --dbg=1 --stdin -s 1 -e 2 --var=FSNT,AODVIS --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc --drc_out=${DATA}/ne30/clm
# Split positional arguments:
# ncclimo --dbg=1 -s 1 -e 1 --var=FSNT,AODVIS,T --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc --drc_out=${DATA}/ne30/clm ${DATA}/ne30/raw/20180129.DECKv1b_piControl.ne30_oEC.edison.cam.h0.0001-??.nc
# Split directory:
# ncclimo --dbg=1 --ypf=50 -s 1 -e 2 --var=FSNT,AODVIS --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc --drc_in=${DATA}/ne30/raw --drc_out=${DATA}/ne30/clm
# Split global mean
# cd ${DATA}/ne30/raw;ls *000[12]-??*.nc | ncclimo --dbg=1 --glb_avg -s 1 -e 2 --var=FSNT,AODVIS --drc_out=${DATA}/ne30/clm
# Split all:
# cd ${DATA}/ne30/raw;ls *000[12]-??*.nc | ncclimo --dbg=1 -s 1 -e 2 --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc --drc_out=${DATA}/ne30/clm
# Split ELM:
# ncclimo --dbg=1 -s 2000 -e 2000 --var=FSDS,TBOT --drc_out=${DATA}/ne30/clm ${DATA}/ne30/raw/F_acmev03_enso_camse_clm45bgc_ne30_co2cycle.clm2.h0.2000-??.nc
# Split vertical:
# cd ${DATA}/ne30/raw;ls *000[12]-??*.nc | ncclimo --dbg=1 -s 1 -e 2 --var=T --vrt=${DATA}/grids/vrt_prs_ncep_L17.nc --drc_out=${DATA}/ne30/clm
# cd ${DATA}/ne30/raw;ls *000[12]-??*.nc | ncclimo --dbg=1 -s 1 -e 2 --var=T --vrt=${DATA}/grids/vrt_prs_ncep_L17.nc --vrt_xtr=mss_val --drc_out=${DATA}/ne30/clm # Missing value interpolation
# Split production:
# cd /scratch2/scratchdirs/golaz/ACME_simulations/20161117.beta0.A_WCYCL1850S.ne30_oEC_ICG.edison/run;ls *cam.h0.000[1-9]* | ncclimo --dbg=1 --yr_srt=1 --yr_end=9 --var=FSNT,AODVIS,T --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc --drc_out=${DATA}/ne30/clm > ~/ncclimo.out 2>&1 &
# cd /scratch2/scratchdirs/golaz/ACME_simulations/20161117.beta0.A_WCYCL1850S.ne30_oEC_ICG.edison/run;ls *cam.h0.0[012]??* | ncclimo --dbg=1 --yr_srt=1 --yr_end=250 --var=FSNT,AODVIS,T --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc --drc_out=${DATA}/ne30/clm > ~/ncclimo.out 2>&1 &
# Daily pipe:
# cd ${DATA}/ne30/raw;ls *h1*.nc | ncclimo --dbg=1 --job_nbr=8 --caseid=famipc5_ne30_v0.3_00007 --clm_md=dly -s 2001 -e 2002 --var=PRECT,TREFHT --drc_out=${DATA}/ne30/clm > ~/ncclimo.out 2>&1 &
# Diurnal pipe:
# cd ${DATA}/ne30/raw;ls *h4*.nc | ncclimo --dbg=1 --job_nbr=8 --caseid=famipc5_ne30_v0.3_00007 --clm_md=dly --tpd=8 -s 2001 -e 2002 --var=PRECT,TS,FSDS --drc_out=${DATA}/ne30/clm > ~/ncclimo.out 2>&1 &
# SGS:
# ncclimo -d 1 -v TBOT,FSDS -m clm2 -c essgcm14 -s 1 -e 1 -i ${DATA}/essgcm14 -o ${DATA}/ne30/rgr --map=${DATA}/maps/map_t42_to_fv129x256_aave.20150901.nc --sgs_frc=${DATA}/grids/clm2_landfrac_t42.nc/landfrac 
# ncclimo -d 1 -v TBOT,FSDS -m clm2 -c F_acmev03_enso_camse_clm45bgc_ne30_co2cycle -s 2000 -e 2000 -i ${DATA}/ne30/raw -o ${DATA}/ne30/rgr --map=${DATA}/maps/map_ne30np4_to_cmip6_180x360_aave.20181001.nc --sgs_frc=${DATA}/grids/elm_landfrac_ne30.nc/landfrac 
# Seasons:
# ncclimo -d 1 --csn_lst=mam,jja,son,djf,jfm,on -v FSNT -c 20180129.DECKv1b_piControl.ne30_oEC.edison -s 1 -e 2 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -r ${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc
# ncclimo -d 1 --csn_lst=jfm,jas,ann -v FSNT -c 20180129.DECKv1b_piControl.ne30_oEC.edison -s 1 -e 2 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -r ${DATA}/maps/map_ne30np4_to_cmip6_180x360_nco.20190601.nc
# Compy Jobs or MPI:
# ncclimo -c 20180215.DECKv1b_H1.ne30_oEC.edison -s 2010 -e 2014 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -O ${DATA}/ne30/rgr -r ${DATA}/maps/map_ne30np4_to_cmip6_180x360_aave.20181001.nc # ne30 climo on 1 node in full-background (12 jobs) mode
# ncclimo --jobs=3 -c 20180410.A_WCYCL1950_HR.ne120_oRRS18v3_ICG.theta -s 1 -e 5 -i ${DATA}/ne120/raw -o ${DATA}/ne120/clm -r ${DATA}/maps/map_ne120np4_to_cmip6_720x1440_aave.20181001.nc # ne120 climo on 1 node in background mode with 3-jobs (to stay under 192 GB RAM limit)
# echo '#!/bin/bash' > ~/ncclimo.slurm
# echo "ncclimo -p mpi -c 20180410.A_WCYCL1950_HR.ne120_oRRS18v3_ICG.theta -s 1 -e 5 -i ${DATA}/ne120/raw -o ${DATA}/ne120/clm -O ${DATA}/ne120/rgr -r ${DATA}/maps/map_ne120np4_to_cmip6_720x1440_aave.20181001.nc" >> ~/ncclimo.slurm;chmod a+x ~/ncclimo.slurm # ne120 climo on 4 nodes, MPI mode:
# sbatch -A e3sm --nodes=4 --time=00:30:00 --job-name=ncclimo --mail-type=END --output=${HOME}/ncclimo.out ~/ncclimo.slurm

# Best performance on resolutions finer than ne30 (~1x1 degree) requires a job scheduler/batch processor
# Cobalt (cooley), SLURM (andes,compy,cori), Maui (a PBS-variant) (blues), Torque (a PBS-variant) (hopper), and PBS (other) schedulers allow both interactive and non-interactive (i.e., script) batch jobs
# ALCF Maui:
# http://www.lcrc.anl.gov/for-users/using-lcrc/running-jobs
# ALCF Cobalt:
# softenv # lists available packages
# http://www.mcs.anl.gov/hs/software/systems/softenv/softenv-intro.html
# http://www.alcf.anl.gov/user-guides/using-cobalt-cooley
# https://www.alcf.anl.gov/user-guides/cobalt-job-control
# NCAR PBSPro:
# https://www2.cisl.ucar.edu/resources/computational-systems/cheyenne/quick-start-cheyenne
# https://www2.cisl.ucar.edu/resources/computational-systems/cheyenne/running-jobs/submitting-jobs-pbs
# NERSC SLURM:
# https://www.nersc.gov/users/computational-systems/cori/running-jobs/slurm-introduction
# https://www.nersc.gov/users/computational-systems/cori/running-jobs/queues-and-policies/
# https://slurm.schedmd.com/sbatch.html # sbatch man page
# https://slurm.schedmd.com/salloc.html # salloc man page
# OLCF SLURM (after 20190903, PBS until 20190903): 
# https://docs.olcf.ornl.gov/systems/andes_user_guide.html
# Requesting interactive nodes, Submitting non-interactive batch jobs, Monitoring queues, Deleting jobs:
# Cobalt: qsub -I,   qsub,  qstat,    qdel, mpirun
# LSF:    bsub -I,   bsub,jobstat,    qdel,  jsrun
# PBS:    qsub -I,   qsub,  qstat,    qdel, mpirun
# PBSPro: qsub -I,   qsub,  qstat,    qdel, mpirun
# SLURM:   salloc, sbatch, squeue, scancel,   srun
# Interactive queue: a) Reserve nodes and acquire prompt on control node b) Execute ncclimo command interactively
#   Andes:  salloc  -A CLI115 --nodes=1 --time=00:30:00 --job-name=ncclimo # Bigmem: --partition=gpu
#   Blues:  salloc  -A condo --nodes=1 --partition=acme-small --time=00:30:00 --job-name=ncclimo
#   Cheyenne: qsub -I -A SCSG0002 -l select=1:ncpus=36:mpiprocs=1 -l walltime=00:30:00 -q regular -N ncclimo
#   Chrysalis: salloc  -A e3sm --nodes=1 --partition=debug --time=00:30:00 --job-name=ncclimo # Use compute queue instead if debug is slow to grant resources
#   Compy:  salloc  -A e3sm --nodes=1 --time=00:30:00 --job-name=ncclimo
#   Cooley: qsub -I -A ClimateEnergy_4 --nodecount=1 --time=00:30:00 --jobname=ncclimo
#   Cori:   salloc  -A e3sm -C knl --nodes=1 --partition=debug --time=00:30:00 --job-name=ncclimo # NB: 30 minute limit
#   Oldrhea:qsub -I -A CLI115 -V -l nodes=1 -l walltime=00:30:00 -N ncclimo # Bigmem: -l partition=gpu
# Non-interactive batch procedure: a) Store ncclimo command in ncclimo.[cobalt|pbs|slurm] b) qsub ncclimo.[cobalt|pbs|slurm]
# Non-interactive batch queue differences (besides argument syntax):
# 1. Cobalt and SLURM require initial 'shebang' line to specify the shell interpreter (not required on PBS)
# 2. Cobalt appends stdout/stderr to existing output files, if any, whereas PBS overwrites existing files
# 3. Cobalt uses ${COBALT_NODEFILE} and (NA) whereas PBS (and PBSPro) use ${PBS_NODEFILE} and ${PBS_NUM_PPN}, respectively, and SLURM uses ${SLURM_NODELIST} and ${SLURM_CPUS_ON_NODE}, respectively
# 4. SLURM automatically combines stdout and stderr, yet does not understand tilde (~ = home directory) expansion in error/output filenames
# 5. After merger of Cori Phase 1 Haswell and Phase 2 KNL cabinets in ~2017, SLURM on Cori requires Quality-of-Service (QOS) argument, either -C haswell or -C knl or -C gpu
# Differences 1 & 2 impose slightly different invocations; difference 3 requires abstracting environment variables; difference 4 requires omitting ~'s; difference 5 requires -C argument on Cori only
#   Andes a): echo '#!/bin/bash' > ~/ncclimo.slurm
#                   echo "ncclimo -a scd -d 1 -p mpi -c famipc5_ne120_v0.3_00003 -s 1 -e 2 -i ${DATA}/ne120/raw -o ${DATA}/ne120/clm -r ${DATA}/maps/map_ne120np4_to_fv257x512_aave.20150901.nc" >> ~/ncclimo.slurm;chmod a+x ~/ncclimo.slurm
#   Blues a):  echo "ncclimo -a scd -d 1 -p mpi -c 20180129.DECKv1b_piControl.ne30_oEC.edison -s 1 -e 2 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -r ${DATA}/maps/map_ne30np4_to_fv129x256_aave.20150901.nc" > ~/ncclimo.pbs;chmod a+x ~/ncclimo.pbs
#   Cheyenne a) echo "ncclimo -a scd -d 1 -p mpi -c 20180129.DECKv1b_piControl.ne30_oEC.edison -s 1 -e 2 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -r ${DATA}/maps/map_ne30np4_to_fv129x256_aave.20150901.nc" > ~/ncclimo.pbs;chmod a+x ~/ncclimo.pbs
#   Compy a): echo '#!/bin/bash' > ~/ncclimo.slurm
#                   echo "ncclimo -d 1 -p mpi -c 20180215.DECKv1b_H1.ne30_oEC.edison -s 2010 -e 2010 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -r ${DATA}/maps/map_ne30np4_to_fv129x256_aave.20150901.nc" >> ~/ncclimo.slurm;chmod a+x ~/ncclimo.slurm
#   Cooley a): /bin/rm -f ~/ncclimo.err ~/ncclimo.out
#              echo '#!/bin/bash' > ~/ncclimo.cobalt
#              echo "ncclimo -d 1 -p mpi -c b1850c5_m2a -s 55 -e 58 -i /home/taylorm/scratch1.qtang/b1850c5_m2a/run -o ${DATA}/ne120/clm" >> ~/ncclimo.cobalt;chmod a+x ~/ncclimo.cobalt
#   Cori a): echo '#!/bin/bash' > ~/ncclimo.slurm
#                   echo "ncclimo -d 1 -p mpi -c 20180129.DECKv1b_piControl.ne30_oEC.edison -s 1 -e 2 -i ${DATA}/ne30/raw -o ${DATA}/ne30/clm -r ${DATA}/maps/map_ne30np4_to_fv129x256_aave.20150901.nc" >> ~/ncclimo.slurm;chmod a+x ~/ncclimo.slurm
#   Andes b): sbatch -A CLI115 --nodes=12 --time=00:30:00 --job-name=ncclimo --mail-type=END --output=${HOME}/ncclimo.out ~/ncclimo.slurm
#   Blues b):  qsub -A ACME -q acme -l nodes=1 -l walltime=00:30:00 -N ncclimo -j oe -m e -o ~/ncclimo.out ~/ncclimo.pbs
#   Cheyenne b): qsub -A SCSG0002 -l select=12:ncpus=36:mpiprocs=1 -l walltime=00:30:00 -q regular -N ncclimo -j oe -m e -o ~/ncclimo.out ~/ncclimo.pbs
#   Compy b): sbatch -A e3sm --nodes=12 --time=00:30:00 --job-name=ncclimo --mail-type=END --output=${HOME}/ncclimo.out ~/ncclimo.slurm
#   Cooley b): qsub -A ClimateEnergy_4 --nodecount=12 --time=00:30:00 --jobname ncclimo --error ~/ncclimo.err --output ~/ncclimo.out --notify zender@uci.edu ~/ncclimo.cobalt
#   Cori b): sbatch -A e3sm -C haswell --nodes=12 --time=00:30:00 --partition=debug --job-name=ncclimo --mail-type=END --output=${HOME}/ncclimo.out ~/ncclimo.slurm
#   Oldrhea b):qsub -A CLI115 -V -l nodes=12 -l walltime=00:30:00 -N ncclimo -j oe -m e -o ~/ncclimo.out ~/ncclimo.pbs
# NB: stdout/stderr files returned from batch jobs are read-protected, need 'chmod 644 ~/ncclimo.out' afterwards

# Normal use: Set five "mandatory" inputs (caseid, yr_srt, yr_end, drc_in, drc_out), and possibly rgr_map, on command line
# caseid:  Simulation name (filenames must start with ${caseid})
# drc_in:  Input directory for raw data
#          Years outside yr_srt and yr_end are ignored
#          yr_srt should, and for SDD mode must, contain complete year of output
#          SCD mode ignores Jan-Nov of yr_srt
#          Dec of yr_end is excluded from the seasonal and monthly analysis in SCD mode
#          yr_end should, and for SDD mode must, contain complete year of output
# drc_out: Output directory for processed native grid climatology ("climo files")
#          User needs write permission for ${drc_out}
# rgr_map: Horizontal regridding map, if non-NULL, invoke regridder with specified map on output datasets
#          Pass options intended exclusively for the NCO regridder as arguments to the -R switch
# yr_srt:  Year of first January to analyze
# yr_end:  Year of last  January to analyze

# Other options (often their default settings work well):
# dec_md:  December mode, i.e., how to treat December. One of two options:
#          Seasonally-contiguous-december (SCD) mode (dec_md=scd|djf) (default through 4.9.4)
#          Seasonally-discontiguous-december (SDD) mode (dec_md=sdd|jfd) (default starting 4.9.5)
#          Both modes use an integral multiple of 12 months, and _never alter any input files_
#          SCD climatologies begin in Dec of yr_srt-1, and end in Nov of yr_end
#          SDD climatologies begin in Jan of yr_srt,   and end in Dec of yr_end
#          SCD excludes Jan-Nov of yr_srt-1 and Dec of yr_end (i.e., SCD excludes 12 months of available data)
#          SDD uses all months of yr_srt through yr_end (i.e., SDD can use all available data)
#          SCD seasonal averages are inconsistent with (calendar-year-based) annual averages, but better capture seasonal the "natural" (not calendar-year-based) climate year
#          SDD seasonal averages are fully consistent with (calendar-year-based) annual averages
# drc_rgr: Regridding directory---store regridded files, if any, in drc_rgr rather than drc_out
# lnk_flg: Link E3SM-climo to AMWG-climo filenames
#          AMWG omits the YYYYMM components of climo filenames, resulting in shorter names
#          This switch (on by default) symbolically links the full (E3SM) filename to the shorter (AMWG) name
#          AMWG diagnostics scripts can produce plots directly from these linked filenames
# par_typ: Parallelism type
#          bck = Background: Spawn children (basic blocks) as background processes on control node then wait()
#                Works best when available RAM > 12*4*sizeof(monthly input file), otherwise jobs swap-to-disk
#          mpi = MPI: Spawn children (basic blocks) as MPI processes (one per node in batch environment) then wait()
#                Requires batch system with PBS and MPI. Use when available RAM/node < 12*2.5*sizeof(monthly input file).
#                Optimized for batch with 12 nodes. Factors thereof (6, 4, 3, 2 nodes) should also work.
#                Remember to request 12 nodes if possible!
#          srl = Serial: Execute script in serial mode on single node
#                Works best when available RAM < 12*4*sizeof(monthly input file), otherwise jobs swap-to-disk
# var_lst: Variables to include, or, with nco_opt='-x', to exclude, in comma-separated list format, e.g.,
#          'FSNT,AODVIS'. Regular expressions work, too: 'AOD.?'

# Infrequently used options:
# dbg_lvl: 0 = Quiet, print basic status during evaluation
#          1 = Print configuration, full commands, and status to output during evaluation
#          2 = As in dbg_lvl=1, but do not evaluate commands
#          3 = As in dbg_lvl=2, with additional information (mainly for batch queues)
# fml_nm:  Family name (nickname) of output files referring to $fml_nm character sequence used in output climo file names:
#          fml_nm_XX_YYYYMM_YYYYMM.nc (examples include '' (default), 'control', 'experiment')
#          By default, fml_nm=$caseid. Use fml_nm instead of $caseid to simplify long names, avoid overlap, etc.
# hst_nm:  History volume name string, i.e., $hst_nm character sequence used in history tape names:
#          caseid.mdl_nm.hst_nm.YYYY-MM.nc (examples include 'h0' (default, works for cam, clm), 'h1', 'h' (for cism), 'hist' (for mpasocean, mpasseaice)
# mdl_nm:  Model name string in output filenames, i.e., character sequence $mdl_nm in standard names:
#          caseid.mdl_nm.h0.YYYY-MM.nc (examples include 'cam' (default), 'clm2', 'cam2', 'cice', 'cism', 'eam', 'elm', 'mosart', 'mpasocean', 'mpaso', 'mpasseaice', 'mpassi', 'pop')
# nco_opt: String of options to pass-through to NCO, e.g.,
#          '-D 2 -7 -L 1' for NCO debugging level 2, netCDF4-classic output, compression level 1
#          '--no_tmp_fl -x' to skip temporary files, turn extraction into exclusion list
# rgr_opt: String of options (besides thread-number) to pass-through exclusively to NCO regridder, e.g., 
#          ncclimo -m clm2 ... -R col_nm=lndgrid -r map.nc ...
# thr_nbr: Thread number to use in NCO regridder, '-t 1' for one thread, '-t 2' for two threads...

# Set NCO version and directory
nco_exe=`which ncks`
if [ -z "${nco_exe}" ]; then
    echo "${spt_nm}: ERROR Unable to find NCO, \${nco_exe} = ${nco_exe}"
    exit 1
fi # !nco_exe
# StackOverflow method finds NCO directory
while [ -h "${nco_exe}" ]; do
  drc_nco="$( cd -P "$( dirname "${nco_exe}" )" && pwd )"
  nco_exe="$(readlink "${nco_exe}")"
  [[ ${nco_exe} != /* ]] && nco_exe="${drc_nco}/${nco_exe}"
done
drc_nco="$( cd -P "$( dirname "${nco_exe}" )" && pwd )"
nco_vrs=$(ncks --version 2>&1 > /dev/null | grep NCO | awk '{print $5}')
nco_sng=$(ncks --version 2>&1 > /dev/null | grep NCO | awk -F '"' '{print $2}')

# 20190218: Die quickly when NCO is found yet cannot run, e.g., due to linker errors
if [ -z "${nco_vrs}" ]; then
    echo "${spt_nm}: ERROR ${nco_exe} dies with error message on next line:"
    $(ncks --version)
    if [ "${NCO_PATH_OVERRIDE}" != 'Yes' ]; then
	printf "HINT: Run-time errors due to link issues (e.g., libraries missing or not found) might be solved at supported national labs (ALCF, NCAR, NERSC, OLCF, PNNL) by employing NCO machine-dependent hardcoded paths/modules. To try this, re-run command after setting \"export NCO_PATH_OVERRIDE=Yes\".\n"
    fi # !NCO_PATH_OVERRIDE
    exit 1
fi # !nco_vrs
lbr_vrs=$(ncks --library 2>&1 > /dev/null | awk '{print $6}')

# Detect and warn about mixed modules (for Qi Tang 20170531)
if [ "${drc_spt}" != "${drc_nco}" ]; then
    echo "INFO: Mixture of NCO scripts and binaries from different locations. Script ${spt_nm} is from directory ${drc_spt} while NCO binaries are from directory ${drc_nco}. Normally this script and the binaries are from the same executables directory. This INFO may be safely ignored for customized scripts and/or binaries that the user has intentionally split into different directories."
    echo "HINT (All-users): Conflicting script and binary directories may result from 1) Hardcoding an NCO script and/or binary pathnames, 2) Incomplete NCO installations in one or more directories in the \$PATH environment variable, 3) (Re-)Installing or (re-)building NCO without issuing a \"hash -r\" command afterward to update the executable pathnames that the shell remembers from before."
    echo "HINT (E3SM-only): In a Conda-based NCO environment, such as E3SM-Unified (which uses NCO in MPAS Analysis and E3SM-Diags), it is possible that some features may be unavailable because the upstream packages (e.g., UDUnits) were not properly linked by Conda. The ncclimo and ncremap scripts contain a mechanism to access the (presumably correctly linked) NCO binary executable and library paths in C. Zender's home directories on the major E3SM machines. Users may turn-on the machine-dependent, hard-coded path by invoking ncclimo and/or ncremap after altering their environment with: \"export NCO_PATH_OVERRIDE=Yes\"."
fi # drc_spt

# When running in a terminal window (not in an non-interactive batch queue)...
if [ -n "${TERM}" ]; then
    # Set fonts for legibility
    if [ -x /usr/bin/tput ] && tput setaf 1 &> /dev/null; then
	fnt_bld=`tput bold` # Bold
	fnt_nrm=`tput sgr0` # Normal
	fnt_rvr=`tput smso` # Reverse
	fnt_tlc=`tput sitm` # Italic
    else
	fnt_bld="\e[1m" # Bold
	fnt_nrm="\e[0m" # Normal
	fnt_rvr="\e[07m" # Reverse
	fnt_tlc="\e[3m" # Italic
    fi # !tput
fi # !TERM
    
# Pre-define enumerated types used in defaults
par_bck='background' # [sng] Parallelism: background
par_mpi='mpi' # [sng] Parallelism: MPI
par_srl='serial' # [sng] Parallelism: serial

# Defaults for command-line options and some derived variables
# Modify these defaults to save typing later
ann_sfx='01-01-00000' # [sng] Annual file suffix (MPAS, e.g., uses '01-01-00000')
area_nm='area' # [sng] Name of variable containing gridcell area
bch_pbs='No' # [sng] PBS batch (non-interactive) job
bch_slr='No' # [sng] SLURM batch (non-interactive) job
bnr_flg='No' # [sng] Binary method
caseid='' # [sng] Case ID
caseid_xmp='20180129.DECKv1b_piControl.ne30_oEC.edison' # [sng] Case ID for examples
cf_flg='Yes' # [sng] Produce CF climatology attribute?
clm_flg='Yes' # [sng] Generate climatology
clm_hfc_or_mth='No' # [sng] Generate monthly output climatology
clm_md='mth' # [sng] Climatology mode ('ann', 'dly', 'hfc', 'hfs', or 'mth')
csn_flg='No' # [flg] Trigger season-processing code
csn_lst='mam,jja,son,djf' # [sng] Seasons to output (subset of mam,jja,son,djf,jfm,amj,jas,ond,fm,on)
d2f_flg='No' # [flg] Convert double-precision fields to single-precision
d2f_opt='--d2f' # [sng] Option string to convert double-precision fields to single-precision
dpt_fl='' # [sng] Depth file with refBottomDepth for MPAS ocean
dpt_opt='' # [sng] Option string to add depth coordinate to regridded MPAS files
dbg_lvl=0 # [nbr] Debugging level
dec_md='sdd' # [sng] December mode (contiguous: 'scd' | 'djf', discontiguous 'sdd' | 'jfd' as per above)
dfl_lvl='' # [enm] Deflate level
drc_in='' # [sng] Input file directory
drc_in_xmp="${DATA}/ne30/raw" # [sng] Input file directory for examples
drc_in_mps="${DATA}/mpas/raw" # [sng] Input file directory for MPAS examples
drc_out="${drc_pwd}" # [sng] Output file directory
drc_out_xmp="${DATA}/ne30/clm" # [sng] Output file directory for examples
drc_out_mps="${DATA}/mpas/clm" # [sng] Output file directory for MPAS examples
drc_prv='' # [sng] Directory containing previous climatology to extend with current data
drc_rgr='' # [sng] Regridded file directory
drc_rgr_prv='' # [sng] Regridded file directory for previous climatology
drc_rgr_xmp="${DATA}/ne30/rgr" # [sng] Regrid file directory for examples
drc_rgr_xtn='' # [sng] Regridded file directory for for extended climatology
drc_xtn='' # [sng] Directory containing extended climatology
dpy=365 # [nbr] Days-per-year
fl_fmt='' # [enm] Output file format
fl_nbr=0 # [nbr] Number of files to split
fl_rmd=0 # [nbr] Number of files/months beyond an integral number of years
fml_nm='' # [sng] Family name (i.e., nickname, e.g., 'amip', 'control', 'experiment')
gaa_sng_std="--gaa climo_script=${spt_nm} --gaa climo_command=\"'${cmd_ln}'\" --gaa climo_hostname=${HOSTNAME} --gaa climo_version=${nco_vrs}" # [sng] Global attributes to add
glb_avg='No' # [sng] Global average timeseries
hdr_pad='10000' # [B] Pad at end of header section
hrz_dmn='ncol' # [sng] Name of horizontal dimension(s) (comma-separated, if 2-D, e.g. 'lat,lon')
hst_nm='h0' # [sng] History volume (e.g., 'h0', 'h1', 'h')
inp_aut='No' # [sng] Input file list automatically generated
inp_glb='No' # [sng] Input file list from globbing directory 
inp_psn='No' # [sng] Input file list from positional arguments
inp_std='No' # [sng] Input file list from stdin
job_nbr=12 # [nbr] Job simultaneity for parallelism
lnk_flg='No' # [sng] Link E3SM-climo to AMWG-climo filenames
map_opt='' # [sng] Map-file options (e.g., '--map=map.nc')
mdl_nm='eam' # [sng] Model name string in history output (e.g., 'cam', 'cam2', 'cice', 'cism', 'clm', 'clm2', 'eam', 'elm', 'mali', 'mosart', 'mpascice', 'mpasseaice', 'mpassi', 'mpaso', 'mpasocean')
mdl_nm_usr='' # [sng] Model name string in history output (e.g., 'cam', 'cam2', 'cice', 'cism', 'clm', 'clm2', 'eam', 'elm', 'mali', 'mosart', 'mpascice', 'mpasseaice', 'mpassi', 'mpaso', 'mpasocean')
mdl_typ='cesm' # [sng] Model type ('cesm', 'mpas') (for regridding)
mem_mb=0 # [MB] Megabytes of RAM per srun job in Cori SLURM in MPI mode (0 indicates unlimited RAM)
mpi_flg='No' # [sng] Parallelize over nodes
mth_end='12' # [yr] End month
mth_srt='1' # [yr] Start month
nco_opt='--no_tmp_fl' # [sng] NCO options (e.g., '-7 -D 1 -L 1')
ncr_flg='No' # [sng] Incremental method
nd_nbr=1 # [nbr] Number of nodes
nm_typ='cesm' # [sng] Name type ('cesm', 'mpas', 'yyyymm', 'yyyy-mm', 'yyyy-mm-01', 'yyyy-mm-01-00000') (for filenames)
no_ntv_tms='No' # [flg] Omit native-grid split timeseries
par_opt='' # [sng] Parallel options to shell
par_typ="${par_bck}" # [sng] Parallelism type
ppc_prc='' # [nbr] Precision-preserving compression precision
prc_typ='' # [sng] Procedure type (e.g., 'cam', 'cam2', 'cice', 'cism', 'clm', 'clm2', 'eam', 'elm', 'mali', 'mpascice', 'mpasseaice', 'mpassi', 'mpaso')
rgr_map='' # [sng] Regridding (horizontal) map
rgr_opt='' # [sng] Regridding options (e.g., '--rgr col_nm=lndgrid', '--rgr col_nm=nCells')
spl_opt='' # [sng] Splitter options (non-MPAS only) (e.g., '--no_cll_msr')
spl_rgr_opt='--no_stagger' # [sng] Splitter regridding options
sbs_flg='No' # [sng] Split (subset) climatologies
sgs_frc='' # [sng] Sub-grid fraction variable
std_chk='Yes' # [sng] Check stdin for input file list
thr_nbr=2 # [nbr] Thread number for regridder
tpd_out=1 # [nbr] Timesteps-per-day in output
#var_lst='FSNT,AODVIS' # [sng] Variables to process (empty means all)
var_lst='' # [sng] Variables to process (empty means all)
var_xtr='' # [sng] Extra variables for splitter timeseries
vrs_prn='No' # [sng] Print version information
vrt_fl='' # [sng] Vertical coordinate file
vrt_opt='' # [sng] Vertical interpolation options (e.g., '--vrt_fl=vrt_grd.nc')
vrt_xtr='' # [sng] Vertical extrapolation type (mss_val|nrs_ngh)
xcl_flg='No' # [sng] Exclude rather than extract variable list
xtn_flg='No' # [sng] Produce extended climatology
ypf_max=50 # [yr] Years-per-output-file
yr_end='2' # [yr] End year
yr_srt='1' # [yr] Start year

function fnc_usg_prn { # NB: dash supports fnc_nm (){} syntax, not function fnc_nm{} syntax
    # Print usage
    printf "${fnt_rvr}Basic usage:\n${fnt_nrm}${fnt_bld}${spt_nm} -c caseid -s yr_srt -e yr_end -i drc_in -o drc_out -r rgr_map${fnt_nrm} # Generate & regrid climatology\n"
    printf "${fnt_bld}${spt_nm} -v var_lst -s yr_srt -e yr_end -o drc_out -r rgr_map in1.nc in2.nc ... inN.nc${fnt_nrm} # Split, reshape, & regrid timeseries\n"
    printf "${fnt_bld}${spt_nm} --case=caseid --start=yr_srt --end=yr_end --input=drc_in --output=drc_out --map=rgr_map${fnt_nrm} # Long options\n\n"
    echo "Command-line options [long-option synonyms in ${fnt_tlc}italics${fnt_nrm}]:"
    echo "${fnt_rvr}-3${fnt_nrm}          Output file format CLASSIC (netCDF3 classic CDF1) [${fnt_tlc}fl_fmt, file_format=classic${fnt_nrm}]"
    echo "${fnt_rvr}-4${fnt_nrm}          Output file format NETCDF4 (netCDF4 extended HDF5) [${fnt_tlc}fl_fmt, file_format=netcdf4${fnt_nrm}]"
    echo "${fnt_rvr}-5${fnt_nrm}          Output file format 64BIT_DATA (netCDF3/PnetCDF CDF5) [${fnt_tlc}fl_fmt, file_format=64bit_data${fnt_nrm}]"
    echo "${fnt_rvr}-6${fnt_nrm}          Output file format 64BIT_OFFSET (netCDF3 64bit CDF2) [${fnt_tlc}fl_fmt, file_format=64bit_offset${fnt_nrm}]"
    echo "${fnt_rvr}-7${fnt_nrm}          Output file format NETCDF4_CLASSIC (netCDF4 classic HDF5) [${fnt_tlc}fl_fmt, file_format=netcdf4_classic${fnt_nrm}]"
    echo "${fnt_rvr}-a${fnt_nrm} ${fnt_bld}dec_md${fnt_nrm}   December mode (default ${fnt_bld}${dec_md}${fnt_nrm}) [${fnt_tlc}dec_md, dcm_md, december_mode, dec_mode${fnt_nrm}] [${fnt_tlc}scd,djf | sdd,jfd${fnt_nrm}]"
    echo " ${fnt_bld}--amwg_lnk${fnt_nrm} Link E3SM-climo to AMWG-climo filenames [${fnt_tlc}amwg_links, AMWG_link${fnt_nrm}]"
    echo " ${fnt_bld}--area_nm${fnt_nrm}  Area-variable name (default ${fnt_bld}${area_nm}${fnt_nrm}) [${fnt_tlc}area_nm, area_name, area${fnt_nrm}]"
    echo "${fnt_rvr}-C${fnt_nrm} ${fnt_bld}clm_md${fnt_nrm}   Climatology mode (default ${fnt_bld}${clm_md}${fnt_nrm}) [${fnt_tlc}clm_md, climatology_mode, climo_mode${fnt_nrm}] [${fnt_tlc}ann | dly | hfc | hfs | mth${fnt_nrm}]"
    echo "${fnt_rvr}-c${fnt_nrm} ${fnt_bld}caseid${fnt_nrm}   Case ID string to generate input names or to name HFC output (default ${fnt_bld}${caseid}${fnt_nrm}) [${fnt_tlc}caseid, case_id, case${fnt_nrm}]"
    echo "${fnt_rvr}-d${fnt_nrm} ${fnt_bld}dbg_lvl${fnt_nrm}  Debug level (default ${fnt_bld}${dbg_lvl}${fnt_nrm}) [${fnt_tlc}dbg_lvl, dbg, debug, debug_level${fnt_nrm}]"
    echo " ${fnt_bld}--d2f${fnt_nrm}      Convert double-precision fields to single-precision (default ${fnt_bld}${d2f_flg}${fnt_nrm}) [${fnt_tlc}d2f | d2s | dbl_flt | dbl_sgl | double_float${fnt_nrm}]"
    echo " ${fnt_bld}--dpf${fnt_nrm}      Days-per-file in every high-frequency splitter input file [${fnt_tlc}dpf, days_per_file${fnt_nrm}]"
    echo " ${fnt_bld}--dpt_fl${fnt_nrm}   Depth file with refBottomDepth for MPAS ocean (empty means none) (default ${fnt_bld}${dpt_fl}${fnt_nrm}) [${fnt_tlc}dpt_fl, mpas_fl, mpas_depth, depth_file${fnt_nrm}]"
    echo "${fnt_rvr}-E${fnt_nrm} ${fnt_bld}yr_end${fnt_nrm}   End year previous climo (empty means none) (default ${fnt_bld}${yr_end_prv}${fnt_nrm}) [${fnt_tlc}yr_end_prv, prv_yr_end, previous_end${fnt_nrm}]"
    echo "${fnt_rvr}-e${fnt_nrm} ${fnt_bld}yr_end${fnt_nrm}   End year (default ${fnt_bld}${yr_end}${fnt_nrm}) [${fnt_tlc}yr_end, end_yr, year_end, end_year, end${fnt_nrm}]"
    echo "${fnt_rvr}-f${fnt_nrm} ${fnt_bld}fml_nm${fnt_nrm}   Family name (nickname) (empty means none) (default ${fnt_bld}${fml_nm}${fnt_nrm}) [${fnt_tlc}fml_nm, fml, family_name${fnt_nrm}]"
    echo " ${fnt_bld}--fl_fmt${fnt_nrm}   File format (empty is inherited from input) (default ${fnt_bld}${fl_fmt}${fnt_nrm}) [${fnt_tlc}fl_fmt, fmt_out, file_format, format_out${fnt_nrm}]"
    echo " ${fnt_bld}--glb_avg${fnt_nrm}  Global average timeseries (default ${fnt_bld}${glb_avg}${fnt_nrm}) [${fnt_tlc}glb_avg, global_average${fnt_nrm}]"
    echo "${fnt_rvr}-h${fnt_nrm} ${fnt_bld}hst_nm${fnt_nrm}   History volume name (default ${fnt_bld}${hst_nm}${fnt_nrm}) [${fnt_tlc}hst_nm, history_name, history${fnt_nrm}]"
    echo " ${fnt_bld}--hrz_dmn${fnt_nrm}  Horizontal dimension name(s) (default ${fnt_bld}${hrz_dmn}${fnt_nrm}) [${fnt_tlc}hrz_dmn, dmn_hrz, hrz_dmn_nm, horizontal_dimension${fnt_nrm}]"
    echo "${fnt_rvr}-i${fnt_nrm} ${fnt_bld}drc_in${fnt_nrm}   Input directory (default ${fnt_bld}${drc_in}${fnt_nrm}) [${fnt_tlc}drc_in, in_drc, dir_in, in_dir, input${fnt_nrm}]"
    echo "${fnt_rvr}-j${fnt_nrm} ${fnt_bld}job_nbr${fnt_nrm}  Job simultaneity for parallelism (default ${fnt_bld}${job_nbr}${fnt_nrm}) [${fnt_tlc}job_nbr, job_number, jobs${fnt_nrm}]"
    echo "${fnt_rvr}-L${fnt_nrm} ${fnt_bld}dfl_lvl${fnt_nrm}  Deflate level (empty is none) (default ${fnt_bld}${dfl_lvl}${fnt_nrm}) [${fnt_tlc}dfl_lvl, dfl, deflate${fnt_nrm}]"
    echo "${fnt_rvr}-l${fnt_nrm} ${fnt_bld}lnk_flg${fnt_nrm}  Link E3SM-climo to AMWG-climo filenames (default ${fnt_bld}${lnk_flg}${fnt_nrm}) [${fnt_tlc}lnk_flg, link_flag${fnt_nrm}]"
    echo "${fnt_rvr}-m${fnt_nrm} ${fnt_bld}mdl_nm${fnt_nrm}   Model name string in output filenames (default ${fnt_bld}${mdl_nm}${fnt_nrm}) [${fnt_tlc}mdl_nm, mdl, model_name, model${fnt_nrm}]"
    echo " ${fnt_bld}--mth_end${fnt_nrm}  End month (default ${fnt_bld}${mth_end}${fnt_nrm}) [${fnt_tlc}mth_end, end_mth, month_end, end_month${fnt_nrm}]"
    echo " ${fnt_bld}--mth_srt${fnt_nrm}  Start month (default ${fnt_bld}${mth_srt}${fnt_nrm}) [${fnt_tlc}mth_srt, start_mth, month_start, start_month${fnt_nrm}]"
    echo "${fnt_rvr}-n${fnt_nrm} ${fnt_bld}nco_opt${fnt_nrm}  NCO options (empty means none) (default ${fnt_bld}${nco_opt}${fnt_nrm}) [${fnt_tlc}nco_opt, nco, nco_options${fnt_nrm}]"
    echo " ${fnt_bld}--no_lnk${fnt_nrm}   Do not link E3SM-climo to AMWG-climo filenames [${fnt_tlc}no_links, no_amwg_links, no_amwg, no_AMWG_link${fnt_nrm}]"
    echo " ${fnt_bld}--no_cll_msr${fnt_nrm}  Omit cell_measures variables (e.g., 'area') [${fnt_tlc}no_area, no_cll_msr, no_cell_measures${fnt_nrm}]"
    echo " ${fnt_bld}--no_frm_trm${fnt_nrm}  Omit formula_terms variables (e.g., 'hyba', 'PS') [${fnt_tlc}no_frm_trm, no_frm, no_formula_terms${fnt_nrm}]"
    echo " ${fnt_bld}--no_ntv_tms${fnt_nrm}  Omit native-grid timeseries (splitter only) [${fnt_tlc}no_ntv_tms, no_ntv, no_native${fnt_nrm}]"
    echo " ${fnt_bld}--no_stg_grd${fnt_nrm}  Omit staggered grid variables ('slat, slon, w_stag') [${fnt_tlc}no_stg_grd, no_stg, no_stagger, no_staggered_grid${fnt_nrm}]"
    echo " ${fnt_bld}--no_stdin${fnt_nrm} Do not check stdin for input file list [${fnt_tlc}no_stdin, no_inp_std, no_redirect, no_standard_input${fnt_nrm}]"
    echo "${fnt_rvr}-O${fnt_nrm} ${fnt_bld}drc_rgr${fnt_nrm}  Regridded directory (default ${fnt_bld}${drc_rgr}${fnt_nrm}) [${fnt_tlc}drc_rgr, rgr_drc, dir_regrid, regrid${fnt_nrm}]"
    echo "${fnt_rvr}-o${fnt_nrm} ${fnt_bld}drc_out${fnt_nrm}  Output directory (default ${fnt_bld}${drc_out}${fnt_nrm}) [${fnt_tlc}drc_out, out_drc, dir_out, out_dir, output${fnt_nrm}]"
    echo "${fnt_rvr}-P${fnt_nrm} ${fnt_bld}prc_typ${fnt_nrm}  Procedure type (empty means none) (default ${fnt_bld}${prc_typ}${fnt_nrm}) [${fnt_tlc}prc_typ, procedure${fnt_nrm}]"
    echo "${fnt_rvr}-p${fnt_nrm} ${fnt_bld}par_typ${fnt_nrm}  Parallelism type (default ${fnt_bld}${par_typ}${fnt_nrm}) [${fnt_tlc}par_typ, par_md, parallel_type, parallel_mode, parallel${fnt_nrm}] [${fnt_tlc}serial | background | mpi${fnt_nrm}]"
    echo " ${fnt_bld}--ppc_prc${fnt_nrm}  Precision-preserving compression precision (empty means none) (default ${fnt_bld}${ppc_prc}${fnt_nrm}) [${fnt_tlc}ppc, ppc_prc, precision, quantize${fnt_nrm}]"
    echo "${fnt_rvr}-R${fnt_nrm} ${fnt_bld}rgr_opt${fnt_nrm}  Regrid options (empty means none) (default ${fnt_bld}${rgr_opt}${fnt_nrm}) [${fnt_tlc}rgr_opt, regrid_options${fnt_nrm}]"
    echo "${fnt_rvr}-r${fnt_nrm} ${fnt_bld}rgr_map${fnt_nrm}  Regrid map-file (horizontal) (empty means none) (default ${fnt_bld}${rgr_map}${fnt_nrm}) [${fnt_tlc}rgr_map, regrid_map, map, map_file, map_fl${fnt_nrm}]"
    echo "${fnt_rvr}-S${fnt_nrm} ${fnt_bld}yr_srt${fnt_nrm}   Start year previous climo (empty means none) (default ${fnt_bld}${yr_srt_prv}${fnt_nrm}) [${fnt_tlc}yr_srt_prv, prv_yr_srt, previous_start${fnt_nrm}]"
    echo "${fnt_rvr}-s${fnt_nrm} ${fnt_bld}yr_srt${fnt_nrm}   Start year (default ${fnt_bld}${yr_srt}${fnt_nrm}) [${fnt_tlc}yr_srt, start_yr, year_start, start_year, start${fnt_nrm}]"
    echo " ${fnt_bld}--seasons${fnt_nrm}  Seasons to output ('none' means none) (default ${fnt_bld}${csn_lst}${fnt_nrm}, also available: jfm,amj,jas,ond,on,fm) [${fnt_tlc}csn_lst, csn, seasons${fnt_nrm}]"
    echo " ${fnt_bld}--sgs_frc${fnt_nrm}  Sub-grid fraction variable (empty means none) (default ${fnt_bld}${sgs_frc}${fnt_nrm}) [${fnt_tlc}sgs_frc, ice_frc, lnd_frc, ocn_frc, subgrid_fraction${fnt_nrm}]"
    echo "${fnt_rvr}-t${fnt_nrm} ${fnt_bld}thr_nbr${fnt_nrm}  Thread number for regridder (default ${fnt_bld}${thr_nbr}${fnt_nrm}) [${fnt_tlc}thr_nbr, thr, thread_number, thread, threads${fnt_nrm}]"
    echo " ${fnt_bld}--tpd_out${fnt_nrm}  Timesteps-per-day in output (default ${fnt_bld}${tpd_out}${fnt_nrm}) [${fnt_tlc}tpd_out, tpd, timesteps_per_day${fnt_nrm}]"
    echo " ${fnt_bld}--uio${fnt_nrm}      Unbuffered I/O (NC_SHARE) for netCDF3 files [${fnt_tlc}uio, unbuffered, share${fnt_nrm}]"
    echo "${fnt_rvr}-v${fnt_nrm} ${fnt_bld}var_lst${fnt_nrm}  Variable list (empty means all) (default ${fnt_bld}${var_lst}${fnt_nrm}) [${fnt_tlc}var_lst, variable_list, var, vars, variable, variables${fnt_nrm}]"
    echo " ${fnt_bld}--var_xtr${fnt_nrm}  Extra variables for splitter timeseries (empty means none) (default ${fnt_bld}${var_xtr}${fnt_nrm}) [${fnt_tlc}var_xtr, var_extra, variables_extra, extra_variables${fnt_nrm}]"
    echo " ${fnt_bld}--version${fnt_nrm}  Version and configuration information [${fnt_tlc}version, vrs, config, configuration, cnf${fnt_nrm}]"
    echo " ${fnt_bld}--vrt_fl${fnt_nrm}   Vertical coordinate file (empty means none) (default ${fnt_bld}${vrt_fl}${fnt_nrm}) [${fnt_tlc}vrt_fl, vrt, vrt_crd, vertical_coordinate${fnt_nrm}]"
    echo " ${fnt_bld}--vrt_xtr${fnt_nrm}  Vertical extrapolation type (empty means none) (default ${fnt_bld}${vrt_xtr}${fnt_nrm}) [${fnt_tlc}vrt_xtr, xtr_mth, extrapolation_type, extrapolation_method${fnt_nrm}] (mss_val|nrs_ngh)"
    echo "${fnt_rvr}-X${fnt_nrm} ${fnt_bld}drc_xtn${fnt_nrm}  Extended climo directory (default ${fnt_bld}${drc_xtn}${fnt_nrm}) [${fnt_tlc}drc_xtn, xtn_drc, extended_dir, extended_climo, extended${fnt_nrm}]"
    echo "${fnt_rvr}-x${fnt_nrm} ${fnt_bld}drc_prv${fnt_nrm}  Previous climo directory (default ${fnt_bld}${drc_prv}${fnt_nrm}) [${fnt_tlc}drc_prv, prv_drc, previous_dir, previous_climo, previous${fnt_nrm}]"
    echo " ${fnt_bld}--xcl_var${fnt_nrm}  Exclude rather than extract var_lst [${fnt_tlc}xcl_var, xcl, exclude, exclude_variables${fnt_nrm}]"
    echo "${fnt_rvr}-Y${fnt_nrm} ${fnt_bld}rgr_xtn${fnt_nrm}  Regridded extended climo directory (default ${fnt_bld}${drc_rgr_xtn}${fnt_nrm}) [${fnt_tlc}drc_rgr_xtn, drc_xtn_rgr, regridded_extended, extended_regridded${fnt_nrm}]"
    echo "${fnt_rvr}-y${fnt_nrm} ${fnt_bld}rgr_prv${fnt_nrm}  Regridded previous climo directory (default ${fnt_bld}${drc_rgr_prv}${fnt_nrm}) [${fnt_tlc}drc_rgr_prv, drc_prv_rgr, regridded_previous, previous_regridded${fnt_nrm}]"
    echo " ${fnt_bld}--ypf_max${fnt_nrm}  Years-per-output-file maximum (default ${fnt_bld}${ypf_max}${fnt_nrm}) [${fnt_tlc}ypf_max, ypf, years, years_per_file${fnt_nrm}]"
    printf "\n"
    printf "${fnt_rvr}Examples:${fnt_nrm}\n${fnt_bld}${spt_nm} -c ${caseid_xmp} -s ${yr_srt} -e ${yr_end} -i ${drc_in_xmp} -o ${drc_out_xmp} -r ~zender/data/maps/map_ne30np4_to_fv129x256_aave.20150901.nc ${fnt_nrm}# Generate EAM/CAM climo\n"
    printf "${fnt_bld}${spt_nm} -c control -m clm2 -s ${yr_srt} -e ${yr_end} -i ${drc_in_xmp} -o ${drc_out_xmp} -r ~zender/data/maps/map_ne30np4_to_fv129x256_aave.20150901.nc ${fnt_nrm}# Generate EAM/CLM climo\n"
    printf "${fnt_bld}${spt_nm} -P mpasseaice -s ${yr_srt} -e ${yr_end} -i ${drc_in_mps} -o ${drc_out_mps} -r ~zender/data/maps/map_oEC60to30v3_to_cmip6_180x360_aave.20181001.nc ${fnt_nrm}# Generate MPAS-Seaice climo\n"
    printf "${fnt_bld}${spt_nm} -P mpaso -p mpi -s 1 -e 5 -i ${drc_in_mps} -o ${drc_out_mps} -r ~zender/data/maps/map_oRRS18to6v3_to_cmip6_720x1440_nco.20200601.nc ${fnt_nrm}# Generate MPAS-Ocean climo\n"
    printf "${fnt_bld}cd output;ls *cam*19??-??*.nc | ${spt_nm} -v FSNT,TREFHT -s 1900 -e 1999 -o ${drc_out_xmp} -r ~zender/data/maps/map_ne30np4_to_fv129x256_aave.20150901.nc ${fnt_nrm}# Split climo\n"
    printf "${fnt_bld}ncclimo -c ${caseid_xmp} -m cam -S 41 -E 50 -x ${drc_rgr_xmp}/0041-0050 -s 51 -e 60 -i ${drc_rgr_xmp}/0051-0060 -X ${drc_rgr_xmp}/0041-0060 ${fnt_nrm}# Combine two climos\n\n"
# 20210831: NB: Alternative access to interactive batch queues via, e.g., "salloc -A e3sm -C knl --nodes=1 --time=00:30:00 --job-name=ncclimo" works well everywhere except Compy
    printf "${fnt_rvr}Interactive batch queues:${fnt_nrm}\n"
    printf "andes : srun -A CLI115 --nodes=1 --time=00:30:00 --job-name=ncclimo --pty bash\n"
    printf "andes : srun -A CLI115 --nodes=1 --time=00:30:00 --partition=gpu --job-name=ncclimo --pty bash # Bigmem\n"
    printf "blues : srun -A condo --nodes=1 --partition=acme-small --time=00:30:00 --job-name=ncclimo --pty bash\n"
    printf "cheyen: qsub -I -A SCSG0002 -l select=1:ncpus=36:mpiprocs=1 -l walltime=00:30:00 -q regular -N ncclimo\n"
    printf "chrysa: srun -A e3sm --nodes=1 --partition=debug --time=00:30:00 --job-name=ncclimo --pty bash\n" # Login node
    printf "chrysa: srun --nodes=1 --time=30 --job-name=ncclimo --pty bash\n" # Compute node
    printf "compy : srun -A e3sm --nodes=1 --time=00:30:00 --job-name=ncclimo --pty bash\n"
    printf "cooley: qsub -I -A ClimateEnergy_4 --nodecount=1 --time=00:30:00 --jobname=ncclimo\n"
    printf "cori  : srun -A e3sm -C haswell --nodes=1 --time=00:30:00 --partition=debug --job-name=ncclimo --pty bash\n"
    printf "cori  : srun -A e3sm -C knl --nodes=1 --time=00:30:00 --partition=debug --job-name=ncclimo --pty bash\n"
    printf "cori  : srun -A e3sm -C amd --nodes=1 --time=00:30:00 --job-name=ncclimo --pty bash # Bigmem\n"
    printf "\nComplete documentation at http://nco.sf.net/nco.html#${spt_nm}\n\n"
    exit 1
} # end fnc_usg_prn()

function trim_leading_zeros {
    # Purpose: Trim leading zeros from string representing an integer
    # Why, you ask? Because Bash treats zero-padded integers as octal!
    # This is surprisingly hard to workaround
    # My workaround is to remove leading zeros prior to arithmetic
    # Usage: trim_leading zeros ${sng}
    sng_trm=${1} # [sng] Trimmed string
    # Use Bash 2.X pattern matching to remove up to three leading zeros, one at a time
    sng_trm=${sng_trm##0} # NeR98 p. 99
    sng_trm=${sng_trm##0}
    sng_trm=${sng_trm##0}
    # If all zeros removed, replace with single zero
    if [ ${sng_trm} = '' ]; then 
	sng_trm='0'
    fi # endif
} # end trim_leading_zeros()

get_spt_drc () {
# SMB (20150814):
# Get calling script location to call other utilities in the PreAndPostProcessingScripts package
# Resolve symlinks in case script is linked elsewhere with technique from
# http://www.ostricher.com/2014/10/the-right-way-to-get-the-directory-of-a-bash-script
    spt_src="${BASH_SOURCE[0]}"
    # If ${spt_src} is a symlink, resolve it
    while [ -h "${spt_src}" ]; do
	spt_drc="$(cd -P "$(dirname "${spt_src}")" && pwd)"
        spt_src="$(readlink "${spt_src}")"
        # Resolve relative symlinks (no initial "/") against symlink base directory
        [[ ${spt_src} != /* ]] && spt_src="${spt_drc}/${spt_src}"
    done
    spt_drc="$(cd -P "$(dirname "${spt_src}")" && pwd)"
    echo ${spt_drc}
} # end get_spt_drc()

# Check argument number and complain accordingly
arg_nbr=$#
#printf "\ndbg: Number of arguments: ${arg_nbr}"
if [ ${arg_nbr} -eq 0 ]; then
  fnc_usg_prn
fi # !arg_nbr

# Parse command-line options:
# http://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options (see method by Adam Katz)
# http://tuxtweaks.com/2014/05/bash-getopts
while getopts :34567a:C:c:d:E:e:f:h:i:j:L:l:m:n:O:o:P:p:R:r:S:s:t:v:X:x:Y:y:-: OPT; do
    case ${OPT} in
	3) fl_fmt='3' ;; # File format
	4) fl_fmt='4' ;; # File format
	5) fl_fmt='5' ;; # File format
	6) fl_fmt='6' ;; # File format
	7) fl_fmt='7' ;; # File format
	a) dec_md_usr="${OPTARG}" ;; # December mode
	C) clm_md_usr="${OPTARG}" ;; # Climatology mode
	c) caseid="${OPTARG}" ;; # CASEID
	d) dbg_lvl="${OPTARG}" ;; # Debugging level
	E) yr_end_prv="${OPTARG}" ;; # End year previous
	e) yr_end="${OPTARG}" ;; # End year
	f) fml_nm_usr="${OPTARG}" ;; # Family name
	h) hst_nm="${OPTARG}" ;; # History tape name
	i) drc_in="${OPTARG}" ;; # Input directory
	j) job_usr="${OPTARG}" ;; # Job simultaneity
	L) dfl_lvl="${OPTARG}" ;; # Deflate level
	l) lnk_flg="${OPTARG}" ;; # Link E3SM to AMWG name
	m) mdl_nm_usr="${OPTARG}" ;; # Model name string
	n) nco_opt="${OPTARG} ${nco_opt}" ;; # NCO options
	o) drc_out_usr="${OPTARG}" ;; # Output directory
	O) drc_rgr_usr="${OPTARG}" ;; # Regridded directory
	P) prc_typ="${OPTARG}" ;; # Procedure type
	p) par_typ="${OPTARG}" ;; # Parallelism type
	R) rgr_opt_usr="${OPTARG}" ;; # Regridding options
	r) rgr_map="${OPTARG}" ;; # Regridding map (horizontal)
	S) yr_srt_prv="${OPTARG}" ;; # Start year previous
	s) yr_srt="${OPTARG}" ;; # Start year
	t) thr_usr="${OPTARG}" ;; # Thread number
	v) var_lst="${OPTARG}" ;; # Variables
	X) drc_xtn="${OPTARG}" ;; # Extended climo directory
	x) drc_prv="${OPTARG}" ;; # Previous climo directory
	Y) drc_rgr_xtn="${OPTARG}" ;; # Regridded extended climo directory
	y) drc_rgr_prv="${OPTARG}" ;; # Regridded previous climo directory
	z) ypf_max_usr="${OPTARG}" ;; # Years-per-output-file maximum
	-) LONG_OPTARG="${OPTARG#*=}"
	   case ${OPTARG} in
	       # Hereafter ${OPTARG} is long argument key, and ${LONG_OPTARG}, if any, is long argument value
	       # Long options with no argument, no short option counterpart
	       # Long options with argument, no short option counterpart
	       # Long options with short counterparts, ordered by short option key
	       area_nm=?* | area_name=?* | area=?* ) area_nm="${LONG_OPTARG}" ;; # # Area-variable name
	       clm_md=?* | climatology_mode=?* | climo_mode=?* ) clm_md_usr="${LONG_OPTARG}" ;; # -C # Climatology mode
	       caseid=?* | case_id=?* | case=?* ) caseid="${LONG_OPTARG}" ;; # -c # CASEID
	       d2f | d2s | dbl_flt | dbl_sgl | double_float ) d2f_flg='Yes'; d2f_opt='--d2f' ;; # # Convert double-precision fields to single-precision
	       d2f=?* | d2s=?* | dbl_flt=?* | dbl_sgl=?* | double_float=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # D2F
	       dec_md=?* | dcm_md=? | december_mode=?* | dec_mode=?* ) dec_md="${LONG_OPTARG}" ;; # -a # December mode
	       dbg_lvl=?* | dbg=?* | debug=?* | debug_level=?* ) dbg_lvl="${LONG_OPTARG}" ;; # -d # Debugging level
	       dfl_lvl=?* | deflate=?* | dfl=?* ) dfl_lvl="${LONG_OPTARG}" ;; # -L # Deflate level
	       dpf=?* | days_per_file=?* ) dpf="${LONG_OPTARG}" ;; # # Days-per-file
	       dpt_fl=?* | mpas_fl=?* | mpas_file=?* | depth_file=?* ) dpt_opt="--dpt_fl=${LONG_OPTARG}" ;; # # Depth file with refBottomDepth for MPAS ocean
	       yr_end_prv=?* | prv_yr_end=?* | previous_end=?* ) yr_end_prv="${LONG_OPTARG}" ;; # -E # End year previous
	       yr_end=?* | end_yr=?* | year_end=?* | end_year=?* | end=?* ) yr_end="${LONG_OPTARG}" ;; # -e # End year
	       fml_nm=?* | fml=?* | family_name=?* | family=?* ) fml_nm_usr="${LONG_OPTARG}" ;; # -f # Family name
	       hst_nm=?* | history_name=?* | history=?* ) hst_nm="${LONG_OPTARG}" ;; # -h # History tape name
	       drc_in=?* | in_drc=?* | dir_in=?* | in_dir=?* | input=?* ) drc_in="${LONG_OPTARG}" ;; # -i # Input directory
	       fl_fmt=?* | fmt_out=?* | file_format=?* | format_out=?* ) fl_fmt="${LONG_OPTARG}" ;; # # Output file format
	       glb_avg | global_average ) glb_avg='Yes' ;; # # Global average timeseries
	       glb_avg=?* | global_average=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Global average timeseries
	       hrz_dmn=?* | hrz_dmn_nm=?* | dmn_hrz=?* | horizontal_dimension=?* ) hrz_dmn="${LONG_OPTARG}" ;; # # Horizontal dimension name(s)
	       job_nbr=?* | job_number=?* | jobs=?* ) job_usr="${LONG_OPTARG}" ;; # -j # Job simultaneity
	       lnk_flg=?* | link_flag=?* ) lnk_flg="${LONG_OPTARG}" ;; # -l # Link E3SM to AMWG name
	       amwg_link | amwg_links | AMWG_link | AMWG_links ) lnk_flg='Yes' ;; # # Link E3SM to AMWG name
	       amwg_link=?* | amwg_links=?* | AMWG_link=?* | AMWG_links=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Link E3SM to AMWG name
	       no_amwg_link | no_amwg_links | no_amwg | no_AMWG_link | no_AMWG_links ) lnk_flg='No' ;; # # Link E3SM to AMWG name
	       no_amwg_link=?* | no_amwg_links=?* | no_amwg=?* | no_AMWG_link=?* | no_AMWG_links=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # -l # Link E3SM to AMWG name
	       mdl_nm=?* | mdl=?* | model_name=?* | model=?* ) mdl_nm_usr="${LONG_OPTARG}" ;; # -m # Model name string
	       mem_mb=?* | mmr_mb=?* | memory_mb=? | memory_megabytes=?* ) mem_mb="${LONG_OPTARG}" ;; # # Megabytes of RAM per srun job in Cori SLURM in MPI mode
	       mth_end=?* | end_mth=?* | month_end=?* | end_month=?* ) mth_end="${LONG_OPTARG}" ;; # # End month
	       mth_srt=?* | start_mth=?* | month_start=?* | start_month=?* ) mth_srt="${LONG_OPTARG}" ;; # # Start month
	       nco_opt=?* | nco=?* | nco_options=?* ) nco_opt="${LONG_OPTARG} ${nco_opt}" ;; # -n # NCO options
	       no_area | no_cll_msr | no_cell_measures ) no_cll_msr='Yes' ;; # # Omit cell_measures variables
	       no_area=?* | no_cell_msr=?* | no_cell_measures=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Omit cell_measures variables
	       no_frm_trm | no_frm | no_formula_terms ) no_frm_trm='Yes' ;; # # Omit formula_terms variables
	       no_frm_trm=?* | no_frm=?* | no_formula_terms=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Omit formula_terms variables
	       no_ntv_tms | no_ntv | no_native | no_native_timeseries | delete_native ) no_ntv_tms='Yes' ;; # # Omit native-grid split files
	       no_ntv_tms=?* | no_ntv=?* | no_native=?* | no_native_timeseries=?* | delete_native=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Omit native-grid split files
	       no_stg_grd | no_stg | no_stagger | no_staggered_grid ) no_stg_grd='Yes' ;; # # Omit staggered grid variables
	       no_stg_grd=?* | no_stg=?* | no_stagger=?* | no_staggered_grid ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Omit staggered grid variables
	       drc_out=?* | out_drc=?* | dir_out=?* | out_dir=?* | output=?* ) drc_out_usr="${LONG_OPTARG}" ;; # -o # Output directory
	       drc_rgr=?* | rgr_drc=?* | dir_regrid=?* | regrid_dir=?* | regrid=?* ) drc_rgr_usr="${LONG_OPTARG}" ;; # -O # Regridded directory
	       prc_typ=?* | procedure=?* ) prc_typ="${LONG_OPTARG}" ;; # -P # Procedure type
	       par_typ=?* | par_md=?* | parallel_type=?* | parallel_mode=?* | parallel=?* ) par_typ="${LONG_OPTARG}" ;; # -p # Parallelism type
	       ppc=?* | ppc_prc=?* | precision=?* | quantize=?* ) ppc_prc="${LONG_OPTARG}" ;; # # Precision-preserving compression
	       rgr_opt=?* | regrid_options=?* ) rgr_opt_usr="${LONG_OPTARG}" ;; # -R # Regridding options
	       rgr_map=?* | regrid_map=?* | map=?* ) rgr_map="${LONG_OPTARG}" ;; # -r # Regridding map (horizontal)
	       yr_srt_prv=?* | prv_yr_srt=?* | previous_start=?* ) yr_srt_prv="${LONG_OPTARG}" ;; # -S # Start year previous
	       yr_srt=?* | start_yr=?* | year_start=?* | start_year=?* | start=?* ) yr_srt="${LONG_OPTARG}" ;; # -s # Start year
	       seasons=?* | csn_lst=?* | csn=?* | season=?* ) csn_lst="${LONG_OPTARG}" ;; # # Seasons to output
	       sgs_frc=?* | ice_frc=?* | lnd_frc=?* | ocn_frc=?* | subgrid_fraction=?* ) sgs_frc="${LONG_OPTARG}" ;; # # Sub-grid fraction variable
	       no_stdin | no_inp_std | no_redirect | no_standard_input ) std_chk='No' ;; # # Check stdin for input file list
	       no_stdin=?* | no_inp_std=?* | no_redirect=?* | no_standard_input=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Check stdin for input file list
	       thr_nbr=?* | thr=?* | thread_number=?* | thread=?* | threads=?* ) thr_usr="${LONG_OPTARG}" ;; # -t # Thread number
	       tpd_out=?* | tpd=?* | timesteps_per_day=?* ) tpd_usr="${LONG_OPTARG}" ;; # # Timesteps-per-day in output
	       uio | unbuffered | share ) uio_flg='Yes' ;; # # Unbuffered I/O (NC_SHARE) for netCDF3 files
	       uio=?* | unbuffered=?* | share=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Unbuffered I/O (NC_SHARE) for netCDF3 files
	       var_lst=?* | variable_list=?* | var=?* | vars=?* | variable=?* | variables=?* ) var_lst="${LONG_OPTARG}" ;; # -v # Variables
	       var_xtr=?* | var_extra=?* | variables_extra=?* | extra_variables=?* ) var_xtr=",${LONG_OPTARG}" ;; # # Extra variables for splitter timeseries
	       version | vrs | config | configuration | cnf ) vrs_prn='Yes' ;; # # Print version information
	       version=?* | vrs=?* | config=?* | configuration=?* | cnf=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Print version information
	       vrt_fl=?* | vrt=?* | vrt_crd=?* | vertical_coordinate=?* ) vrt_fl="${LONG_OPTARG}" ;; # # Vertical coordinate file
	       vrt_xtr=?* | xtr_mth=?* | extrapolation_type=?* | extrapolation_method=?* ) vrt_xtr="${LONG_OPTARG}" ;; # # Vertical extrapolation type (mss_val|nrs_ngh)
	       drc_xtn=?* | xtn_drc=?* | extended_dir=?* | extended_climo=?* | extended=?* ) drc_xtn="${LONG_OPTARG}" ;; # -X # Extended climo directory
	       drc_prv=?* | prv_drc=?* | previous_dir=?* | previous_climo=?* | previous=?* ) drc_prv="${LONG_OPTARG}" ;; # -x # Previous climo directory
	       drc_rgr_xtn=?* | drc_xtn_rgr=?* | regridded_extended=?* | extended_regridded=?* ) drc_rgr_xtn="$
	       xcl_var | xcl | exclude | exclude_variables ) xcl_flg='Yes' ;; # # Exclude rather than extract variable list
	       xcl_var=?* | xcl=?* | exclude=?* | exclude_variables=?* ) echo "No argument allowed for --${OPTARG switch}" >&2; exit 1 ;; # # Exclude rather than extract variable list
{LONG_OPTARG}" ;; # -Y # Regridded extended climo directory
	       drc_rgr_prv=?* | drc_prv_rgr=?* | regridded_previous=?* | previous_regridded=?* ) drc_rgr_prv="${LONG_OPTARG}" ;; # -y # Regridded previous climo directory
	       ypf_max=?* | ypf=?* | years=?* | years_per_file=?* ) ypf_max_usr="${LONG_OPTARG}" ;; # -z # Years-per-output-file maximum
               '' ) break ;; # "--" terminates argument processing
               * ) printf "\nERROR: Unrecognized option ${fnt_bld}--${OPTARG}${fnt_nrm}\n" >&2; fnc_usg_prn ;;
	   esac ;; # !OPTARG
	\?) # Unrecognized option
	    printf "\nERROR: Option ${fnt_bld}-${OPTARG}${fnt_nrm} not recognized\n" >&2
	    fnc_usg_prn ;;
    esac # !OPT
done # !getopts
shift $((OPTIND-1)) # Advance one argument
psn_nbr=$#
if [ ${psn_nbr} -ge 1 ]; then
    inp_psn='Yes'
    # 20200430 Input files on command-line mean we need not check standard-input
    std_chk='No'
fi # !psn_nbr

if [ ${vrs_prn} = 'Yes' ]; then
    printf "${spt_nm}, the NCO climatology operator, version ${nco_vrs} \"${nco_sng}\"\n"
    printf "Copyright (C) 2016--present Charlie Zender\n"
    printf "This program is part of NCO, the netCDF Operators\n"
    printf "NCO is free software and comes with a BIG FAT KISS and ABSOLUTELY NO WARRANTY\n"
    printf "You may redistribute and/or modify NCO under the terms of the\n"
    printf "3-Clause BSD License with exceptions described in the LICENSE file\n"
    printf "BSD: https://opensource.org/licenses/BSD-3-Clause\n"
    printf "LICENSE: https://github.com/nco/nco/tree/master/LICENSE\n"
    printf "Config: ${spt_nm} script located in directory ${drc_spt}\n"
    printf "Config: NCO binaries located in directory ${drc_nco}, linked to netCDF library version ${lbr_vrs}\n"
    if [ "${hrd_pth_fnd}" = 'Yes' ]; then
	printf "Config: Employ NCO machine-dependent hardcoded paths/modules for ${HOSTNAME}. (If desired, turn-off NCO hardcoded paths with \"export NCO_PATH_OVERRIDE=No\").\n"
    else
	printf "Config: No hardcoded machine-dependent path/module overrides. (If desired, turn-on NCO hardcoded paths at supported national labs with \"export NCO_PATH_OVERRIDE=Yes\").\n"
    fi # !hrd_pth_fnd
    exit 0
fi # !vrs_prn

# Detect input on pipe to stdin:
# http://stackoverflow.com/questions/2456750/detect-presence-of-stdin-contents-in-shell-script
# http://unix.stackexchange.com/questions/33049/check-if-pipe-is-empty-and-run-a-command-on-the-data-if-it-isnt
# 20170119 "if [ ! -t 0 ]" tests whether unit 0 (stdin) is connected to terminal, not whether pipe has data
# Non-interactive batch mode (e.g., qsub, sbatch) disconnects stdin from terminal and triggers false-positives with ! -t 0
# 20170123 "if [ -p foo ]" tests whether foo exists and is a pipe or named pipe
# Non-interactive batch mode (i.e., sbatch) behaves as desired for -p /dev/stdin on SLURM
# Non-interactive batch mode (e.g., qsub) always returns true for -p /dev/stdin on PBS, leads to FALSE POSITIVES!
# This is because PBS uses stdin to set the job name
# Hence -p /dev/stdin test works everywhere tested except PBS non-interactive batch environment
# Check stdin if user has not explicitly disallowed it with --no_stdin
if [ "${std_chk}" = 'Yes' ]; then
    if [ -n "${PBS_ENVIRONMENT}" ]; then
	if [ "${PBS_ENVIRONMENT}" = 'PBS_BATCH' ]; then
	    # PBS batch detection suggested by OLCF ticket CCS #338970 on 20170127
	    bch_pbs='Yes'
	fi # !PBS_ENVIRONMENT
    fi # !PBS
    if [ -n "${SLURM_JOBID}" ] && [ -z "${SLURM_PTY_PORT}" ]; then
	# SLURM batch detection suggested by NERSC ticket INC0096873 on 20170127
	bch_slr='Yes'
    fi # !SLURM
    if [ ${bch_pbs} = 'Yes' ] || [ ${bch_slr} = 'Yes' ]; then
	# Batch environment
	if [ ${bch_pbs} = 'Yes' ]; then
	    if [ ! -p /dev/stdin ]; then
		# PBS batch jobs cause -p to return true except for stdin redirection 
		# When -p returns true we do not know whether stdin pipe contains any input
		# User must explicitly indicate use of stdin pipes with --stdin option
		# Redirection in PBS batch jobs unambiguously causes -p to return false
		inp_std='Yes'
	    fi # !stdin
	fi # !bch_slr
	if [ ${bch_slr} = 'Yes' ]; then
	    if [ -p /dev/stdin ]; then
		# SLURM batch jobs cause -p to return true for stdin pipes
		# When -p returns false we do not know whether output was redirectd
		# User must explicitly indicate use of redirection with --stdin option
		# Stdin pipes in SLURM batch jobs unambiguously cause -p to return true
		inp_std='Yes'
	    fi # !stdin
	fi # !bch_slr
    else # !bch
	# Interactive environment
	if [ -p /dev/stdin ] || [ ! -t 0 ]; then
	    # Interactive environments unambiguously cause -p to return true for stdin pipes
	    # Interactive environments unambiguously cause -t 0 to return false for stdin redirection
	    inp_std='Yes'
	fi # !stdin
    fi # !bch
    if [ ${inp_std} = 'Yes' ] && [ ${inp_psn} = 'Yes' ]; then
	echo "${spt_nm}: ERROR expecting input from both stdin and positional command-line arguments"
	exit 1
    fi # !inp_std
fi # !std_chk

# Determine mode first (this helps determine other defaults)
if [ -n "${yr_srt_prv}" ]; then
    # Specifying only yr_srt_prv implies incremental method
    # Specifying both yr_srt_prv and yr_end_prv implies binary method
    xtn_flg='Yes'
    if [ -n "${yr_end_prv}" ]; then
	bnr_flg='Yes'
    else # !yr_end_prv binary method
	ncr_flg='Yes'
    fi # !yr_end_prv binary method
fi # !yr_srt_prv extended climo
# Utilize user-specified model name, if any
if [ -n "${prc_typ}" ]; then
    # 20201016: Supercede mdl_nm with prc_typ so both need not be explicitly invoked?
    # EAM/ELM are difficult since history file name can be *cam*/*clm2* or *eam*/*elm*
    # Instinct is to change mdl_nm so it only refers to name string in history file
    # and must be provided only when that name string differs from default name string for prc_typ
    prc_opt="-P ${prc_typ}"
    mdl_nm="${prc_typ}"
    # Exceptions to rule that mdl_nm == prc_typ
    [[ "${prc_typ}" = 'clm' ]] && mdl_nm='clm2'
fi # !prc_typ
if [ -n "${mdl_nm_usr}" ]; then 
    mdl_nm="${mdl_nm_usr}"
fi # !mdl_nm_usr
if [ -n "${dec_md_usr}" ]; then 
    if [ "${dec_md_usr}" = 'djf' ] || [ "${dec_md_usr}" = 'DJF' ] || [ "${dec_md_usr}" = 'scd' ] || [ "${dec_md_usr}" = 'seasonally_contiguous_december' ] ; then
	dec_md='scd'
    elif [ "${dec_md_usr}" = 'jfd' ] || [ "${dec_md_usr}" = 'JFD' ] || [ "${dec_md_usr}" = 'sdd' ] || [ "${dec_md_usr}" = 'seasonally_discontiguous_december' ] ; then
	dec_md='sdd'
    fi # !dec_md_usr
fi # !dec_md_usr
if [ -n "${clm_md_usr}" ]; then 
    # Climo mode must be explicitly selected with --clm_md when climo input files are piped or positional
    if [ "${clm_md_usr}" = 'ann' ] || [ "${clm_md_usr}" = 'annual' ] || [ "${clm_md_usr}" = 'yearly' ] || [ "${clm_md_usr}" = 'year' ] ; then
	clm_md_usr='ann'
    fi # !clm_md_usr
    if [ "${clm_md_usr}" = 'dly' ] || [ "${clm_md_usr}" = 'daily' ] || [ "${clm_md_usr}" = 'doy' ] || [ "${clm_md_usr}" = 'day' ] ; then
	clm_md_usr='dly'
    fi # !clm_md_usr
    if [ "${clm_md_usr}" = 'hfc' ] || [ "${clm_md_usr}" = 'high_frequency_climo' ] || [ "${clm_md_usr}" = 'hgh_frq_clm' ] ; then
	clm_md_usr='hfc'
    fi # !clm_md_usr
    if [ "${clm_md_usr}" = 'hfs' ] || [ "${clm_md_usr}" = 'high_frequency_splitter' ] || [ "${clm_md_usr}" = 'hgh_frq_spl' ] ; then
	clm_md_usr='hfs'
    fi # !clm_md_usr
    if [ "${clm_md_usr}" = 'mth' ] || [ "${clm_md_usr}" = 'month' ] || [ "${clm_md_usr}" = 'monthly' ] ; then
	clm_md_usr='mth'
    fi # !clm_md_usr
    clm_md="${clm_md_usr}"
fi # !clm_md_usr
if [ "${clm_md}" = 'hfc' ] || [ "${clm_md}" = 'mth' ]; then
    clm_hfc_or_mth='Yes'
fi # !clm_md
if [ "${clm_md}" = 'ann' ] || [ "${clm_md}" = 'dly' ]; then
    clm_flg='Yes'
fi # !clm_md
if [ "${clm_md}" = 'dly' ] || [ "${clm_md}" = 'hfc' ]; then
    unset dpm # Days per month
    declare -a dpm
    dpm=(0 31 28 31 30 31 30 31 31 30 31 30 31) # 365-day calendar, 1-based indexing
fi # !clm_md
if [ -z "${drc_in}" ]; then
    drc_in="${drc_pwd}"
else # !drc_in
    if [ ! -d "${drc_in}" ]; then
	echo "${spt_nm}: ERROR specified input directory \"${drc_in}\" does not exist"
	exit 1
    fi # !drc_in
    drc_in_usr_flg='Yes'
fi # !drc_in

if [ "${clm_md}" = 'hfs' ] || [ "${clm_md}" = 'mth' ] ; then
    # Subset (split) mode whenever stdin pipe has data or positional arguments are used
    if [ ${inp_std} = 'Yes' ] || [ ${inp_psn} = 'Yes' ] || [ "${clm_md}" = 'hfs' ]; then
	sbs_flg='Yes'
    fi # !sbs_flg
    # fxm: If normal resolution (monthly, not HFS) input files are specified by -i drc_in must also specify --ypf to turn-on splitter mode
    # Otherwise it is ambiguous whether to generate climatology or to split
    if [ -n "${ypf_max_usr}" ]; then 
	ypf_max=${ypf_max_usr}
	sbs_flg='Yes'
    fi # !ypf_max_usr
    if [ "${sbs_flg}" = 'Yes' ]; then 
	clm_flg='No'
	dec_md='sdd'
	if [ -z "${mdl_nm_usr}" ]; then 
	    mdl_nm='nil' # Unset default model name for stdin pipe and positional arguments, otherwise default mdl_nm could be used to add model-specific variables to var_xtr
	fi # !mdl_nm_usr
	# Glob files in splitter mode if other input avenues were not used
	if [ ${inp_psn} = 'No' ] && [ ${inp_std} = 'No' ] && [ "${drc_in_usr_flg}" = 'Yes' ]; then
	    inp_glb='Yes'
	fi # !inp_*
    fi # !sbs_flg
fi # !clm_md
if [ ${clm_flg} = 'Yes' ] && [ ${clm_md} = 'dly' ] && [ ${inp_psn} = 'No' ] && [ ${inp_std} = 'No' ] && [ "${drc_in_usr_flg}" = 'Yes' ]; then
    inp_glb='Yes'
fi # !clm_flg, !dly

# Read files from stdin pipe, positional arguments, or directory glob
#printf "dbg: inp_aut  = ${inp_aut}\n"
#printf "dbg: inp_glb  = ${inp_glb}\n"
#printf "dbg: inp_psn  = ${inp_psn}\n"
#printf "dbg: inp_std  = ${inp_std}\n"

# Derived variables
if [ -n "${drc_out_usr}" ]; then
    # Fancy %/ syntax removes trailing slash (e.g., from $TMPDIR)
    drc_out="${drc_out_usr%/}"
fi # !drc_out_usr
if [ -n "${drc_rgr_usr}" ]; then 
    drc_rgr="${drc_rgr_usr%/}"
else 
    drc_rgr="${drc_out%/}"
fi # !drc_rgr_usr
if [ -n "${drc_prv}" ]; then
    drc_prv="${drc_prv%/}"
else
    if [ "${bnr_flg}" = 'Yes' ]; then
	drc_prv="${drc_in}"
    fi # !bnr_flg
    if [ "${ncr_flg}" = 'Yes' ]; then
	drc_prv="${drc_out}"
    fi # !ncr_flg
fi # !drc_prv
if [ -n "${drc_xtn}" ]; then
    drc_xtn="${drc_xtn%/}"
else
    drc_xtn="${drc_prv}"
fi # !drc_xtn

# Doubly-derived variables
if [ -n "${drc_rgr_prv}" ]; then
    drc_rgr_prv="${drc_rgr_prv%/}"
else
    drc_rgr_prv="${drc_prv%/}"
fi # !drc_rgr_prv
if [ -n "${drc_rgr_xtn}" ]; then
    drc_rgr_xtn="${drc_rgr_xtn%/}"
else
    drc_rgr_xtn="${drc_xtn%/}"
fi # !drc_rgr_xtn

# Create output directories
if [ -n "${drc_out}" ] && [ ! -d "${drc_out}" ]; then 
    cmd_mkd="mkdir -p ${drc_out}"
    eval ${cmd_mkd}
    if [ "$?" -ne 0 ]; then
	printf "${spt_nm}: ERROR Failed to create output directory. Debug this:\n${cmd_mkd}\n"
	printf "${spt_nm}: HINT Creating a directory requires proper write permissions\n"
	exit 1
    fi # !err
fi # !drc_out
if [ -n "${drc_rgr}" ] && [ ! -d "${drc_rgr}" ]; then 
    cmd_mkd="mkdir -p ${drc_rgr}"
    eval ${cmd_mkd}
    if [ "$?" -ne 0 ]; then
	printf "${spt_nm}: ERROR Attempt to create regrid directory. Debug this:\n${cmd_mkd}\n"
	printf "${spt_nm}: HINT Creating a directory requires proper write permissions\n"
	exit 1
    fi # !err
fi # !drc_rgr

# Determine first full year
trim_leading_zeros ${yr_srt}
yr_srt_rth=${sng_trm}
yyyy_srt=`printf "%04d" ${yr_srt_rth}`
let yr_srtm1=${yr_srt_rth}-1
trim_leading_zeros ${yr_end}
yr_end_rth=${sng_trm}
yyyy_end=`printf "%04d" ${yr_end_rth}`
let yr_endm1=${yr_end_rth}-1
let yr_endp1=${yr_end_rth}+1
let yr_nbr=${yr_end_rth}-${yr_srt_rth}+1

if [ ${mth_srt} -eq '0' ]; then
    echo "${spt_nm}: ERROR User-defined start month is ${mth_srt}. Please use 1-based (not 0-based) indexing for months, where [Jan..Dec] = [1..12]"
    exit 1
fi # !mth_srt
trim_leading_zeros ${mth_srt}
mth_srt_rth=${sng_trm}
mm_srt=`printf "%02d" ${mth_srt_rth}`
trim_leading_zeros ${mth_end}
mth_end_rth=${sng_trm}
mm_end=`printf "%02d" ${mth_end_rth}`
let mth_nbr=${yr_end_rth}*12+${mth_end_rth}-${yr_srt_rth}*12-${mth_srt_rth}+1
let mth_srtm1=${mth_srt_rth}-1
let mth_endp1=${mth_end_rth}+1
if [ ${mth_srtm1} -eq 0 ] ; then
    mm_srtm1='12'
else # !mth_srtm1
    mm_srtm1=`printf "%02d" ${mth_srtm1}`
fi # !mth_srtm1
if [ ${mth_endp1} -eq 13 ] ; then
    mm_endp1='01'
else # !mth_endp1
    mm_endp1=`printf "%02d" ${mth_endp1}`
fi # !mth_endp1
if [ "${mm_srt}" != '01' ] || [ "${mm_end}" != '12' ]; then
    mth_flg='Yes'
fi # !mm_srt
    
# Derived variables
out_nm=${caseid}
if [ "${caseid}" = 'hist' ] || [ "${mdl_nm}" = 'mali' ] || [ "${mdl_nm}" = 'mpas' ] || [ "${mdl_nm}" = 'mpaso' ] || [ "${mdl_nm}" = 'mpas-ocean' ] || [ "${mdl_nm}" = 'mpascice' ] || [ "${mdl_nm}" = 'mpasseaice' ] || [ "${mdl_nm}" = 'mpas-seaice' ] || [ "${mdl_nm}" = 'mpassi' ] || [ "${prc_typ}" = 'mali' ] || [ "${prc_typ}" = 'mpas' ] || [ "${prc_typ}" = 'mpaso' ] || [ "${prc_typ}" = 'mpas-ocean' ] || [ "${prc_typ}" = 'mpascice' ] || [ "${prc_typ}" = 'mpasseaice' ] || [ "${prc_typ}" = 'mpas-seaice' ] || [ "${prc_typ}" = 'mpassi' ]; then
    mdl_typ='mpas'
    nm_typ='mpas'
    out_nm="${mdl_nm}"
    hst_nm='hist'
fi # !caseid
if [ "${mdl_typ}" = 'mpas' ] && [ -z "${prc_opt}" ] ; then
    prc_typ='mpas'
    prc_opt='-P mpas'
fi # !mdl_typ && prc_opt
# http://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash
# http://stackoverflow.com/questions/17420994/bash-regex-match-string
# NB: Order and 'elif' ensure name matches only fullest rx
if [[ "${caseid}" =~ ^(.*)([0-9][0-9][0-9][0-9][01][0-9].nc.?)$ ]]; then
    nm_typ='yyyymm'
elif [[ "${caseid}" =~ ^(.*)([0-9][0-9][0-9][0-9]-[01][0-9]-01.nc.?)$ ]]; then
    nm_typ='yyyy-mm-01'
elif [[ "${caseid}" =~ ^(.*)([0-9][0-9][0-9][0-9]-[01][0-9]-01-00000.nc.?)$ ]]; then
    nm_typ='yyyy-mm-01-00000'
elif [[ "${caseid}" =~ ^(.*)([0-9][0-9][0-9][0-9]-[01][0-9].nc.?)$ ]]; then
    nm_typ='yyyy-mm'
fi # !caseid
if [ "${nm_typ}" = 'yyyymm' ]  || [ "${nm_typ}" = 'yyyy-mm' ] || [ "${nm_typ}" = 'yyyy-mm-01' ] || [ "${nm_typ}" = 'yyyy-mm-01-00000' ]; then
    bs_nm="${BASH_REMATCH[1]}"
    bs_nm="$(basename ${bs_nm})"
    bs_nm="${bs_nm%.*}"
    bs_nm="${bs_nm%_*}"
    out_nm=${bs_nm}
    # https://stackoverflow.com/questions/12426659/how-extract-last-part-of-string-in-bash
    bs_sfx="${caseid##*.}"
fi # !nm_typ
if [ -n "${fml_nm_usr}" ]; then 
    fml_nm="${fml_nm_usr}"
    out_nm="${fml_nm}"
fi # !fml_nm
if [ "${clm_md}" != 'ann' ] && [ "${clm_md}" != 'dly' ] && [ "${clm_md}" != 'hfc' ] && [ "${clm_md}" != 'hfs' ] && [ "${clm_md}" != 'mth' ]; then 
    echo "${spt_nm}: ERROR User-defined climatology mode is ${clm_md}. Valid options are 'ann', 'dly', hfc', 'hfs', and 'mth' (default)"
    exit 1
fi # !clm_md
if [ "${clm_md}" = 'ann' ]; then 
    clm_nbr=1
    dec_md='sdd'
elif [ "${clm_md}" = 'dly' ]; then 
    clm_nbr=${dpy}
    dec_md='sdd'
elif [ "${clm_hfc_or_mth}" = 'Yes' ]; then 
    clm_nbr=12
    csn_flg='Yes'
    if [ ${csn_lst} = 'none' ]; then
	csn_nbr=0
	csn_flg='No'
    fi # !csn_lst
fi # !clm_md
if [ -n "${gaa_sng_std}" ]; then
    if [ "${yr_nbr}" -gt 1 ] ; then
	yrs_avg_sng="${yr_srt}-${yr_end}"
    else
	yrs_avg_sng="${yr_srt}"
    fi # !yr_nbr
    # NB: E3SM diagnostics (e3sm_diagnostics) relies on this attribute
    if [ "${sbs_flg}" != 'Yes' ]; then
	gaa_sng="${gaa_sng_std} --gaa yrs_averaged=${yrs_avg_sng}"
    fi # !sbs_flg
else
    echo "${spt_nm}: INFO User-defined global attributes string is empty. Diagnostics routines (such as E3SM Diagnostics) that depend on these attributes may not work."
fi # !gaa_sng
if [ -n "${job_usr}" ]; then 
    job_nbr="${job_usr}"
fi # !job_usr
if [ -n "${fl_fmt}" ]; then
    if [ "${fl_fmt}" = '3' ] || [ "${fl_fmt}" = 'classic' ] || [ "${fl_fmt}" = 'netcdf3' ]; then
	nco_fl_fmt='--fl_fmt=classic'
    elif [ "${fl_fmt}" = '4' ] || [ "${fl_fmt}" = 'netcdf4' ] || [ "${fl_fmt}" = 'hdf5' ]; then
	nco_fl_fmt='--fl_fmt=netcdf4'
    elif [ "${fl_fmt}" = '5' ] || [ "${fl_fmt}" = '64bit_data' ] || [ "${fl_fmt}" = 'cdf5' ]; then
	nco_fl_fmt='--fl_fmt=64bit_data'
    elif [ "${fl_fmt}" = '6' ] || [ "${fl_fmt}" = '64bit_offset' ] || [ "${fl_fmt}" = '64' ]; then
	nco_fl_fmt='--fl_fmt=64bit_offset'
    elif [ "${fl_fmt}" = '7' ] || [ "${fl_fmt}" = 'netcdf4_classic' ]; then
	nco_fl_fmt='--fl_fmt=netcdf4_classic'
    else
	echo "${spt_nm}: ERROR User-supplied file-format specifier fl_fmt='${fl_fmt}' is invalid"
	echo "${spt_nm}: HINT Valid format-specifiers include '3', '4', '5', '6', and '7'"
	exit 1
    fi # !fl_fmt
    nco_opt="${nco_fl_fmt} ${nco_opt}"
fi # !fl_fmt
if [ ${dbg_lvl} -ge 2 ]; then
    nco_opt="--dbg_lvl=${dbg_lvl} ${nco_opt}"
fi # !dbg_lvl
if [ -n "${var_lst}" ] && [ "${sbs_flg}" != 'Yes' ]; then
    if [ "${xcl_flg}" = 'Yes' ]; then
	nco_opt="${nco_opt} -x"
    fi # !xcl_flg
    nco_opt="${nco_opt} -v ${var_lst}"
fi # !var_lst
if [ -n "${ppc_prc}" ]; then
    nco_opt="${nco_opt} --ppc default=${ppc_prc}"
fi # !ppc_prc
if [ -n "${dfl_lvl}" ]; then
    nco_opt="${nco_opt} --dfl_lvl=${dfl_lvl}"
fi # !dfl_lvl
if [ -n "${hdr_pad}" ]; then
    nco_opt="${nco_opt} --hdr_pad=${hdr_pad}"
fi # !hdr_pad
if [ -n "${uio_flg}" ]; then
    nco_opt="${nco_opt} --unbuffered_io"
fi # !ppc_prc
if [ "${no_cll_msr}" = 'Yes' ]; then 
    spl_opt="${spl_opt} --no_cll_msr"
fi # !no_cll_msr
if [ "${no_frm_trm}" = 'Yes' ]; then 
    spl_opt="${spl_opt} --no_frm_trm"
fi # !no_frm_trm
if [ "${no_stg_grd}" = 'Yes' ]; then 
    spl_rgr_opt=''
fi # !no_stg_grd
if [ "${par_typ}" = ${par_bck} ] || [[ "${par_typ}" =~ [bB]ck ]] || [[ "${par_typ}" =~ [bB]ackground ]]; then 
    par_typ=${par_bck}
    par_opt=' &'
elif [ "${par_typ}" = ${par_mpi} ] || [[ "${par_typ}" =~ (mpi|MPI) ]]; then 
    par_typ=${par_mpi}
    par_opt=' &'
    mpi_flg='Yes'
elif [ "${par_typ}" = ${par_srl} ] || [ "${par_typ}" = 'srl' ] || [[ "${par_typ}" =~ [sS]erial ]] || [[ "${par_typ}" =~ [nN]il ]] || [[ "${par_typ}" =~ [nN]one ]]; then 
    par_typ=${par_srl}
else 
    echo "ERROR: Invalid -p par_typ option = ${par_typ}"
    echo "HINT: Valid par_typ arguments include '${par_bck}' (or 'bck'), '${par_mpi}' (or 'MPI'), and '${par_srl}' (or 'srl' or 'nil' or 'none'). For background parallelism, select '${par_bck}' which causes ${spt_nm} to spawn parallel processes as background tasks on a single node. For MPI parallelism, select '${par_mpi}' which causes ${spt_nm} to spawn parallel processes on across available cluster nodes. For no parallelism (aka serial mode), select '${par_srl}', which causes ${spt_nm} to spawn all processes serially on a single compute node."
    exit 1
fi # !par_typ
if [ -n "${rgr_opt_usr}" ]; then
    rgr_opt="${rgr_opt_usr}"
    rmp_opt="-R \'${rgr_opt_usr}\'"
fi # !rgr_opt_usr
if [ -n "${rgr_map}" ]; then 
    if [ ! -f "${rgr_map}" ]; then
	echo "${spt_nm}: ERROR Unable to find specified regrid map ${rgr_map}"
	echo "${spt_nm}: HINT Supply the full path-name for the regridding map"
	exit 1
    fi # ! -f
    if [ "${mdl_nm}" = 'clm2' ] || [ "${mdl_nm}" = 'elm' ] && [ -z "${sgs_frc}" ]; then
	printf "${spt_nm}: WARNING ${spt_nm} CLM/ELM output typically uses sub-gridscale (SGS) fractions that can only be properly renormalized if the SGS variable option to ncclimo is invoked, but that option was not invoked\n"
	printf "${spt_nm}: HINT Provide ${spt_nm} with the SGS fraction variable using the \"--sgs_frc=\${sgs_frc_nm}\" option, otherwise the regridded data will likely not be properly or conservatively regridded\n"
    fi # !mdl_nm
    map_opt="--map=${rgr_map}"
    rgr_opt="${rgr_opt} ${map_opt}"
    rmp_opt="${rmp_opt} ${map_opt}"
else # !rgr_map
    if [ -n "${dpt_opt}" ]; then
	echo "${spt_nm}: ERROR Cannot add depth coordinate unless horizontal regridding is invoked"
	echo "${spt_nm}: HINT Specify a regridding map or run add_depth.py on these files yourself. To request that add_depth.py work directly in ncclimo, contact Charlie."
	exit 1
    fi # !dpt_opt
fi # !rgr_map
if [ "${sbs_flg}" = 'Yes' ]; then
    # 20190419: Splitter typically writes 1--3 multi-dimensional fields into each output file
    # OpenMP incurs significant RAM overhead that may not be worthwhile
    # New default is 1 regridding thread for splitter 
    # Users can override this splitter-only default by explicitly requesting multiple threads
    thr_nbr=1 # [nbr] Thread number for regridder
fi # !sbs_flg
if [ -n "${sgs_frc}" ]; then
    sgs_opt="--rgr sgs_frc_nm=${sgs_frc}"
    rgr_opt="${rgr_opt} ${sgs_opt}"
    rmp_opt="--sgs_frc=${sgs_frc} ${rmp_opt}"
fi # !sgs_frc
if [ -n "${thr_usr}" ]; then
    thr_nbr="${thr_usr}"
fi # !thr_usr
if [ -n "${tpd_usr}" ]; then 
    tpd_out="${tpd_usr}"
fi # !tpd_usr
if [ -n "${vrt_fl}" ]; then
    if [ ! -f "${vrt_fl}" ]; then
	echo "ERROR: Unable to find specified vertical coordinate file ${vrt_fl}"
	exit 1
    fi # ! -f
    vrt_opt="--vrt_fl=${vrt_fl}"
    rmp_opt="--vrt_fl=${vrt_fl} ${rmp_opt}"
    if [ -n "${vrt_xtr}" ]; then
	if [ ${vrt_xtr} = 'mss_val' ] || [ ${vrt_xtr} = 'missing_value' ] || [ ${vrt_xtr} = 'msv' ]; then 
	    vrt_opt="--rgr xtr_mth=mss_val ${vrt_opt}"
	    rmp_opt="--xtr_mth=mss_val ${rmp_opt}"
	elif [ ${vrt_xtr} = 'nrs_ngh' ] || [ ${vrt_xtr} = 'nearest_neighbor' ] || [ ${vrt_xtr} = 'nn' ]; then 
	    vrt_opt="--rgr xtr_mth=nrs_ngh ${vrt_opt}"
	    rmp_opt="--xtr_mth=nrs_ngh ${rmp_opt}"
	else 
	    echo "${spt_nm}: ERROR ${vrt_xtr} is not a valid extrapolation method"
	    echo "${spt_nm}: HINT Valid vertical extrapolation methods and synonyms are mss_val,missing_value,msv | nrs_ngh,nearest_neighbor,nn"
	    exit 1
	fi # !vrt_xtr
    fi # !vrt_xtr
    rgr_opt="${rgr_opt} ${vrt_opt}"
fi # !vrt_fl
yyyy_clm_srt=${yyyy_srt}
yyyy_clm_end=${yyyy_end}
yyyy_clm_srt_dec=${yyyy_srt}
yyyy_clm_end_dec=${yyyy_end}
mm_ann_srt='01' # [idx] First month used in annual climatology
mm_ann_end='12' # [idx] Last  month used in annual climatology
mm_djf_srt='01' # [idx] First month used in DJF climatology
mm_djf_end='12' # [idx] Last  month used in DJF climatology
yr_cln=${yr_nbr} # [nbr] Calendar years in climatology
if [ ${dec_md} = 'scd' ]; then 
    yyyy_clm_srt_dec=`printf "%04d" ${yr_srtm1}`
    yyyy_clm_end_dec=`printf "%04d" ${yr_endm1}`
    mm_ann_srt='12'
    mm_ann_end='11'
    mm_djf_srt='12'
    mm_djf_end='02'
    let yr_cln=${yr_cln}+1
fi # !scd

# clm_flg and sbs_flg are now fixed so finalize input file options then gather input filenames
if [ ${sbs_flg} = 'Yes' ] && [ ${inp_glb} = 'No' ] && [ ${inp_psn} = 'No' ] && [ ${inp_std} = 'No' ]; then
    echo "${spt_nm}: ERROR Specify input file(s) with -i \$drc_in or with positional argument(s) or with stdin"
    if [ ${bch_pbs} = 'Yes' ]; then
	echo "${spt_nm}: HINT PBS batch job environment detected, pipe to stdin not allowed, try positional arguments instead"
    else # !bch_pbs
	echo "${spt_nm}: HINT Pipe input file list to stdin with, e.g., 'ls *.nc | ${spt_nm}'"
    fi # !bch_pbs
    exit 1
fi # !sbs_flg
if [ ${clm_flg} = 'Yes' ] && [ ${clm_md} = 'ann' ]; then
    inp_aut='Yes'
    inp_std='No' # fxm: 20170123 hack for false positives in non-interactive batch mode on PBS
fi # !clm_flg
if [ ${clm_flg} = 'Yes' ] && [ ${clm_md} = 'mth' ]; then
    inp_aut='Yes'
    inp_std='No' # fxm: 20170123 hack for false positives in non-interactive batch mode on PBS
fi # !clm_flg
if [ ${inp_glb} = 'Yes' ]; then 
    for fl in "${drc_in}"/*.nc "${drc_in}"/*.nc3 "${drc_in}"/*.nc4 "${drc_in}"/*.nc5 "${drc_in}"/*.nc6 "${drc_in}"/*.nc7 "${drc_in}"/*.cdf "${drc_in}"/*.hdf "${drc_in}"/*.he5 "${drc_in}"/*.h5 ; do
	if [ -f "${fl}" ]; then
	    fl_in[${fl_nbr}]=${fl}
	    let fl_nbr=${fl_nbr}+1
	fi # !file
    done
fi # !inp_glb

if [ ${inp_psn} = 'Yes' ]; then
    # Read any positional arguments
    for ((psn_idx=1;psn_idx<=psn_nbr;psn_idx++)); do
	fl_in[(${psn_idx}-1)]=${!psn_idx}
	fl_nbr=${psn_nbr}
    done # !psn_idx
fi # !inp_psn
if [ ${inp_std} = 'Yes' ]; then
    # Input awaits on unit 0, i.e., on stdin
    while read -r line; do # NeR05 p. 179
	fl_in[${fl_nbr}]=${line}
	let fl_nbr=${fl_nbr}+1
    done < /dev/stdin
fi # !inp_std

if [ ${clm_md} = 'hfc' ] || [ ${clm_md} = 'hfs' ]; then
    if [ -z "${tpd_usr}" ]; then 
	# User requested high-frequency mode and did not specify tpd so (try to) infer it
	# Examine bounds of second timestep in case first timestep is instantaneous (e.g., restart values)
	tm_var='time'
	tm_bnds=`ncks --trd -M -m -v ${tm_var} ${fl_in[0]} | grep -E -i "^${tm_var} attribute [0-9]+: bounds" | cut -f 11- -d ' ' | sed 's/^ *//g'`
	# 20201220: Using %g not %f in format results in tpd=10 not tpd=8 for 3-hourly EAM data!
	# 20210815: Prefer to hyperslab second time index since some files contain an "instantaneous" (i.e., zero-duration) first timestep.
	# However this presumes file has multiple timesteps---fails with monthly data
	# 20210902: Use first time index if file has only one timestep
	tm_nbr=`ncks --trd -M ${fl_in[0]} | grep -E -i "^Root record dimension 0:" | cut -f 10- -d ' '`
	[ "${tm_nbr}" -gt 1 ] 2>/dev/null && tm_idx='1' || tm_idx='0'
	tpd_out=`ncks --trd -H -s '%25.15f ' -v ${tm_bnds} -d ${tm_var},${tm_idx} -C ${fl_in[0]} | awk '{ print 1.0/($2-$1); exit }'`
	# Ensure result is positive integer
	[ "${tpd_out}" -ge 0 ] 2>/dev/null && tpd_ntg='Yes' || tpd_ntg='No'
	if [ "${tpd_ntg}" != 'Yes' ]; then
	    printf "${spt_nm}: ERROR Inferred value of tpd=${tpd_out} is not a positive integer. User requested high-frequency climatology or splitter operations without specifying the number of timesteps per day (tpd) in the input files, so ${spt_nm} tried to infer tpd from the first input file. This operation resulted in a nonsense value. This is not surprising since a number of assumptions must hold true for the inferral to work correctly.\nHINT: Explicitly specify the number of timesteps-per-day in every input file with the --tpd option.\n"
	    exit 1
	fi # !tpd_ntg
    fi # !tpd_usr
fi # !hfc && !hfs

# Parse grid/map arguments before in_fl arguments so we know whether this could be a map-only invocation
if [ "${sbs_flg}" = 'Yes' ]; then

    # 20210823: Pre-process extraction list if it contains a regular expression character
    # Match var_lst against first file contents to create real extraction list
    # Only necessary in subset mode to extract distinct timeseries
    # https://stackoverflow.com/questions/26621736/how-to-check-if-a-string-contains-a-special-character
    if [ -n "${var_lst}" ] && [[ "${var_lst}" == *[\^\$\+\?\.\&\(\)\[\]\{\}\|]* ]]; then
	var_lst=`ncks -C -v ${var_lst} --lst_xtr ${fl_in[0]}`
    fi # !var_lst
    
    if [ -z "${var_lst}" ]; then
	echo "${spt_nm}: WARNING Splitter mode without explicitly specified variable list (i.e., -v var_lst) splits all variables of rank >= 2 into separate files, thus doubling the on-disk data amount"
	var_lst=`ncks --lst_rnk_ge2 ${fl_in[0]}`
    fi # !var_lst
    # http://stackoverflow.com/questions/27702452/loop-through-a-comma-separated-shell-variable
    var_nbr=0 # [sng] Split (subset) files
    for var in ${var_lst//,/ }; do
	# NB:
	var_sbs[${var_nbr}]=${var}
	let var_nbr=${var_nbr}+1
    done # !var_lst

    if [ -z "${job_usr}" ]; then 
	job_nbr=${var_nbr}
    fi # !job_usr

    # Input files per year
    flg_dct='Yes' # [flg] Double-check time in years against file count
    if [ "${clm_md}" = 'ann' ]; then
	fpy=1
    elif [ "${clm_md}" = 'dly' ]; then
	dpf=1
	let fpy=${dpy}/${dpf}
    elif [ "${clm_md}" = 'hfc' ]; then
	fpy=12
	flg_dct='No'
    elif [ "${clm_md}" = 'hfs' ]; then
	# CESM/E3SM high-frequency output often employs noleap calendar and 30 days per file (no matter how many timesteps per day)
	if [ -z "${dpf}" ]; then
	    # 20210329 Automatically infer dpf from first input file
	    # E3SM/CESM high-frequency timeseries (e.g., h4 files) are all equal # timesteps, not duration
	    # First timestep in first file (often? always?) is of zero duration and contains the IC
	    # Remaining timesteps in first file, and all timesteps in following files, is of normal duration
	    # For example 3-hourly E3SM/CESM output files of 120 timesteps each are all 30 days duration except the first file which is only 29.875 days because its first timestep is of zero duration
	    # Solution: Base dpf on second file, not first file, if it exists
	    fl_idx=$((fl_nbr > 1 ? 1 : 0))
	    tm_var='time'
	    tm_bnds=`ncks --trd -M -m -v ${tm_var} ${fl_in[${fl_idx}]} | grep -E -i "^${tm_var} attribute [0-9]+: bounds" | cut -f 11- -d ' ' | sed 's/^ *//g'`
	    dpf=`ncks --trd -H -s '%25.15f ' -v ${tm_bnds} -d ${tm_var},0 -d ${tm_var},-1 -C ${fl_in[${fl_idx}]} | awk '{ print $4-$1; exit }'`
	    # Ensure result is positive integer
	    [ "${dpf}" -ge 0 ] 2>/dev/null && dpf_ntg='Yes' || dpf_ntg='No'
	    if [ "${dpf_ntg}" != 'Yes' ]; then
		printf "${spt_nm}: ERROR Inferred value of dpf=${dpf} is not a positive integer. User requested high-frequency splitter operations without specifying the number of days-per-file (dpf) in the input files, so ${spt_nm} tried to infer dpf from the first input file. This operation resulted in a nonsense value. This is not surprising since a number of assumptions must hold true for the inferral to work correctly.\nHINT: Explicitly specify the number of days-per-file in every input file with the --dpf option.\n"
		exit 1
	    fi # !dpf_ntg
	fi # !dpf
	let fpy=${dpy}/${dpf}
	flg_dct='No'
    elif [ "${clm_md}" = 'mth' ]; then
	fpy=12
    fi # !clm_md
    if [ "${clm_md}" = 'hfs' ]; then
	let yr_sbs=${yr_nbr}
    else
	let yr_sbs=${fl_nbr}/${fpy}
	let fl_rmd=${fl_nbr}%${fpy}
    fi # !clm_md
    if [ ${flg_dct} = 'Yes' ]; then
	if [ "${mth_flg}" = 'Yes' ]; then
	    if [ ${mth_nbr} -ne ${fl_nbr} ]; then
		printf "${spt_nm}: ERROR Number of input files = ${fl_nbr} differs from number of months = ${mth_nbr} computed from calendar inputs\n"
		printf "${spt_nm}: HINT In this mode (clm_md=${clm_md}) splitter expects one monthly file per calendar month\n"
		exit 1
	    fi # !mth_nbr
	else # !mth_flg
	    if [ ${fl_rmd} -ne 0 ]; then
		printf "${spt_nm}: ERROR ${fl_nbr} files of clm_md=${clm_md} input contain non-integral number of years, ${fl_rmd} files leftover in final year\n"
		printf "${spt_nm}: HINT Provide input filenames in multiples of ${fpy}\n"
		exit 1
	    fi # !fl_rmd
	    if [ ${yr_sbs} -ne ${yr_nbr} ]; then
		# Sanity check that number of files specified matches number expected from date switches
		printf "${spt_nm}: ERROR ${fl_nbr} files specified (via stdin pipe, positional, or input directory) expected to contain ${yr_sbs} years of data but date options specify ${yr_nbr} years of data\n"
		printf "${spt_nm}: HINT Number of files at ${fpy} files-per-year must match number of years implied by arguments to start- and end-year switches (--yr_srt=${yr_srt} and --yr_end=${yr_end}, respectively)\n"
		exit 1
	    fi # !yr_sbs
	fi # !mth_flg
    fi # !flg_dct

    # How many segments of output?
    if [ "${mth_flg}" = 'Yes' ]; then
	# NB: When start/end months are not Jan/Dec, sgm_rmd measured in months
	let mpf_max=${fpy}*${ypf_max}
	let sgm_nbr=${mth_nbr}/${mpf_max}
	let sgm_rmd=${mth_nbr}%${mpf_max}
	if [ ${sgm_rmd} -ne 0 ]; then
	    let sgm_nbr=${sgm_nbr}+1
	    rmd_flg='Yes'
	else # !sgm_rmd
	    rmd_flg='No'
	fi # !sgm_rmd  
    else  # !mth_flg
	# NB: When start/end months are Jan/Dec, sgm_rmd measured in years
	let sgm_nbr=${yr_sbs}/${ypf_max}
	let sgm_rmd=${yr_sbs}%${ypf_max}
	if [ ${sgm_rmd} -ne 0 ]; then
	    let sgm_nbr=${sgm_nbr}+1
	    rmd_flg='Yes'
	else # !sgm_rmd
	    rmd_flg='No'
	fi # !sgm_rmd  
    fi  # !mth_flg
    let sgm_nbrm1=${sgm_nbr}-1

else # !sbs_flg

    if [ "${glb_avg}" = 'Yes' ]; then 
	echo "${spt_nm}: ERROR Global average functionality is intended for time-series (splitting) mode only. If you would like ${spt_nm} to provide global mean output for other types of climatologies, please talk to Charlie."
	echo "${spt_nm}: HINT Create global means of climos with, e.g., \"ncwa -w ${area_nm} in.nc out.nc\""
	echo "${spt_nm}: HINT See averaging documentation at http://nco.sf.net/nco.html#ncwa"
	exit 1
    fi # !glb_avg

    if [ -z "${out_nm}" ]; then
	echo "${spt_nm}: ERROR Missing information needed to generate output filenames"
	echo "${spt_nm}: HINT Climo generation requires that users specify a case ID with -c \$caseid or specify with -m \$mdl_nm a recognized model name (like \"mpaso\")"
	echo "${spt_nm}: HINT ${spt_nm} needs this information to generate output filenames"
	echo "${spt_nm}: HINT See invocation examples at http://nco.sf.net/nco.html#ncclimo"
	exit 1
    fi # out_nm

    if [ ${inp_std} = 'Yes' ] && [ ${clm_md} != 'dly' ] && [ ${clm_md} != 'hfc' ] ; then
	echo "${spt_nm}: ERROR Detected input on pipe to stdin rather than console in climatology generation mode"
	echo "${spt_nm}: HINT Piping filenames to ${spt_nm} only works when splitting files or in daily or in high-frequency climatology mode"
	echo "${spt_nm}: HINT In other climo generation modes (monthly and annual), one specifies the year/month boundaries and ${spt_nm} automatically generates the correct input file names"
	echo "${spt_nm}: HINT See invocation examples at http://nco.sf.net/nco.html#ncclimo"
	exit 1
    fi # !stdin

fi # !sbs_flg

# 20170807 Custom seasons
if [ "${csn_flg}" = 'Yes' ]; then

    if [ "${clm_md}" != 'hfc' ] && [ "${clm_md}" != 'mth' ]; then
	echo "${spt_nm}: ERROR Custom seasons available only in high-frequency or monthly climatology mode"
	echo "${spt_nm}: HINT Re-run with --clm_md=hfc or --clm_md=mth or without --csn_lst/--seasons"
	exit 1
    fi # !mth

    # Standard and custom season definitions
    # Index into season definition table
    csn_idx_srt=0 # [idx] Starting index for season definitions
    csn_mam=0
    csn_jja=1
    csn_son=2
    csn_djf=3
    csn_jfm=4
    csn_amj=5
    csn_jas=6
    csn_ond=7
    csn_on=8
    csn_fm=9
    csn_ann=10
    csn_nbr_max=11 # [nbr] Maximum number of seasons in definitions database

    # Seasonal abbreviations, uppercase
    csn_abb[${csn_mam}]='MAM'
    csn_abb[${csn_jja}]='JJA'
    csn_abb[${csn_son}]='SON'
    csn_abb[${csn_djf}]='DJF'
    csn_abb[${csn_jfm}]='JFM'
    csn_abb[${csn_amj}]='AMJ'
    csn_abb[${csn_jas}]='JAS'
    csn_abb[${csn_ond}]='OND'
    csn_abb[${csn_on}]='ON'
    csn_abb[${csn_fm}]='FM'
    csn_abb[${csn_ann}]='ANN'

    # Seasonal abbreviations, lowercase
    csn_abb_lc[${csn_mam}]='mam'
    csn_abb_lc[${csn_jja}]='jja'
    csn_abb_lc[${csn_son}]='son'
    csn_abb_lc[${csn_djf}]='djf'
    csn_abb_lc[${csn_jfm}]='jfm'
    csn_abb_lc[${csn_amj}]='amj'
    csn_abb_lc[${csn_jas}]='jas'
    csn_abb_lc[${csn_ond}]='ond'
    csn_abb_lc[${csn_on}]='on'
    csn_abb_lc[${csn_fm}]='fm'
    csn_abb_lc[${csn_ann}]='ann'

    # Seasonal start-month index (1-based calendar)
    csn_srt_idx[${csn_mam}]=3
    csn_srt_idx[${csn_jja}]=6
    csn_srt_idx[${csn_son}]=9
    csn_srt_idx[${csn_djf}]=12
    csn_srt_idx[${csn_jfm}]=1
    csn_srt_idx[${csn_amj}]=4
    csn_srt_idx[${csn_jas}]=7
    csn_srt_idx[${csn_ond}]=10
    csn_srt_idx[${csn_on}]=10
    csn_srt_idx[${csn_fm}]=2
    csn_srt_idx[${csn_ann}]=1

    # Seasonal end-month index (1-based calendar)
    csn_end_idx[${csn_mam}]=5
    csn_end_idx[${csn_jja}]=8
    csn_end_idx[${csn_son}]=11
    csn_end_idx[${csn_djf}]=2
    csn_end_idx[${csn_jfm}]=3
    csn_end_idx[${csn_amj}]=6
    csn_end_idx[${csn_jas}]=9
    csn_end_idx[${csn_ond}]=12
    csn_end_idx[${csn_on}]=11
    csn_end_idx[${csn_fm}]=3
    csn_end_idx[${csn_ann}]=12

    for ((csn_idx=0;csn_idx<${csn_nbr_max};csn_idx++)); do
	csn_srt_mm[${csn_idx}]=`printf "%02d" ${csn_srt_idx[${csn_idx}]}`
	csn_end_mm[${csn_idx}]=`printf "%02d" ${csn_end_idx[${csn_idx}]}`
    done # !csn_idx

    # Seasonal number of months
    csn_nom[${csn_mam}]=3
    csn_nom[${csn_jja}]=3
    csn_nom[${csn_son}]=3
    csn_nom[${csn_djf}]=3
    csn_nom[${csn_jfm}]=3
    csn_nom[${csn_amj}]=3
    csn_nom[${csn_jas}]=3
    csn_nom[${csn_ond}]=3
    csn_nom[${csn_on}]=2
    csn_nom[${csn_fm}]=2
    csn_nom[${csn_ann}]=12

    # Seasonal monthly weights (ncra/nces -w argument), assumes calendar = 365 dpy
    csn_wgt[${csn_mam}]='31,30,31'
    csn_wgt[${csn_jja}]='30,31,31'
    csn_wgt[${csn_son}]='30,31,30'
    csn_wgt[${csn_djf}]='31,31,28'
    csn_wgt[${csn_jfm}]='31,28,31'
    csn_wgt[${csn_amj}]='30,31,30'
    csn_wgt[${csn_jas}]='31,31,30'
    csn_wgt[${csn_ond}]='31,30,31'
    csn_wgt[${csn_on}]='31,30'
    csn_wgt[${csn_fm}]='28,31'
    csn_wgt[${csn_ann}]='31,28,31,30,31,30,31,31,30,31,30,31'

    # Which seasons are requested?
    # http://stackoverflow.com/questions/27702452/loop-through-a-comma-separated-shell-variable
    csn_nbr=0 # [sng] Number of seasons to compute
    for csn in ${csn_lst//,/ }; do
	csn_rqs[${csn_nbr}]=${csn}
	# NB: Requested seasons are 0-based, defined seasons are 0-based
	for ((csn_dfn_idx=${csn_idx_srt};csn_dfn_idx<${csn_nbr_max};csn_dfn_idx++)); do
	    if [[ "${csn}" =~ "${csn_abb[${csn_dfn_idx}]}" ]] || [[ "${csn}" =~ "${csn_abb_lc[${csn_dfn_idx}]}" ]]; then
		# Map requested to defined (r2d) seasons and inverse (d2r)
		# map_r2d[0]=3 means first season that user requested (i.e., in csn_lst) is fourth defined in table
		# map_d2r[3]=0 means fourth defined season is first requested
		map_r2d[${csn_nbr}]=${csn_dfn_idx}
		map_d2r[${csn_dfn_idx}]=${csn_nbr}
		let csn_nbr=${csn_nbr}+1
		break
	    fi # !match
	done # !csn_dfn_idx
	if [ "${csn_dfn_idx}" -eq "${csn_nbr_max}" ]; then
	    printf "${spt_nm}: ERROR Requested season ${csn} not defined\n"
	    exit 1
	fi # !match
    done # !csn_lst

    # Were all four seasons requested so annual mean can be computed from seasons?
    ann_flg='No'
    ann_nbr=0
    if [[ ${csn_lst} =~ 'mam' ]] || [[ ${csn_lst} =~ 'MAM' ]]; then
	let clm_csn_mam_idx=${map_d2r[${csn_mam}]}+12+1
	if [[ ${csn_lst} =~ 'jja' ]] || [[ ${csn_lst} =~ 'JJA' ]]; then
	    let clm_csn_jja_idx=${map_d2r[${csn_jja}]}+12+1
	    if [[ ${csn_lst} =~ 'son' ]] || [[ ${csn_lst} =~ 'SON' ]]; then
		let clm_csn_son_idx=${map_d2r[${csn_son}]}+12+1
		if [[ ${csn_lst} =~ 'djf' ]] || [[ ${csn_lst} =~ 'DJF' ]]; then
		    let clm_csn_djf_idx=${map_d2r[${csn_djf}]}+12+1
		    ann_flg='Yes'
		    ann_nbr=1
		fi # !djf
	    fi # !son
	fi # !jja
    fi # !mam

    # Account for season number in climatology command number and parallelism
    let clm_csn_srt_idx=12+1
    let clm_csn_end_idx=12+${csn_nbr}
    let clm_nbr=12+${csn_nbr}+${ann_nbr}
    
fi # !csn_flg
    
if [ "${mpi_flg}" = 'Yes' ]; then
    if [ -n "${COBALT_NODEFILE}" ]; then 
	nd_fl="${COBALT_NODEFILE}"
    elif [ -n "${PBS_NODEFILE}" ]; then 
	nd_fl="${PBS_NODEFILE}"
    elif [ -n "${SLURM_NODELIST}" ]; then 
	# SLURM returns compressed lists (e.g., "nid00[076-078,559-567]")
	# Convert this to file with uncompressed list (like Cobalt, PBS)
	# http://www.ceci-hpc.be/slurm_faq.html#Q12
	# Save file in writable directory
	nd_fl="${drc_out}/ncclimo.slurm_nodelist.pid${spt_pid}.tmp"
	nd_lst=`scontrol show hostname ${SLURM_NODELIST}`
	echo ${nd_lst} > ${nd_fl}
    else
	echo "${spt_nm}: ERROR MPI master process unable to find node-list for distributing jobs"
	echo "${spt_nm}: ${spt_nm} uses first node-list found in \$COBALT_NODEFILE, \$PBS_NODEFILE, or \$SLURM_NODELIST"
	echo "${spt_nm}: However, none of these environment variables are set so there is no node-list for distributing MPI jobs"
	echo "${spt_nm}: HINT: Requesting MPI-parallelism (i.e., invoking ${spt_nm} with \"-p mpi\") in a non-MPI environment will trigger this error. Use \"-p mpi\" only when one of the preceding schedulers has allocated (for interactive use) or will allocate (for non-interactive use) the compute nodes. Otherwise use the default background parallelism (use \"-p bck\" or omit the option completely) or use serial mode (use \"-p serial\"). See http://nco.sf.net/nco.html#par_typ for more information on parallelism."
	exit 1
    fi # !PBS
    if [ "${sbs_flg}" = 'Yes' ]; then 
	mpi_nbr=${var_nbr}
    else
	mpi_nbr=${clm_nbr}
    fi # !sbs_flg
    # 20210310 MPI run command may need/use thr_nbr so set it first
    if [ -z "${thr_usr}" ]; then 
	if [ -n "${PBS_NUM_PPN}" ]; then
#	NB: use export OMP_NUM_THREADS when thr_nbr > 8
#	thr_nbr=${PBS_NUM_PPN}
	    thr_nbr=$((PBS_NUM_PPN > 8 ? 8 : PBS_NUM_PPN))
	fi # !pbs
    fi # !thr_usr
    if [ -n "${nd_fl}" ]; then 
	# NB: nodes are always 0-based, e.g., [0..11]
	# For climo generation MPI index loops over months    and is 1-based, e.g., [1..17] (December is 12 and ANN is 17)
	# For climo subsetting MPI index loops over variables and is 0-based, e.g., [0..5], as are input files
	nd_idx=0
	for nd in `cat ${nd_fl} | uniq` ; do
	    nd_nm[${nd_idx}]=${nd}
	    let nd_idx=${nd_idx}+1
	done # !nd
	nd_nbr=${#nd_nm[@]}
	if [ "${nd_nbr}" -eq 0 ]; then
	    echo "${spt_nm}: ERROR MPI-mode node number nd_nbr = ${nd_nbr}"
	    echo "${spt_nm}: HINT Parsing the node-list for distributing MPI jobs failed"
	    exit 1
	fi # !nd_nbr
	# NB: ncclimo and ncremap employ different node-allocation algorithms:
	# ncclimo (monthly climatology mode) assigns monthly regridding and seasonal climos to different nodes (i.e., load-balances), and likewise for seasonal-regridding and annual climo
	# ncclimo (splitter mode) uses simple round robin based on position in variable list
	# ncremap uses simple round-robin allocation based on position in input file list
	# Only ncclimo monthly climatology-mode uses 1-based cmd_mpi array
	# ncclimo splitter-mode, and daily and annual climatology-mode, and ncremap all use 0-based cmd_mpi array
	# Copy host-specific mpirun syntax but not node-allocation algorithms or loop indices between ncclimo and ncremap
	# 20160502: Remove tasks-per-node limits (ntasks, npernode) so round-robin algorithm can schedule multiple jobs on same node
	for ((mpi_idx_zro=0;mpi_idx_zro<mpi_nbr;mpi_idx_zro++)); do
	    mpi_idx=${mpi_idx_zro}
	    if [ "${clm_flg}" = 'Yes' ] && [ "${clm_hfc_or_mth}" = 'Yes' ]; then 
		# Offset MPI index from 0- to 1-based for traditional monthly-based climo generation
		let mpi_idx=${mpi_idx_zro}+1
	    fi # !sbs_flg
	    case "${HOSTNAME}" in 
		andes* | blues* | blogin* | b[0123456789][0123456789][0123456789] | chrysalis* | chrlogin* | chr-[0123456789][0123456789][0123456789][0123456789] | cmem* | compy* | constance* | cori* | n[0123456789][0123456789][0123456789][0123456789] | nid* | node* )
		    # 20160502: Non-interactive batch jobs at NERSC return HOSTNAME as nid*, not cori*
		    # 20160803: Non-interactive batch jobs at PNNL constance return HOSTNAME as node*, not constance*
		    # 20190526: Non-interactive batch jobs at PNNL compy return HOSTNAME as n????, not compy*
		    # 20210310: srun option<->long option equivalences are -N = --nodes, -n = --ntasks, -c = --cpus-per-task
		    # NB: NERSC staff say srun automatically assigns to unique nodes even without "-L $node" argument?
 		    cmd_mpi[${mpi_idx}]="srun --nodelist ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} --nodes=1" ; ;; # SLURM
		    # cmd_mpi[${mpi_idx}]="srun --nodelist ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} --nodes=1 --ntasks=1" ; ;; # SLURM
		    # cmd_mpi[${mpi_idx}]="srun --nodelist ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} --nodes=1 --ntasks=1 --cpus-per-task=1" ; ;; # SLURM
		*cheyenne* )
		    # 20180120: Non-interactive batch jobs at NCAR return HOSTNAME as cheyenne?
		    # Cheyenne prefers 'mpiexec_mpt dplace -s 1 ncclimo ...'
		    # Unsure how to specify nd_nm to mpiexec_mpt
		    # mpirun from SGI MPT does not accept '-H nd_nm', unlike regular PBS
		    # PBSPro considers ncclimo a 'non-MPT application' so must set MPI_SHEPHERD=true
		    export MPI_SHEPHERD=true
#		    cmd_mpi[${mpi_idx}]="mpiexec_mpt dplace ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} -n 1" ; ;; # PBSPro
		    cmd_mpi[${mpi_idx}]="mpirun ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} -n 1" ; ;; # PBSPro
		cooley* | cc[0123456789][0123456789][0123456789] | mira* ) 
		    # 20190310: Cooley mpirun now uses -hosts instead of -H for hostname option
		    cmd_mpi[${mpi_idx}]="mpirun -hosts ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} -n 1" ; ;; # ALCF
		theta* )
		    # NB: ALCF Theta (not cooley or mira) uses aprun:
		    # https://www.alcf.anl.gov/user-guides/example-program-and-makefile-xc40
		    cmd_mpi[${mpi_idx}]="aprun -L ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} -n 1" ; ;; # ALCF Theta
		* )
		    cmd_mpi[${mpi_idx}]="mpirun -H ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} -n 1" ; ;; # Other (Cobalt, PBS)
#		    cmd_mpi[${mpi_idx}]="mpirun -H ${nd_nm[$((${mpi_idx_zro} % ${nd_nbr}))]} -npernode 1 -n 1" ; ;; # Other
	    esac # !HOSTNAME
	    case "${HOSTNAME}" in 
		cori* | nid* )
		    # 20210319: Multiple srun commands cannot run concurrently on single Cori node without special options
		    # --gres=craynetwork:0 --mem=20000 tested by me, Noel Keen documented here:
		    # https://docs.nersc.gov/jobs/examples/#multiple-parallel-jobs-while-sharing-nodes
		    # These options are only expected to work on NERSC Cori
		    # craynetwork:0 (instead of, e.g., :1) allows any number of MPI jobs to run
		    # Argument to --mem is requested number of MB RAM per job so 96 GB Cori node with 4 MPI processes can use ~20000 MB RAM per job, and, I think, jobs in excess of that will queue until more RAM becomes available as old jobs finish
		    # 20210319: --zonesort=off tested by me does eliminate zonesort messages like these:
		    # slurmstepd: error: Detected zonesort setup failure: zonesort interface write failure (40558186.7)
		    # However, using zonesort option appears to slow-down throughput considerably
		    cmd_mpi[${mpi_idx}]="${cmd_mpi[${mpi_idx}]} --gres=craynetwork:0 --mem=${mem_mb}" ; ;; # SLURM    
		    # cmd_mpi[${mpi_idx}]="${cmd_mpi[${mpi_idx}]} --nodes=1 --gres=craynetwork:0 --mem=${mem_mb} --zonesort=off" ; ;; # SLURM
	    esac # !HOSTNAME
	done # !mpi_idx_zro
	if [ -n "${SLURM_NODELIST}" ]; then 
	    /bin/rm -f ${nd_fl}
	fi # !SLURM
    else # !nd_fl
	mpi_flg='No'
	for ((mpi_idx=0;mpi_idx<=mpi_nbr;mpi_idx++)); do
	    cmd_mpi[${mpi_idx}]=''
	done # !mpi_idx
    fi # !nd_fl
    if [ -z "${job_usr}" ]; then 
	job_nbr=${nd_nbr}
    fi # !job_usr
fi # !mpi

if [ "${clm_flg}" = 'Yes' ] && [ "${clm_hfc_or_mth}" = 'Yes' ] && [ "${bnr_flg}" = 'No' ]; then
    # For regular monthly climos, override default job_nbr (2) with months-per-year
    # If user does not explicitly set job number then, for monthly climos, base it on parallelism type
    if [ -z "${job_usr}" ]; then 
	if [ "${par_typ}" = ${par_bck} ]; then 
	    # Background mode will run one batch with all twelve months
	    job_nbr=12
	elif [ "${par_typ}" = ${par_mpi} ]; then 
	    # MPI mode will round-robin all twelve months to all available nodes
	    job_nbr=12
	elif [ "${par_typ}" = 'nil' ] || [ -z "${par_typ}" ]; then 
	    # Serial mode equates to job_nbr=1, full serial
	    job_nbr=1
	fi # !job_usr
    fi # !job_usr
fi # !clm_md !bnr_flg

# Print initial state
if [ ${dbg_lvl} -ge 2 ]; then
    printf "dbg: bnr_flg  = ${bnr_flg}\n"
    printf "dbg: bs_nm    = ${bs_nm}\n"
    printf "dbg: bs_sfx   = ${bs_sfx}\n"
    printf "dbg: caseid   = ${caseid}\n"
    printf "dbg: cf_flg   = ${cf_flg}\n"
    printf "dbg: clm_flg  = ${clm_flg}\n"
    printf "dbg: clm_md   = ${clm_md}\n"
    printf "dbg: clm_nbr  = ${clm_nbr}\n"
    printf "dbg: csn_lst  = ${csn_lst}\n"
    printf "dbg: csn_nbr  = ${csn_nbr}\n"
    printf "dbg: d2f_flg  = ${d2f_flg}\n"
    printf "dbg: d2f_opt  = ${d2f_opt}\n"
    printf "dbg: dec_md   = ${dec_md}\n"
    printf "dbg: dbg_lvl  = ${dbg_lvl}\n"
    printf "dbg: dfl_lvl  = ${dfl_lvl}\n"
    printf "dbg: dpt_opt  = ${dpt_opt}\n"
    printf "dbg: drc_in   = ${drc_in}\n"
    printf "dbg: drc_nco  = ${drc_nco}\n"
    printf "dbg: drc_out  = ${drc_out}\n"
    printf "dbg: drc_prv  = ${drc_prv}\n"
    printf "dbg: drc_pwd  = ${drc_pwd}\n"
    printf "dbg: drc_rgr  = ${drc_rgr}\n"
    printf "dbg: drc_spt  = ${drc_spt}\n"
    printf "dbg: drc_xtn  = ${drc_xtn}\n"
    printf "dbg: fl_fmt   = ${fl_fmt}\n"
    printf "dbg: fl_nbr   = ${fl_nbr}\n"
    printf "dbg: fml_nm   = ${fml_nm}\n"
    printf "dbg: gaa_sng  = ${gaa_sng}\n"
    printf "dbg: glb_avg  = ${glb_avg}\n"
    printf "dbg: hdr_pad  = ${hdr_pad}\n"
    printf "dbg: hrd_pth  = ${hrd_pth}\n"
    printf "dbg: hrz_dmn  = ${hrz_dmn}\n"
    printf "dbg: hst_nm   = ${hst_nm}\n"
    printf "dbg: inp_aut  = ${inp_aut}\n"
    printf "dbg: inp_glb  = ${inp_glb}\n"
    printf "dbg: inp_psn  = ${inp_psn}\n"
    printf "dbg: inp_std  = ${inp_std}\n"
    printf "dbg: job_nbr  = ${job_nbr}\n"
    printf "dbg: lnk_flg  = ${lnk_flg}\n"
    printf "dbg: mdl_nm   = ${mdl_nm}\n"
    printf "dbg: mdl_typ  = ${mdl_typ}\n"
    printf "dbg: mpi_flg  = ${mpi_flg}\n"
    printf "dbg: mpi_nbr  = ${mpi_nbr}\n"
    printf "dbg: mth_end  = ${mth_end}\n"
    printf "dbg: mth_nbr  = ${mth_nbr}\n"
    printf "dbg: mth_srt  = ${mth_srt}\n"
    printf "dbg: nco_opt  = ${nco_opt}\n"
    printf "dbg: ncr_flg  = ${ncr_flg}\n"
    printf "dbg: nd_nbr   = ${nd_nbr}\n"
    printf "dbg: nm_typ   = ${nm_typ}\n"
    printf "dbg: no_ntv   = ${no_ntv_tms}\n"
    printf "dbg: out_nm   = ${out_nm}\n"
    printf "dbg: par_typ  = ${par_typ}\n"
    printf "dbg: ppc_prc  = ${ppc_prc}\n"
    printf "dbg: prc_typ  = ${prc_typ}\n"
    printf "dbg: rgr_map  = ${rgr_map}\n"
    printf "dbg: rgr_opt  = ${rgr_opt}\n"
    printf "dbg: rgr_sfx  = ${rgr_sfx}\n"
    printf "dbg: sbs_flg  = ${sbs_flg}\n"
    printf "dbg: sgm_nbr  = ${sgm_nbr}\n"
    printf "dbg: sgm_rmd  = ${sgm_rmd}\n"
    printf "dbg: sgs_frc  = ${sgs_frc}\n"
    printf "dbg: thr_nbr  = ${thr_nbr}\n"
    printf "dbg: uio_flg  = ${uio_flg}\n"
    printf "dbg: var_lst  = ${var_lst}\n"
    printf "dbg: vrt_fl   = ${vrt_fl}\n"
    printf "dbg: xtn_flg  = ${xtn_flg}\n"
    printf "dbg: ypf_max  = ${ypf_max}\n"
    printf "dbg: yr_sbs   = ${yr_sbs}\n"
    printf "dbg: yyyy_end = ${yyyy_end}\n"
    printf "dbg: yyyy_srt = ${yyyy_srt}\n"
    if [ "${csn_flg}" = 'Yes' ]; then
	for ((csn_idx=0;csn_idx<${csn_nbr};csn_idx++)); do
	    printf "dbg: Requested season index ${csn_idx} (\"${csn_rqs[${csn_idx}]}\") is defined season index ${map_r2d[${csn_idx}]} (\"${csn_abb[${map_r2d[${csn_idx}]}]}\")\n"
	done # !csn_idx
    fi # !csn
fi # !dbg
if [ ${dbg_lvl} -ge 2 ]; then
    printf "dbg: yyyy_srt   = ${yyyy_srt}\n"
    printf "dbg: yr_srt_rth = ${yr_srt_rth}\n"
    printf "dbg: yr_srtm1   = ${yr_srtm1}\n"
    printf "dbg: yr_endm1   = ${yr_endm1}\n"
    if [ ${mpi_flg} = 'Yes' ]; then
	for ((nd_idx=0;nd_idx<${nd_nbr};nd_idx++)); do
	    printf "dbg: nd_nm[${nd_idx}] = ${nd_nm[${nd_idx}]}\n"
	done # !nd
    fi # !mpi
fi # !dbg
if [ ${dbg_lvl} -ge 2 ]; then
    psn_nbr=$#
    printf "dbg: Found ${psn_nbr} positional parameters (besides \$0):\n"
    for ((psn_idx=1;psn_idx<=psn_nbr;psn_idx++)); do
	printf "dbg: psn_arg[${psn_idx}] = ${!psn_idx}\n"
    done # !psn_idx
fi # !dbg

# Human-readable summary
date_srt=$(date +"%s")
if [ ${dbg_lvl} -ge 0 ]; then
    printf "Climatology operations invoked with command:\n"
    echo "${cmd_ln}"
fi # !dbg
if [ "${sbs_flg}" = 'Yes' ]; then
    printf "Started climatology splitting at `date`\n"
else
    printf "Started climatology generation at `date`\n"
fi # !sbs_flg
printf "Running climatology script ${spt_nm} from directory ${drc_spt}\n"
printf "NCO binaries version ${nco_vrs} from directory ${drc_nco}\n"
printf "Parallelism mode = ${par_typ}\n"
if [ "${sbs_flg}" = 'Yes' ]; then
    if [ ${var_nbr} -gt 1 ]; then
	printf "Timeseries will be created for each of ${var_nbr} variables\n"
	if [ "${par_typ}" = ${par_bck} ]; then
	    printf "Background parallelism processing variables in var_nbr/job_nbr = ${var_nbr}/${job_nbr} = $((var_nbr / job_nbr)) sequential batches each concurrently processing job_nbr = ${job_nbr} jobs (1 per variable), then remaining $((var_nbr % job_nbr)) jobs/variables simultaneously\n"
	elif [ "${par_typ}" = ${par_mpi} ]; then
	    printf "MPI parallelism dividing processing of ${var_nbr} variables onto nd_nbr = ${nd_nbr} nodes simultaneously\n"
	fi # !par_typ
    else # !var_nbr
	printf "Timeseries will be created for only one variable\n"
    fi # !var_nbr
    rmd_units='year'
    if [ "${mth_flg}" = 'Yes' ]; then
	rmd_units='month'
    fi # !mth_flg
    sgm_sng='0'
    if [ ${sgm_rmd} -ne 0 ]; then
	sgm_sng='1'
    fi # !sgm_rmd
    if [ ${sgm_nbr} -gt 1 ]; then
	printf "All this occurs within an outer loop of (yr_sbs/ypf_max) + (remainder, if any) = ${yr_sbs}/${ypf_max} + ${sgm_sng} = ${sgm_nbr} time segments\n"
	if [ ${rmd_flg} = 'Yes' ]; then 
	    printf "Will split data for each variable into ${sgm_nbrm1} timeseries segment(s) of length ${ypf_max} years and 1 segment of length ${sgm_rmd} ${rmd_units}(s)\n"
	else # !rmd_flg
	    printf "Will split data for each variable into ${sgm_nbr} timeseries segment(s) of length ${ypf_max} years\n"
	fi # !rmd_flg
    else # !sgm_nbr
	if [ ${mth_nbr} -eq 12 ]; then
	    printf "Will split data for each variable into one timeseries of length ${yr_sbs} years\n"
	else
	    if [ ${clm_md} = 'hfs' ]; then 
		printf "Will split data for each variable into one timeseries of length ${yr_sbs} years\n"
	    else
		if [ "${mth_flg}" = 'Yes' ]; then
		    printf "Will split data for each variable into one timeseries of length ${yr_sbs} years and ${sgm_rmd} months\n"
		else
		    printf "Will split data for each variable into one timeseries of length ${yr_sbs} years and ${fl_rmd} months\n"
		fi # !mth_flg
	    fi # !clm_md
	fi # !mth_nbr
    fi # !sgm_nbr
    if [ ${inp_std} = 'No' ]; then 
	if [ "${drc_in_usr_flg}" = 'Yes' ]; then
	    printf "Splitting climatology from ${fl_nbr} raw input files in directory ${drc_in}\n"
	else # !drc_in
	    printf "Splitting climatology from ${fl_nbr} raw input files specified as positional arguments\n"
	fi # !drc_in
    else
	printf "Splitting climatology from list of ${fl_nbr} raw input files piped to stdin\n"
    fi # !stdin
    if [ ${clm_md} = 'ann' ]; then 
	printf "Each input file assumed to contain statistic for one year\n"
    elif [ ${clm_md} = 'dly' ]; then 
	printf "Each input file assumed to contain statistic for one day\n"
    elif [ ${clm_md} = 'hfs' ]; then 
	printf "Each input file assumed to contain statistics for ${dpf} days each comprising ${tpd_out} timesteps\n"
    elif [ ${clm_md} = 'mth' ]; then 
	printf "Each input file assumed to contain statistic for one month\n"
    fi # !mth
    printf "Native-grid split files to directory ${drc_out}\n"
    if [ -n "${rgr_opt}" ]; then 
	printf "Regridded split files to directory ${drc_rgr}\n"
	if [ "${thr_nbr}" -ne 1 ]; then 
	    printf "Regridder will request ${thr_nbr} OpenMP threads to parallelize weight application across variables\n"
	fi # !thr_nbr
    else
	printf "Split files will not be regridded\n"
    fi # !rgr
fi # !sbs_flg
if [ "${clm_flg}" = 'Yes' ]; then
    if [ "${clm_hfc_or_mth}" = 'Yes' ] && [ "${bnr_flg}" = 'No' ]; then
	if [ "${par_typ}" = ${par_bck} ]; then
	    printf "Background parallelism processing monthly climos in 12/job_nbr = 12/${job_nbr} = $((12 / job_nbr)) sequential batches each handling job_nbr = ${job_nbr} months concurrently\n"
	elif [ "${par_typ}" = ${par_mpi} ]; then
	    printf "MPI parallelism distributing monthly and then seasonal climos to ${nd_nbr} nodes simultaneously via round-robin scheduling, and load-balancing with unused nodes, if any\n"
	fi # !par_typ
    fi # !clm_md
    if [ "${xtn_flg}" = 'No' ]; then
	printf "Producing standard climatology from raw input files in directory ${drc_in}\n"
	printf "Output files to directory ${drc_out}\n"
    fi # !xtn_flg
    if [ "${bnr_flg}" = 'Yes' ]; then
	printf "Producing extended climatology in binary mode: Will combine pre-computed climatology in directory ${drc_prv} with pre-computed climatology in directory ${drc_in}\n"
	printf "Output files to directory ${drc_xtn}\n"
    fi # !bnr_flg
    if [ "${ncr_flg}" = 'Yes' ]; then
	printf "Producing extended climatology in incremental mode: Pre-computed climatology in directory ${drc_prv} will be incremented by raw input files in directory ${drc_in}\n"
	printf "Output files to directory ${drc_xtn}\n"
    fi # !ncr_flg
    if [ "${bnr_flg}" = 'No' ]; then
	printf "Climatology from ${yr_nbr} years of contiguous raw data touching ${yr_cln} calendar years from YYYYMM = ${yyyy_clm_srt_dec}${mm_ann_srt} to ${yyyy_end}${mm_ann_end}\n"
    fi # !bnr_flg
    if [ "${nm_typ}" = 'yyyymm' ]; then
	printf "Input ilenames will be constructed with generic conventions as ${bs_nm}_YYYYMM.${bs_sfx}\n"
    elif [ "${nm_typ}" = 'yyyy-mm' ]; then
	printf "Input filenames will be constructed with generic conventions as ${bs_nm}.YYYY-MM.${bs_sfx}\n"
    elif [ "${nm_typ}" = 'yyyy-mm-01' ]; then
	printf "Input filenames will be constructed with generic conventions as ${bs_nm}.YYYY-MM-01.${bs_sfx}\n"
    elif [ "${nm_typ}" = 'yyyy-mm-01-00000' ]; then
	printf "Input filenames will be constructed with generic conventions as ${bs_nm}.YYYY-MM-01-00000.${bs_sfx}\n"
    elif [ "${nm_typ}" = 'mpas' ]; then
	printf "Input filenames will be constructed with MPAS conventions\n"
    elif [ ${inp_aut} = 'Yes' ]; then 
	printf "Input filenames will be constructed with CESM'ish or E3SM'ish conventions\n"
    else # !nm_typ
	printf "Input filenames will be read from globbing, positional arguments, or stdin\n"
    fi # !nm_typ
    if [ ${clm_md} = 'ann' ]; then 
	printf "Each input file assumed to contain statistic for one year\n"
    elif [ ${clm_md} = 'dly' ]; then 
	printf "Each input file assumed to contain statistics for one or more daily or sub-daily (e.g., 3-hourly) means\n"
    elif [ ${clm_md} = 'hfc' ]; then 
	printf "Each input file assumed to contain statistics for an integer number of days each comprising ${tpd_out} timesteps\n"
    elif [ ${clm_md} = 'mth' ]; then 
	printf "Each input file assumed to contain statistic for one month\n"
    fi # !mth
    if [ "${clm_hfc_or_mth}" = 'Yes' ]; then 
	if [ ${csn_flg} = 'No' ]; then 
	    printf "Seasons and therefore annual means derived from seasonal means have been turned-off and will not be computed\n"
	elif [ ${dec_md} = 'scd' ]; then 
	    printf "Winter statistics based on seasonally contiguous December (scd-mode): DJF-means formed from consecutive DJF months that cross calendar-year boundaries\n"
	else
	    printf "Winter statistics based on seasonally discontiguous December (sdd-mode): Means labeled as \"DJF\" are formed from JFD months from the same calendar year\n"
	fi # !scd
    fi # !mth
    if [ ${cf_flg} = 'Yes' ]; then 
	printf "Annotation for CF climatology attribute and climatology_bounds variable will be performed\n"
    else
	printf "Annotation for CF climatology attribute and climatology_bounds variable will not be performed\n"
    fi # !cf
    if [ -n "${rgr_opt}" ]; then 
	printf "This climatology will also be regridded\n"
	printf "Regridder will request ${thr_nbr} OpenMP threads to parallelize weight application across variables\n"
    else
	printf "This climatology will not be regridded\n"
    fi # !rgr
fi # !clm_flg

# Block 1: Generate, check, and store (but do not yet execute) commands

# Block 1 Loop 1: Produce monthly-mean climatology output
if [ "${clm_flg}" = 'Yes' ] && [ "${clm_hfc_or_mth}" = 'Yes' ]; then
    clm_idx=0
    for mth in {01..12}; do
	let clm_idx=${clm_idx}+1
	MM=`printf "%02d" ${clm_idx}`
	if [ ${clm_md} = 'mth' ]; then 
	    fl_all=''
	    for yr in `seq ${yyyy_srt} ${yyyy_end}`; do
		YYYY=`printf "%04d" ${yr}`
		if [ ${nm_typ} = 'cesm' ]; then
		    fl_all="${fl_all} ${caseid}.${mdl_nm}.${hst_nm}.${YYYY}-${MM}.nc"
		elif [ ${nm_typ} = 'mpas' ]; then # Use MPAS not CESM conventions
		    if [ ${mdl_nm} = 'mali' ] ; then 
			fl_all="${fl_all} ${mdl_nm}.${hst_nm}.${YYYY}-${MM}-01_00000.nc"
		    else # !mdl_nm
			# 20161130: Old MPAS rule until today
			# fl_all="${fl_all} ${caseid}.${mdl_nm}.${YYYY}-${MM}-01_00.00.00.nc"
			# Example file: /scratch2/scratchdirs/golaz/ACME_simulations/20161117.beta0.A_WCYCL1850S.ne30_oEC_ICG.edison/run/mpascice.hist.am.timeSeriesStatsMonthly.0001-02-01.nc
 			fl_all="${fl_all} ${mdl_nm}.hist.am.timeSeriesStatsMonthly.${YYYY}-${MM}-01.nc"
		    fi # !mdl_nm
		elif [ ${nm_typ} = 'yyyymm' ]; then # Generate from caseid + YYYYMM
		    fl_all="${fl_all} ${bs_nm}_${YYYY}${MM}.${bs_sfx}"
		elif [ ${nm_typ} = 'yyyy-mm' ]; then # Generate from caseid + YYYY-MM
		    fl_all="${fl_all} ${bs_nm}.${YYYY}-${MM}.${bs_sfx}"
		elif [ ${nm_typ} = 'yyyy-mm-01' ]; then # Generate from caseid + YYYY-MM-01
		    fl_all="${fl_all} ${bs_nm}.${YYYY}-${MM}-01.${bs_sfx}"
		elif [ ${nm_typ} = 'yyyy-mm-01-00000' ]; then # Generate from caseid + YYYY-MM-01-00000
		    fl_all="${fl_all} ${bs_nm}.${YYYY}-${MM}-01-00000.${bs_sfx}"
		fi # !cesm
	    done # !yr
	    if [ ${dec_md} = 'scd' ] && [ ${MM} = '12' ]; then 
		fl_all=''
		for yr in `seq ${yr_srtm1} ${yr_endm1}`; do
		    YYYY=`printf "%04d" ${yr}`
		    if [ ${nm_typ} = 'cesm' ]; then
			fl_all="${fl_all} ${caseid}.${mdl_nm}.${hst_nm}.${YYYY}-${MM}.nc"
		    elif [ ${nm_typ} = 'mpas' ]; then # Use MPAS not CESM conventions
 			fl_all="${fl_all} ${mdl_nm}.hist.am.timeSeriesStatsMonthly.${YYYY}-${MM}-01.nc"
		    elif [ ${nm_typ} = 'yyyymm' ]; then # Generate from caseid + YYYYMM
			fl_all="${fl_all} ${bs_nm}_${YYYY}${MM}.${bs_sfx}"
		    elif [ ${nm_typ} = 'yyyy-mm' ]; then # Generate from caseid + YYYY-MM
			fl_all="${fl_all} ${bs_nm}.${YYYY}-${MM}.${bs_sfx}"
		    elif [ ${nm_typ} = 'yyyy-mm-01' ]; then # Generate from caseid + YYYY-MM-01
			fl_all="${fl_all} ${bs_nm}.${YYYY}-${MM}-01.${bs_sfx}"
		    elif [ ${nm_typ} = 'yyyy-mm-01-00000' ]; then # Generate from caseid + YYYY-MM-01-00000
			fl_all="${fl_all} ${bs_nm}.${YYYY}-${MM}-01-00000.${bs_sfx}"
		    fi # !cesm
		done # !yr
	    fi # !scd
	    # Check for existence of raw input only when file will be used
	    if [ "${bnr_flg}" = 'No' ]; then
		for fl_crr in ${fl_all} ; do
		    if [ ! -f "${drc_in}/${fl_crr}" ]; then
			echo "${spt_nm}: ERROR Unable to find required input file ${drc_in}/${fl_crr}"
			echo "${spt_nm}: HINT All files implied to exist by the climatology bounds (start/end year/month) and by the specified (with -P or -m) or default model type, must be in ${drc_in} before ${spt_nm} will proceed"
			exit 1
		    fi # ! -f
		done # !fl_crr
	    else # !bnr_flg
		# In binary mode drc_out is actually used to locate input files from climatology B (same as output files in incremental mode)
		drc_out="${drc_in}"
	    fi # !bnr_flg

	fi # !clm_md

	# 20210819 former location of var_lst_rx_xtr block

	# Common to both high-frequency and monthly-input climos
	if [ ${dec_md} = 'scd' ] && [ ${MM} = '12' ]; then 
	    yyyy_clm_srt=${yyyy_clm_srt_dec}
	    yyyy_clm_end=${yyyy_clm_end_dec}
	fi # !scd
	fl_out[${clm_idx}]="${drc_out}/${out_nm}_${MM}_${yyyy_clm_srt}${MM}_${yyyy_clm_end}${MM}_climo.nc"
	
	if [ ${clm_md} = 'hfc' ]; then 
	    # Computing climos from high-freqency data requires many steps:
	    # A primary goal is to re-use not re-write code, especially existing monthly frequency climo code
	    # Otherwise code maintainability significantly suffers
	    # Code-reuse all-but-requires same existing parallelization as monthly frequency climos
	    # Difficulty is that traditionaly monthly mean climos equally weight all input records using ncra
	    # Proposed Algorithm:
	    # 1. Re-use splitter option ypf for intermediate files so users can prevent excessive intermediate filesizes
	    # 2. Generate month-aligned intermediate segments containing ypf instances of a single month (and no other months) for all variables
	    # 2a. To subset repeated appearances of given month and skip intervening 11 months, command must have form ncrcat -d time,srt_sng,end_sng,srd,ssc where srd=dpy*tpd and ssc=dpm*tpd
	    # 2b. Same as draft annual-mean HFS feature for diurnal cycle, except srt_sng/end_sng are "yyyy_srt_sgm-MM-01 00:00:01/yyyy_end_sgm-(MMp1)-01 00:00:00" instead of "yyyy_srt_sgm-01-01 00:00:01/yyyy_endp1_sgm-01-01 00:00:00"
	    # 2c. Intermediate monthly segments sgm_yyyy_srt_yyyy_end_MM.nc have ypf instances of given month, except last segment may be ragged
	    # 2d. Evaluate mean diurnal cycle of monthly-segment using ncra --d time,srt,end,srd=ssc,ssc=dpm*tpd,tpd
	    # 2c. Ragged numbers of ypf in last segment suggests using ncra climatological monthly diurnal-mean output
	    # 2d. Parallelize this over month (probably) using job_nbr algorithm? or over segment (unlikely)?
	    # 2e. Parallelization is difficult/tedious due to number of basic blocks (3? 4? 5?)
	    # 3. Intermediate files of shape (time=min(actual,ypf),hour,space) are nearly interchangeable with monthly-frequency output in stage-1 ncra
	    # 4. Use climatological monthly diurnal-mean output to create climatological seasonal diurnal-mean output
	    # 5. Use climatological monthly seasonal-mean output to create climatological annual diurnal-mean output
	    # 6. Final output climo format: (time=1,hour,space) (or convert to (time=hour,space)?)
	    # Advantages:
	    # 1. Code-reuse and maintainability
	    # 2. Scales to any length o simulation (intermediate files can be a single month)
	    # 3. Output similar to existing monthly-frequency climos with new, fixed-length 'hour' dimension
	    # 4. Climatology bounds attributes correct for time variable(?)
	    # Disadvantages:
	    # 1. E3SM-diags post-processing may not like new 'hour' dimension
	    # 2. No corresponding examples in CF Conventions
	    # 3. Any utility in multiple-variables per output file, e.g., TS, PRECT? Would single variables suffice?
	    DD=`printf "%02d" ${dpm[${clm_idx}]}` 
	    tm_srt="${yyyy_clm_srt}-${MM}-01T00:00:01"
	    let mthp1=$((clm_idx + 1))
	    MMp1=`printf "%02d" ${mthp1}`
	    tm_end="${yyyy_clm_end}-${MMp1}-01T00:00:00"
	    yyyy_clm_endp1=`printf "%04d" ${yr_endp1}`
	    if [ ${clm_idx} -eq 12 ]; then
		tm_srt="${yyyy_clm_srt_dec}-${MM}-01T00:00:01"
		yyyy_clm_endp1=`printf "%04d" ${yr_endp1}`
		if [ ${dec_md} = 'scd' ]; then 
		    yyyy_clm_endp1=`printf "%04d" ${yr_end}`
		fi # !scd
		tm_end="${yyyy_clm_endp1}-01-01T00:00:00"
	    fi # !December
	    let srd=${dpy}*${tpd_out}
	    let ssc=${dpm[${clm_idx}]}*${tpd_out}
	    
	    # Cull this month from interannual climatology into interannual diurnal file
	    fl_mth[${clm_idx}]="${drc_out}/${out_nm}_${MM}_tms_foo_${yyyy_clm_srt}_${yyyy_clm_end}.nc"

	    # Command to produce interleaved monthly climos
	    if [ "${fl_nbr}" -eq 1 ]; then
		# Compute interleaved mean of single-file input (e.g., CMIP timeseries format)
		#cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncra -O ${nco_opt} ${gaa_sng} -d time,${tm_srt},${tm_end},${srd},${ssc},${tpd_out} ${fl_in} ${fl_out[${clm_idx}]}"
		tm_nbr=`ncks --trd -M ${fl_in} | grep -E -i "^Root record dimension 0:" | cut -f 10- -d ' '`
		let ssc2=${tm_nbr}*${tpd_out}/${srd}
		# Ensure result is positive integer
		[ "${ssc2}" -ge 0 ] 2>/dev/null && ssc2_ntg='Yes' || ssc2_ntg='No'
		if [ "${ssc2_ntg}" != 'Yes' ]; then
		    printf "${spt_nm}: ERROR Inferred value of ssc2=${ssc2} is not a positive integer. User requested high-frequency climatology with one input file. ${spt_nm} attempted to divine the sub-cycle length based on the assumption that the file contains and integral number of years (at any temporal resolution) of input data. The divination resulted in a non-integral sub-cycle length. This is not surprising since a number of assumptions must hold true for the inferral to work correctly.\nHINT: Clip the input timeseries to an integral number of years.\n"
		    exit 1
		fi # !ssc2_ntg

		cmd_cll[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncra -O ${nco_opt} ${gaa_sng} -d time,${tm_srt},${tm_end},${srd},${ssc},${tpd_out} ${fl_in} ${fl_mth[${clm_idx}]}"

		cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncra --clm_bnd=${yr_srt},${yr_end},${clm_idx},${clm_idx},${tpd_out} -O ${nco_opt} ${gaa_sng} -d time,,,,${ssc2},${tpd_out} ${fl_mth[${clm_idx}]} ${fl_out[${clm_idx}]}"
		    
	    else # !fl_nbr
		# Concatenate means of each interleaved index of multi-file input (e.g., raw history output or multiple segments) 

		cmd_cll[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncrcat -O ${nco_opt} ${gaa_sng} -d time,${tm_srt},${tm_end},${srd},${ssc} -p ${drc_in} ${fl_in[*]} ${fl_mth[${clm_idx}]}"

		# Take mean value of each diurnal timestep across all instances of this month
		fl_tpd_tpl[${clm_idx}]="${drc_out}/${out_nm}_${MM}_tpdTPDTT_${yyyy_clm_srt}_${yyyy_clm_end}.nc"
		cmd_tpd_tpl[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncra -O ${nco_opt} ${gaa_sng} -d time,TPDidx,,${tpd_out} ${fl_mth[${clm_idx}]} ${fl_tpd_tpl[${clm_idx}]}"

		# Concatenate individual timestep means into full diurnal timeseries
		cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncrcat -O --clm_bnd=${yr_srt},${yr_end},${clm_idx},${clm_idx},${tpd_out} ${nco_opt} ${gaa_sng} ${drc_out}/${out_nm}_${MM}_tpd* ${fl_out[${clm_idx}]}"
	    fi # !fl_nbr
	    # Combine monthly into seasonal and annual means with nces (preserving diurnal timesteps)
	    cmd_mth2csn='nces'
	    cmd_csn2ann='nces'
	elif [ ${clm_md} = 'mth' ]; then 
	    cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncra --clm_bnd=${yr_srt},${yr_end},${clm_idx},${clm_idx},0 -O ${nco_opt} ${gaa_sng} -p ${drc_in} ${fl_all} ${fl_out[${clm_idx}]}"
	    # Combine monthly into seasonal and annual means with ncra
	    cmd_mth2csn='ncra'
	    cmd_csn2ann='ncra'
	fi # !clm_md
    done # !mth
		    
    if [ ${clm_md} = 'hfc' ]; then

	if [ "${fl_nbr}" -gt 1 ]; then
	    printf "Cull each month from interannual climatology into interannual diurnal file...\n"
	else
	    printf "Create interannual timeseries of monthly diurnal timestep means from single file...\n"
	fi # !fl_nbr
	[[ ${dbg_lvl} -ge 1 ]] && date_cll=$(date +"%s")

	if [ ${job_nbr} -eq 1 ] || [ ${job_nbr} -eq 2 ] || [ ${job_nbr} -eq 3 ] || [ ${job_nbr} -eq 4 ] || [ ${job_nbr} -eq 6 ] || [ ${job_nbr} -eq 12 ]; then
	    echo "Successful match of job_nbr to list of permissible values" > /dev/null
	else
	    echo "${spt_nm}: ERROR Job number job_nbr=${job_nbr} is invalid in monthly climo mode. Must be a factor of 12. Valid values are 1, 2, 3, 4, 6, and 12. Please re-submit with valid job_nbr."
	    exit 1
	fi # !job_nbr

	# Batch number is inverse to job number, so job_nbr=1->bch_nbr=12 (serial mode, slowest), job_nbr=2->bch_nbr=6, ... job_nbr=12->bch_nbr=1 (full background mode, fastest)
	let bch_nbr=$((12 / job_nbr))
	for ((bch_idx=0;bch_idx<bch_nbr;bch_idx++)); do
	    # clm_idx is 1-based, bch_idx is 0-based
	    let clm_idx_srt=$(((bch_idx * job_nbr) + 1))
	    let clm_idx_end=$((clm_idx_srt + job_nbr - 1))
	    for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
		printf "Cull climatological monthly timeseries for month ${clm_idx} ...\n"
		if [ ${dbg_lvl} -ge 1 ]; then
		    echo ${cmd_cll[${clm_idx}]}
		fi # !dbg
		if [ ${dbg_lvl} -le 1 ]; then
		    if [ -z "${par_opt}" ]; then
			eval ${cmd_cll[${clm_idx}]}
			if [ "$?" -ne 0 ]; then
			    printf "${spt_nm}: ERROR monthly cull cmd_cll[${clm_idx}] failed. Debug this:\n${cmd_cll[${clm_idx}]}\n"
			    exit 1
			fi # !err
		    else # !par_opt
			eval ${cmd_cll[${clm_idx}]} ${par_opt} # eval always returns 0 on backgrounded processes
			cll_pid[${clm_idx}]=$!
		    fi # !par_opt
		fi # !dbg
	    done # !clm_idx
	    if [ -n "${par_opt}" ]; then
		for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
		    wait ${cll_pid[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR monthly cull cmd_cll[${clm_idx}] failed. Debug this:\n${cmd_cll[${clm_idx}]}\n"
			# 20200805: exiting parent here creates orphans, kill live children then exit
			for ((kid_idx=clm_idx+1;kid_idx<=clm_idx_end;kid_idx++)); do
			    kill -9 ${cll_pid[${kid_idx}]}
			done # !kid_idx
			exit 1
		    fi # !err
		done # !clm_idx
	    fi # !par_opt
	done # !bch_idx
	if [ ${dbg_lvl} -ge 1 ]; then
	    date_crr=$(date +"%s")
	    date_dff=$((date_crr-date_cll))
	    echo "Elapsed time to cull monthly timeseries $((date_dff/60))m$((date_dff % 60))s"
	fi # !dbg

	if [ "${fl_nbr}" -gt 1 ]; then

	    printf "Take mean value of each diurnal timestep across all instances of every month...\n"
	    [[ ${dbg_lvl} -ge 1 ]] && date_tpd=$(date +"%s")

	    for ((tpd_idx=0;tpd_idx<tpd_out;tpd_idx++)); do
		TT=`printf "%02d" ${tpd_idx}`
		printf "Compute monthly means for diurnal timestep ${tpd_idx}...\n"
		let bch_nbr=$((12 / job_nbr))
		for ((bch_idx=0;bch_idx<bch_nbr;bch_idx++)); do
		    # clm_idx is 1-based, bch_idx is 0-based
		    let clm_idx_srt=$(((bch_idx * job_nbr) + 1))
		    let clm_idx_end=$((clm_idx_srt + job_nbr - 1))
		    for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
			printf "De-interleave and average diurnal timestep ${tpd_idx} for month ${clm_idx} ...\n"
			cmd_tpd[${clm_idx}]=${cmd_tpd_tpl[${clm_idx}]/TPDTT/${TT}}
			cmd_tpd[${clm_idx}]=${cmd_tpd[${clm_idx}]/TPDidx/${tpd_idx}}
			if [ ${dbg_lvl} -ge 1 ]; then
			    echo ${cmd_tpd[${clm_idx}]}
			fi # !dbg
			if [ ${dbg_lvl} -le 1 ]; then
			    if [ -z "${par_opt}" ]; then
				eval ${cmd_tpd[${clm_idx}]}
				if [ "$?" -ne 0 ]; then
				    printf "${spt_nm}: ERROR monthly de-interleave and average command cmd_tpd[${clm_idx}] failed. Debug this:\n${cmd_tpd[${clm_idx}]}\n"
				    exit 1
				fi # !err
			    else # !par_opt
				eval ${cmd_tpd[${clm_idx}]} ${par_opt} # eval always returns 0 on backgrounded processes
				tpd_pid[${clm_idx}]=$!
			    fi # !par_opt
			fi # !dbg
		    done # !clm_idx
		    if [ -n "${par_opt}" ]; then
			for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
			    wait ${tpd_pid[${clm_idx}]}
			    if [ "$?" -ne 0 ]; then
				printf "${spt_nm}: ERROR monthly de-interleave and average command cmd_tpd[${clm_idx}] failed. Debug this:\n${cmd_tpd[${clm_idx}]}\n"
				# 20200805: exiting parent here creates orphans, kill live children then exit
				for ((kid_idx=clm_idx+1;kid_idx<=clm_idx_end;kid_idx++)); do
				    kill -9 ${tpd_pid[${kid_idx}]}
				done # !kid_idx
				exit 1
			    fi # !err
			done # !clm_idx
		    fi # !par_opt
		done # !bch_idx
	    done # !tpd_idx
	    if [ ${dbg_lvl} -ge 1 ]; then
		date_crr=$(date +"%s")
		date_dff=$((date_crr-date_tpd))
		echo "Elapsed time to de-interleave and average all timesteps $((date_dff/60))m$((date_dff % 60))s"
	    fi # !dbg

	    # Clean-up monthly inputs (important since these file can be huge)
	    for ((clm_idx=1;clm_idx<=12;clm_idx++)); do
		/bin/rm -f ${fl_mth[${clm_idx}]}
	    done # !clm_idx

	fi # !fl_nbr

    fi # !clm_md
    
    if [ ${clm_md} = 'mth' ]; then
	tpd_out=0
    fi # !tpd_out	
    # Monthly output filenames constructed above; specify remaining (seasonal, annual) output names
    for ((csn_idx=0;csn_idx<${csn_nbr};csn_idx++)); do
	# Produces names like "${out_nm}_MAM_${yyyy_srt}03_${yyyy_end}05_climo.nc"
	let clm_idx=1+12+${csn_idx}
	fl_out[${clm_idx}]="${drc_out}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_srt}${csn_srt_mm[${map_r2d[${csn_idx}]}]}_${yyyy_end}${csn_end_mm[${map_r2d[${csn_idx}]}]}_climo.nc"
	clm_bnd[${clm_idx}]="--clm_bnd=${yr_srt},${yr_end},${csn_srt_idx[${map_r2d[${csn_idx}]}]},${csn_end_idx[${map_r2d[${csn_idx}]}]},${tpd_out}"
	if [ "${csn_abb[${map_r2d[${csn_idx}]}]}" = 'DJF' ]; then
	    fl_out[${clm_idx}]="${drc_out}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_clm_srt_dec}${mm_djf_srt}_${yyyy_end}${mm_djf_end}_climo.nc"
	    fl_out[${clm_idx}]="${drc_out}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_clm_srt_dec}${mm_djf_srt}_${yyyy_end}${mm_djf_end}_climo.nc"
	    clm_bnd[${clm_idx}]="--clm_bnd=${yyyy_clm_srt_dec},${yyyy_clm_end_dec},${mm_djf_srt},${mm_djf_end},${tpd_out}"
	fi # !DJF
	if [ "${csn_abb[${map_r2d[${csn_idx}]}]}" = 'ANN' ]; then
	    fl_out[${clm_idx}]="${drc_out}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_clm_srt_dec}${mm_ann_srt}_${yyyy_end}${mm_ann_end}_climo.nc"
	    fl_out[${clm_idx}]="${drc_out}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_clm_srt_dec}${mm_ann_srt}_${yyyy_end}${mm_ann_end}_climo.nc"
	    clm_bnd[${clm_idx}]="--clm_bnd=${yyyy_clm_srt_dec},${yyyy_end},${mm_ann_srt},${mm_ann_end},${tpd_out}"
	fi # !ANN
    done # !csn_idx
#    fl_out[13]="${drc_out}/${out_nm}_MAM_${yyyy_srt}03_${yyyy_end}05_climo.nc"
#    fl_out[14]="${drc_out}/${out_nm}_JJA_${yyyy_srt}06_${yyyy_end}08_climo.nc"
#    fl_out[15]="${drc_out}/${out_nm}_SON_${yyyy_srt}09_${yyyy_end}11_climo.nc"
#    fl_out[16]="${drc_out}/${out_nm}_DJF_${yyyy_clm_srt_dec}${mm_djf_srt}_${yyyy_end}${mm_djf_end}_climo.nc"
    if [ "${ann_flg}" = 'Yes' ]; then
	fl_out[${clm_nbr}]="${drc_out}/${out_nm}_ANN_${yyyy_clm_srt_dec}${mm_ann_srt}_${yyyy_end}${mm_ann_end}_climo.nc"
	clm_bnd[${clm_nbr}]="--clm_bnd=${yyyy_clm_srt_dec},${yyyy_end},${mm_ann_srt},${mm_ann_end},${tpd_out}"
    fi # !ann_flg
    # Derive all regridded and AMWG names from output names
    for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
	fl_amwg[${clm_idx}]=`expr match "${fl_out[${clm_idx}]}" '\(.*\)_.*_.*_climo.nc'` # Prune _YYYYYMM_YYYYMM_climo.nc
	fl_amwg[${clm_idx}]="${fl_amwg[${clm_idx}]}_climo.nc" # Replace with _climo.nc
	fl_amwg[${clm_idx}]="${fl_amwg[${clm_idx}]/${drc_out}\//}" # Delete prepended path to ease symlinking
	if [ -n "${rgr_opt}" ]; then
	    fl_rgr[${clm_idx}]="${fl_out[${clm_idx}]/${drc_out}/${drc_rgr}}"
	    if [ "${drc_out}" = "${drc_rgr}" ]; then 
		# Append geometry suffix to regridded files in same directory as native climo
		# http://tldp.org/LDP/abs/html/string-manipulation.html
		dfl_sfx='rgr'
		rgr_sfx=`expr match "${rgr_map}" '.*_to_\(.*\).nc'`
		if [ "${#rgr_sfx}" -eq 0 ]; then
		    printf "${spt_nm}: WARNING Unable to extract geometric suffix from mapfile, will suffix regridded files with \"${dfl_sfx}\" instead\n"
		    rgr_sfx=${dfl_sfx}
		else
		    yyyymmdd_sng=`expr match "${rgr_sfx}" '.*\(\.[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\)'` # Find YYYYYMMDD
		    if [ "${#yyyymmdd_sng}" -ne 0 ]; then
			rgr_sfx=${rgr_sfx%%${yyyymmdd_sng}} # Delete YYYYYMMDD
		    fi # !strlen
		fi # !strlen
		#    rgr_sfx=`expr match "${rgr_sfx}" '\(.*\)\.[0-9][0-9][0-9][0-9][0-9][0-9]'` # 
		fl_rgr[${clm_idx}]="${fl_rgr[${clm_idx}]/.nc/_${rgr_sfx}.nc}"
	    fi # !drc_rgr
	fi # !rgr_opt
    done # !clm_idx
fi # !clm_md

# Block 1 Loop N: Split
if [ "${sbs_flg}" = 'Yes' ]; then
	
    if [ "${glb_avg}" = 'Yes' ]; then 
	# 20191217 Global average timeseries require area variable
	var_xtr="${var_xtr},${area_nm}"
    else # !glb_avg
	# Extract area variable from model output known to contain it
	case ${mdl_nm} in
	    cam* | eam* | mosart* ) var_xtr="${var_xtr},${area_nm}" ;;
	esac # !mdl_nm
    fi # !glb_avg
	
    # Populate input file arguments fl_sgm with all files necessary to bracket each segment
    for ((sgm_idx=0;sgm_idx<sgm_nbr;sgm_idx++)); do
	fl_sgm[${sgm_idx}]=''

	if [ "${clm_md}" != 'hfs' ]; then
	    # Non-high-frequency timeseries receive exact file-lists to hyperslab by index (not by time-coordinate)
	    if [ "${mth_flg}" = 'Yes' ]; then
		let fl_idx_srt=${sgm_idx}*${ypf_max}*${fpy} # NB: 0-based
		let fl_idx_end=${fl_idx_srt}+${ypf_max}*${fpy} # NB: 1-based
		let yr_srt_sgm=${yr_srt_rth}+${sgm_idx}*${ypf_max}
		let yr_end_sgm=${yr_srt_sgm}+${ypf_max}
		if [ ${sgm_rmd} -ne 0 ] && [ ${sgm_idx} -eq ${sgm_nbrm1} ]; then
		    let fl_idx_end=${fl_idx_srt}+${sgm_rmd} # NB: 1-based
		    let yr_end_sgm=${yr_end_rth}
		fi # !sgm_nbrm1
	    else # !mth_flg
		let fl_idx_srt=${sgm_idx}*${ypf_max}*${fpy} # NB: 0-based
		let fl_idx_end=${fl_idx_srt}+${ypf_max}*${fpy} # NB: 1-based
		let yr_srt_sgm=${yr_srt_rth}+${sgm_idx}*${ypf_max}
		let yr_end_sgm=${yr_srt_sgm}+${ypf_max}-1
		if [ ${sgm_rmd} -ne 0 ] && [ ${sgm_idx} -eq ${sgm_nbrm1} ]; then
		    let fl_idx_end=${fl_idx_srt}+${sgm_rmd}*${fpy} # NB: 1-based
		    let yr_end_sgm=${yr_srt_sgm}+${sgm_rmd}-1
		fi # !sgm_nbrm1
	    fi # !mth_flg
	else # !clm_md
	    # High-frequency timeseries are hyperslabbed by date and so need fuzzy file-lists
	    # that may contain an extra file or two in case year-boundaries are intermediate records.
	    # 20210331: Prior to now, output high-frequency timeseries always start/end on year boundaries
	    # 20210331: Support high-frequency splitter mode mth_srt/end != Jan/Dec
	    # However, the number of files to read in each segment may differ, and is not known a priori
	    # NB: High-frequency timeseries underestimate fpy by [0,1) when fpy=dpy/dpf is non-integral
	    # 20210329: Current formulation assumes manual minimal file list is specified
	    # Extraneous (more than one non-overlapping) files at beginning of list will cause failure
	    # fxm: Omit extraneous files at start of list (extraneous at end are OK now)
	    let fpyp1=${fpy}+1
	    let fl_idx_srt=${sgm_idx}*${ypf_max}*${dpy}/${dpf} # NB: dpy/dpf more accurate than fpy
	    let fl_idx_end=${fl_idx_srt}+${ypf_max}*${fpyp1} # NB: use fpyp1 to overestimate fl_idx_end
	    let yr_srt_sgm=${yr_srt_rth}+${sgm_idx}*${ypf_max}
	    let yr_end_sgm=${yr_srt_sgm}+${ypf_max}-1
	    if [ ${sgm_rmd} -ne 0 ] && [ ${sgm_idx} -eq ${sgm_nbrm1} ]; then
		if [ "${mth_flg}" = 'Yes' ]; then
		    # 20210331 Peings
		    let fl_idx_end=${fl_idx_srt}+${sgm_rmd}
		    let yr_end_sgm=${yr_end_rth}
		else # !mth_flg
		    let fl_idx_end=${fl_idx_srt}+${sgm_rmd}*${fpyp1} # NB: use fpyp1 to overestimate fl_idx_end
		    let yr_end_sgm=${yr_srt_sgm}+${sgm_rmd}-1
		fi # !mth_flg
	    fi # !sgm_rmd || !sgm_nbrm1
	    # Fuzzify list by including files nominally before and after first and last expected files
	    if [ ${fl_idx_srt} -ne 0 ]; then
		let fl_idx_srt=${fl_idx_srt}-1
	    fi # !fl_idx_srt
	    # Fuzziness may generate indices that are too large
	    if [ ${fl_idx_end} -gt ${fl_nbr} ]; then
		let fl_idx_end=${fl_nbr}
	    fi # !fl_idx_end
	    if [ ${fl_idx_end} -lt ${fl_nbr} ]; then
		let fl_idx_end=${fl_idx_end}+1
	    fi # !fl_idx_end
	fi # !clm_md
	
	# NB: fl_idx_srt is zero-based while fl_idx_end one-based, i.e., the count, one more than the index
	for ((fl_idx=fl_idx_srt;fl_idx<fl_idx_end;fl_idx++)); do
	    fl_sgm[${sgm_idx}]="${fl_sgm[${sgm_idx}]} ${fl_in[${fl_idx}]}"
	done # !fl_idx
	yyyy_srt_sgm[${sgm_idx}]=`printf "%04d" ${yr_srt_sgm}`
	yyyy_end_sgm[${sgm_idx}]=`printf "%04d" ${yr_end_sgm}`
	let yr_endp1_sgm=${yr_end_sgm}+1
	yyyy_endp1_sgm[${sgm_idx}]=`printf "%04d" ${yr_endp1_sgm}`

	mm_srt_sgm[${sgm_idx}]='01'
	mm_end_sgm[${sgm_idx}]='12'
	mm_srt_sgm[${sgm_idx}]=${mm_srt}
	if [ ${sgm_idx} -eq ${sgm_nbrm1} ]; then
	    mm_end_sgm[${sgm_idx}]=${mm_end}
	else # !sgm_idx
	    mm_end_sgm[${sgm_idx}]=${mm_srtm1}
	fi # !sgm_idx
	let mm_endp1_sgm=${mm_end_sgm}+1
	mm_endp1_sgm[${sgm_idx}]=`printf "%02d" ${mm_endp1_sgm}`

	# Sanity check all files existence before wasting time and space on concatenation
	for fl_crr in ${fl_sgm[${sgm_idx}]} ; do
	    if [ ! -f "${fl_crr}" ]; then
		echo "${spt_nm}: ERROR Unable to find required input file ${fl_crr}"
		echo "${spt_nm}: HINT All files implied to exist by the climatology bounds (start/end year/month) and by the specified (with -P or -m) or default model type, must be in ${drc_in} before ${spt_nm} will proceed"
		exit 1
	    fi # ! -f
	done # !fl_crr

    done # !sgm_idx

    if [ "${clm_md}" = 'hfs' ]; then

	if [ ${dbg_lvl} -ge 1 ]; then
	    printf "dbg: HFS variable(s):\n"
	    printf "dpf = ${dpf}\n"
	    printf "dpy = ${dpy}\n"
	    printf "fl_idx_srt = ${fl_idx_srt}\n"
	    printf "fl_idx_end = ${fl_idx_end}\n"
	    printf "fl_nbr = ${fl_nbr}\n"
	    printf "fpy = ${fpy}\n"
	    printf "fpyp1 = ${fpyp1}\n"
	    printf "nco_opt = ${nco_opt}\n"
	    printf "ppn_opt = ${ppn_opt}\n"
	    printf "sgm_nbr = ${sgm_nbr}\n"
	    printf "sgm_rmd = ${sgm_rmd}\n"
	    printf "spl_opt = ${spl_opt}\n"
	    printf "tpd_out = ${tpd_out}\n"
	    printf "var_xtr = ${var_xtr}\n"
	    printf "ypf_max = ${ypf_max}\n"
	    printf "yr_nbr = ${yr_nbr}\n"
	    printf "yr_sbs = ${yr_sbs}\n"
	fi # !dbg

    fi # !clm_md

    if [ ${dbg_lvl} -ge 2 ]; then
	printf "dbg: Will split into files for ${var_nbr} variable(s):\n"
	for ((var_idx=0;var_idx<${var_nbr};var_idx++)); do
	    printf "${var_sbs[${var_idx}]}\n"
	done # !var_idx
	printf "dbg: Will split timeseries into ${sgm_nbr} segment(s):\n"
	for ((sgm_idx=0;sgm_idx<${sgm_nbr};sgm_idx++)); do
	    printf "Segment ${sgm_idx} months: ${yyyy_srt_sgm[${sgm_idx}]}${mm_srt_sgm[${sgm_idx}]}--${yyyy_end_sgm[${sgm_idx}]}${mm_end_sgm[${sgm_idx}]}\n"
	done # !sgm_idx
#	printf "dbg: Will split ${fl_nbr} files into ${sgm_nbr} segment(s):\n"
#	for ((sgm_idx=0;sgm_idx<${sgm_nbr};sgm_idx++)); do
#	    printf "${fl_sgm[${sgm_idx}]}\n"
#	done # !sgm_idx
    fi # !dbg

    # Create template output filenames (to avoid Bash 2D string arrays)
    for ((sgm_idx=0;sgm_idx<sgm_nbr;sgm_idx++)); do
	if [ -z "${fml_nm_usr}" ]; then 
	    fl_out_tpl[${sgm_idx}]="${drc_out}/var_nm_tpl_${yyyy_srt_sgm[${sgm_idx}]}${mm_srt_sgm[${sgm_idx}]}_${yyyy_end_sgm[${sgm_idx}]}${mm_end_sgm[${sgm_idx}]}.nc"
	else # !fml_nm_usr
	    fl_out_tpl[${sgm_idx}]="${drc_out}/var_nm_tpl_${fml_nm}_${yyyy_srt_sgm[${sgm_idx}]}${mm_srt_sgm[${sgm_idx}]}_${yyyy_end_sgm[${sgm_idx}]}${mm_end_sgm[${sgm_idx}]}.nc"
	fi # !fml_nm_usr
	if [ -n "${rgr_opt}" ]; then
	    fl_rgr_tpl[${sgm_idx}]="${fl_out_tpl[${sgm_idx}]/${drc_out}/${drc_rgr}}"
	    if [ "${drc_out}" = "${drc_rgr}" ]; then 
		# Append geometry suffix to regridded files in same directory as native climo
		# http://tldp.org/LDP/abs/html/string-manipulation.html
		dfl_sfx='rgr'
		rgr_sfx=`expr match "${rgr_map}" '.*_to_\(.*\).nc'`
		if [ "${#rgr_sfx}" -eq 0 ]; then
		    printf "${spt_nm}: WARNING Unable to extract geometric suffix from mapfile, will suffix regridded files with \"${dfl_sfx}\" instead\n"
		    rgr_sfx=${dfl_sfx}
		else
		    yyyymmdd_sng=`expr match "${rgr_sfx}" '.*\(\.[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\)'` # Find YYYYYMMDD
		    if [ "${#yyyymmdd_sng}" -ne 0 ]; then
			rgr_sfx=${rgr_sfx%%${yyyymmdd_sng}} # Delete YYYYYMMDD
		    fi # !strlen
		fi # !strlen
		#    rgr_sfx=`expr match "${rgr_sfx}" '\(.*\)\.[0-9][0-9][0-9][0-9][0-9][0-9]'` # 
		fl_rgr_tpl[${sgm_idx}]="${fl_rgr_tpl[${sgm_idx}]/.nc/_${rgr_sfx}.nc}"
	    fi # !drc_rgr
	fi # !rgr_opt
    done # !sgm_idx

    # Begin outer loop over time segments
    for ((sgm_idx=0;sgm_idx<sgm_nbr;sgm_idx++)); do

	# Begin loop over variables to split
	let bch_nbr=$((var_nbr / job_nbr))
	let bch_flg=$((var_nbr % job_nbr))
	if [ ${bch_nbr} -eq 0 ]; then
	    let bch_nbr=$((bch_nbr+1))
	elif [ ${bch_flg} -ne 0 ]; then
	    let bch_nbr=$((bch_nbr+1))
	fi # !bch_flg
	let bch_nbrm1=$((bch_nbr-1))
	for ((bch_idx=0;bch_idx<bch_nbr;bch_idx++)); do
	    # var_idx is 0-based, bch_idx is 0-based
	    let var_idx_srt=$((bch_idx * job_nbr))
	    let var_idx_end=$((var_idx_srt + job_nbr - 1))
	    if [ ${bch_idx} -eq ${bch_nbrm1} ] && [ ${bch_flg} -ne 0 ] ; then
		let var_idx_srt=$((bch_idx * job_nbr))
		let var_idx_end=$((var_nbr - 1))
	    fi # !bch_flg
	    for ((var_idx=var_idx_srt;var_idx<=var_idx_end;var_idx++)); do
		fl_out[${var_idx}]="${fl_out_tpl[${sgm_idx}]/var_nm_tpl/${var_sbs[${var_idx}]}}"
		if [ "${clm_md}" = 'hfs' ]; then
		    # 20200619 High-frequency output requires time-coordinate hyperslabs
		    if [ "${mth_flg}" = 'Yes' ]; then
			tm_srt="${yyyy_srt_sgm[${sgm_idx}]}-${mm_srt_sgm[${sgm_idx}]}-01T00:00:01"
			tm_end="${yyyy_end_sgm[${sgm_idx}]}-${mm_endp1_sgm[${sgm_idx}]}-01T00:00:00"
		    else # !mth_flg
			tm_srt="${yyyy_srt_sgm[${sgm_idx}]}-01-01T00:00:01"
			tm_end="${yyyy_endp1_sgm[${sgm_idx}]}-01-01T00:00:00"
		    fi # !mth_flg
		    if [ ${tpd_out} -lt 3600 ]; then
			cmd_sbs[${var_idx}]="${cmd_mpi[${var_idx}]} OMP_PROC_BIND=false ncrcat -O -v ${var_sbs[${var_idx}]}${var_xtr} ${nco_opt} ${spl_opt} ${gaa_sng} -d time,${tm_srt},${tm_end} ${ppn_opt} ${fl_sgm[${sgm_idx}]} ${fl_out[${var_idx}]}"
		    else
			# Deprecated code to output in sub-average periods or different shapes
			fl_rcat[${var_idx}]="${drc_out}/${var_sbs[${var_idx}]}_${yyyy_srt_sgm[${sgm_idx}]}01_${yyyy_end_sgm[${sgm_idx}]}12_ncrcat.nc"
			fl_ecat[${var_idx}]="${drc_out}/${var_sbs[${var_idx}]}_${yyyy_srt_sgm[${sgm_idx}]}01_${yyyy_end_sgm[${sgm_idx}]}12_ncecat.nc"
			fl_pdq[${var_idx}]="${drc_out}/${var_sbs[${var_idx}]}_${yyyy_srt_sgm[${sgm_idx}]}01_${yyyy_end_sgm[${sgm_idx}]}12_ncpdq.nc"
			fl_tpd_tpl="${drc_out}/${var_sbs[${var_idx}]}_${yyyy_srt_sgm[${sgm_idx}]}01_${yyyy_end_sgm[${sgm_idx}]}12_tpd"
			fl_tpd_all=''
			for ((tpd_idx=0;tpd_idx<tpd_out;tpd_idx++)); do
			    fl_tpd[${tpd_idx}]="${drc_out}/${var_sbs[${var_idx}]}_${yyyy_srt_sgm[${sgm_idx}]}01_${yyyy_end_sgm[${sgm_idx}]}12_tpd${tpd_idx}.nc"
			    fl_tpd_all="${fl_tpd_all} ${fl_tpd[${tpd_idx}]}"
			done # !tpd_idx
			# 20200627: Average all timesteps into one climatological record as data(time,space)
			# cmd_sbs[${var_idx}]="${cmd_mpi[${var_idx}]} OMP_PROC_BIND=false ncrcat -O -v ${var_sbs[${var_idx}]}${var_xtr} ${nco_opt} ${spl_opt} ${gaa_sng} -d time,${tm_srt},${tm_end} ${ppn_opt} ${fl_sgm[${sgm_idx}]} ${fl_rcat[${var_idx}]};/bin/rm -f ${fl_out[${var_idx}]};for srt in `seq 0 $((tpd_out-1))`; do ncra --rec_apn -d time,\${srt},,${tpd_out} ${nco_opt} ${fl_rcat[${var_idx}]} ${fl_out[${var_idx}]};done;/bin/rm -f ${fl_rcat[${var_idx}]}"
			# 20200628: Output one record per year as data(time,tpd,space), easier to regrid
			cmd_sbs[${var_idx}]="${cmd_mpi[${var_idx}]} OMP_PROC_BIND=false ncrcat -O -v ${var_sbs[${var_idx}]}${var_xtr} ${nco_opt} ${spl_opt} ${gaa_sng} -d time,${tm_srt},${tm_end} ${ppn_opt} ${fl_sgm[${sgm_idx}]} ${fl_rcat[${var_idx}]};/bin/rm -f ${fl_out[${var_idx}]};for srt in `seq 0 $((tpd_out-1))`; do ncrcat -O -v ${var_sbs[${var_idx}]}${var_xtr} -d time,\${srt},,${tpd_out} ${ppn_opt} ${fl_rcat[${var_idx}]} ${fl_tpd_tpl}\${srt}.nc;ncra -O --mro -d time,,,${dpy},${dpy} ${fl_tpd_tpl}\${srt}.nc ${fl_tpd_tpl}\${srt}.nc;done;/bin/rm -f ${fl_rcat[${var_idx}]};ncecat -O -u hour ${nco_opt} ${fl_tpd_all} ${fl_ecat[${var_idx}]};/bin/rm -f ${fl_tpd_all};ncap2 -O ${nco_opt} -s 'hour=array(0.0,24/\$hour.size,\$hour);*hr_dlt=(hour(1)-hour(0))/2;hour+=hr_dlt;hour@units=\"hours\";hour@long_name=\"Hour of Day\";hour_bnds=make_bounds(hour,\$nbnd,\"hour_bnds\")' ${fl_ecat[${var_idx}]} ${fl_pdq[${var_idx}]};/bin/rm -f ${fl_ecat[${var_idx}]};ncpdq -O ${nco_opt} -a time,hour ${fl_pdq[${var_idx}]} ${fl_out[${var_idx}]};/bin/rm -f ${fl_pdq[${var_idx}]}"
		    fi # !tpd_out
		else
		    # Normal splitting, non-high-frequency 
		    cmd_sbs[${var_idx}]="${cmd_mpi[${var_idx}]} OMP_PROC_BIND=false ncrcat -O -v ${var_sbs[${var_idx}]}${var_xtr} ${nco_opt} ${spl_opt} ${gaa_sng} ${ppn_opt} ${fl_sgm[${sgm_idx}]} ${fl_out[${var_idx}]}"
		fi # !clm_md
		if [ ${dbg_lvl} -ge 1 ]; then
		    echo ${cmd_sbs[${var_idx}]}
		fi # !dbg
		if [ ${dbg_lvl} -le 1 ]; then
		    if [ -z "${par_opt}" ]; then
			eval ${cmd_sbs[${var_idx}]}
			if [ "$?" -ne 0 ]; then
			    printf "${spt_nm}: ERROR subset cmd_sbs[${var_idx}] failed. Debug this:\n${cmd_sbs[${var_idx}]}\n"
			    exit 1
			fi # !err
		    else # !par_opt
			eval ${cmd_sbs[${var_idx}]} ${par_opt}
			sbs_pid[${var_idx}]=$!
		    fi # !par_opt
		fi # !dbg
	    done # !var_idx
	    
	    # Block NNN: Wait
	    # Parallel splitting (both Background and MPI) spawns simultaneous processes in batches of ${job_nbr}
	    # Once ${job_nbr} jobs are running, wait() for all to finish before issuing another batch
	    if [ -n "${par_opt}" ]; then
		#printf "${spt_nm}: DEBUG var_idx = ${var_idx}, bch_idx = ${bch_idx}, bch_flg = ${bch_flg}\n"
		for ((var_idx=var_idx_srt;var_idx<=var_idx_end;var_idx++)); do
		    wait ${sbs_pid[${var_idx}]}
		    if [ "$?" -eq 0 ]; then
			printf "`date`: Generated ${cmd_sbs[${var_idx}]##* }\n"
		    else
			printf "${spt_nm}: ERROR Failed to split. cmd_sbs[${var_idx}] failed. Debug this:\n${cmd_sbs[${var_idx}]}\n"
			# 20190323: exiting parent here creates orphans, kill live children then exit
			for ((kid_idx=var_idx+1;kid_idx<=var_idx_end;kid_idx++)); do
			    kill -9 ${sbs_pid[${kid_idx}]}
			done # !kid_idx
			exit 1
		    fi # !err
		done # !var_idx
	    fi # !par_opt
	    
	    if [ "${glb_avg}" = 'Yes' ]; then 
		for ((var_idx=var_idx_srt;var_idx<=var_idx_end;var_idx++)); do
		    cmd_avg[${var_idx}]="${cmd_mpi[${var_idx}]} OMP_PROC_BIND=false ncwa -O -w ${area_nm} -a ${hrz_dmn} ${fl_out[${var_idx}]} ${fl_out[${var_idx}]}"
		    if [ ${dbg_lvl} -ge 1 ]; then
			echo ${cmd_avg[${var_idx}]}
		    fi # !dbg
		    if [ ${dbg_lvl} -le 1 ]; then
			if [ -z "${par_opt}" ]; then
			    eval ${cmd_avg[${var_idx}]}
			    if [ "$?" -ne 0 ]; then
				printf "${spt_nm}: ERROR global average cmd_avg[${var_idx}] failed. Debug this:\n${cmd_avg[${var_idx}]}\n"
				exit 1
			    fi # !err
			else # !par_opt
			    eval ${cmd_avg[${var_idx}]} ${par_opt} # eval always returns 0 on backgrounded processes
			    avg_pid[${var_idx}]=$!
			fi # !par_opt
		    fi # !dbg
		done # !var_idx

		# Once ${job_nbr} jobs are running, wait() for all to finish before issuing another batch
		if [ -n "${par_opt}" ]; then
		    for ((var_idx=var_idx_srt;var_idx<=var_idx_end;var_idx++)); do
			wait ${avg_pid[${var_idx}]}
			if [ "$?" -eq 0 ]; then
			    printf "`date`: Globally averaged ${cmd_avg[${var_idx}]##* }\n"
			else
			    printf "${spt_nm}: ERROR Failed to globally average. cmd_avg[${var_idx}] failed. Debug this:\n${cmd_avg[${var_idx}]}\n"
			    # 20190323: exiting parent here creates orphans, kill live children then exit
			    for ((kid_idx=var_idx+1;kid_idx<=var_idx_end;kid_idx++)); do
				kill -9 ${avg_pid[${kid_idx}]}
			    done # !kid_idx
			    exit 1
			fi # !err
		    done # !var_idx
		fi # !par_opt
		
	    fi # !glb_avg
	    
	done # !bch_idx
	
	# Begin loop over variables to regrid
	if [ -n "${rgr_opt}" ]; then 
	    let bch_nbr=$((var_nbr / job_nbr))
	    let bch_flg=$((var_nbr % job_nbr))
	    if [ ${bch_nbr} -eq 0 ]; then
		let bch_nbr=$((bch_nbr+1))
	    elif [ ${bch_flg} -ne 0 ]; then
		let bch_nbr=$((bch_nbr+1))
	    fi # !bch_flg
	    let bch_nbrm1=$((bch_nbr-1))
	    for ((bch_idx=0;bch_idx<bch_nbr;bch_idx++)); do
		# var_idx is 0-based, bch_idx is 0-based
		let var_idx_srt=$((bch_idx * job_nbr))
		let var_idx_end=$((var_idx_srt + job_nbr - 1))
		if [ ${bch_idx} -eq ${bch_nbrm1} ] && [ ${bch_flg} -ne 0 ] ; then
		    let var_idx_srt=$((bch_idx * job_nbr))
		    let var_idx_end=$((var_nbr - 1))
		fi # !bch_flg
		for ((var_idx=var_idx_srt;var_idx<=var_idx_end;var_idx++)); do
		    fl_rgr[${var_idx}]="${fl_rgr_tpl[${sgm_idx}]/var_nm_tpl/${var_sbs[${var_idx}]}}"
		    #cmd_rgr[${var_idx}]="${cmd_mpi[${var_idx}]} ncks -O -t ${thr_nbr} ${nco_opt} ${rgr_opt} ${spl_opt} ${spl_rgr_opt} ${fl_out[${var_idx}]} ${fl_rgr[${var_idx}]}"
		    cmd_rgr[${var_idx}]="${cmd_mpi[${var_idx}]} ncremap -u .pid${spt_pid}.split.${var_idx}.tmp --nco_opt=\"${nco_opt}\" -t ${thr_nbr} ${spl_opt} ${spl_rgr_opt} ${prc_opt} ${rmp_opt} ${fl_out[${var_idx}]} ${fl_rgr[${var_idx}]}"
		    if [ "${mdl_typ}" = 'mpas' ]; then
			# 20181114: Remove '-C' argument from ncremap invocation for MPAS splitter only
			# This ensures CMIP6 MPAS timeseries will have _FillValue
			cmd_rgr[${var_idx}]="${cmd_mpi[${var_idx}]} ncremap -u .pid${spt_pid}.split.${var_idx}.tmp ${prc_opt} ${d2f_opt} ${dpt_opt} --nco_opt=\"${nco_opt}\" ${spl_opt} ${spl_rgr_opt} -t ${thr_nbr} ${map_opt} ${vrt_opt} ${fl_out[${var_idx}]} ${fl_rgr[${var_idx}]}"
		    fi # !mdl_typ
		    if [ ${dbg_lvl} -ge 1 ]; then
			echo ${cmd_rgr[${var_idx}]}
		    fi # !dbg
		    if [ ${dbg_lvl} -le 1 ]; then
			if [ -z "${par_opt}" ]; then
			    eval ${cmd_rgr[${var_idx}]}
			    if [ "$?" -ne 0 ]; then
				printf "${spt_nm}: ERROR regrid cmd_rgr[${var_idx}] failed. Debug this:\n${cmd_rgr[${var_idx}]}\n"
				exit 1
			    fi # !err
			else # !par_opt
			    eval ${cmd_rgr[${var_idx}]} ${par_opt}
			    rgr_pid[${var_idx}]=$!
			fi # !par_opt
		    fi # !dbg
		done # !var_idx
		    
		# Block NNN: Wait
		# Parallel regridding (both Background and MPI) spawns simultaneous processes in batches of ${job_nbr}
		# Once ${job_nbr} jobs are running, wait() for all to finish before issuing another batch
		if [ -n "${par_opt}" ]; then
		    for ((var_idx=var_idx_srt;var_idx<=var_idx_end;var_idx++)); do
			wait ${rgr_pid[${var_idx}]}
			if [ "$?" -eq 0 ]; then
			    printf "`date`: Regridded ${cmd_rgr[${var_idx}]##* }\n"
			else
			    printf "${spt_nm}: ERROR Failed to regrid. cmd_rgr[${var_idx}] failed. Debug this:\n${cmd_rgr[${var_idx}]}\n"
			    # 20190323: exiting parent here creates orphans, kill live children then exit
			    for ((kid_idx=var_idx+1;kid_idx<=var_idx_end;kid_idx++)); do
				kill -9 ${rgr_pid[${kid_idx}]}
			    done # !kid_idx
			    exit 1
			fi # !err
		    done # !var_idx
		fi # !par_opt

	    done # !bch_idx
	    
	    if [ "${no_ntv_tms}" = 'Yes' ]; then
		# Omit native-grid split timeseries by overwriting fl_out with fl_rgr
		for ((var_idx=0;var_idx<var_nbr;var_idx++)); do
		    /bin/mv -f ${fl_rgr[${var_idx}]} ${fl_out[${var_idx}]}
		    printf "`date`: Overwrote native timeseries with regridded for ${var_sbs[${var_idx}]}\n"
		done # !var_idx
	    fi # !no_ntv_tms

	fi # !rgr_opt
	
    done # !sgm_idx
    
fi # !sbs_flg

# Block 1 Loop 2: Climatologies based on annual means
if [ "${clm_flg}" = 'Yes' ] && [ "${clm_md}" = 'ann' ]; then
    clm_idx=1
    fl_all=''
    for yr in `seq ${yyyy_srt} ${yyyy_end}`; do
	YYYY=`printf "%04d" ${yr}`
	fl_all="${fl_all} ${caseid}.${mdl_nm}.${hst_nm}.${YYYY}-${ann_sfx}.nc"
    done # !yr
    # Check for existence of raw input only when file will be used (NB: next ~12 lines duplicate monthly code)
    if [ "${bnr_flg}" = 'No' ]; then
	for fl_crr in ${fl_all} ; do
	    if [ ! -e "${drc_in}/${fl_crr}" ]; then
		echo "${spt_nm}: ERROR Unable to find required input file ${drc_in}/${fl_crr}"
		echo "${spt_nm}: HINT All files implied to exist by the climatology bounds (start/end year/month) and by the specified (with -P or -m) or default model type, must be in ${drc_in} before ${spt_nm} will proceed"
		exit 1
	    fi # ! -e
	done # !fl_crr
    else # !bnr_flg
	# In binary mode drc_out is actually used to locate input files from climatology B (same as output files in incremental mode)
	drc_out="${drc_in}"
    fi # !bnr_flg
    fl_out[${clm_idx}]="${drc_out}/${out_nm}_ANN_${yyyy_srt}01_${yyyy_end}12_climo.nc"
    cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncra -O ${nco_opt} ${gaa_sng} -p ${drc_in} ${fl_all} ${fl_out[${clm_idx}]} ${par_opt}"

    # Block 1 Loop 2: Climatological annual mean
    printf "Climatological annual mean...\n"
    if [ ${dbg_lvl} -ge 1 ]; then
	echo ${cmd_clm[${clm_idx}]}
    fi # !dbg
    if [ ${dbg_lvl} -le 1 ]; then
	eval ${cmd_clm[${clm_idx}]}
	if [ "$?" -ne 0 ]; then
	    printf "${spt_nm}: ERROR annual climo cmd_clm[${clm_idx}] failed\n"
	    exit 1
	fi # !err
    fi # !dbg
    wait
    
    # Block 2: Regrid climatological annual mean
    if [ -n "${rgr_opt}" ]; then 
	printf "Regrid annual data...\n"
	cmd_rgr[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncks -O -t ${thr_nbr} ${nco_opt} ${rgr_opt} ${fl_out[${clm_idx}]} ${fl_out[${clm_idx}]/.nc/.rgr.nc}"
	if [ ${dbg_lvl} -ge 1 ]; then
	    echo ${cmd_rgr[${clm_idx}]} ${par_opt}
	fi # !dbg
	if [ ${dbg_lvl} -le 1 ]; then
	    eval ${cmd_rgr[${clm_idx}]} ${par_opt}
	    if [ "$?" -ne 0 ]; then
		printf "${spt_nm}: ERROR annual regrid cmd_rgr[${clm_idx}] failed\n"
		exit 1
	    fi # !err
	fi # !dbg
	wait
	printf "Done with regridding\n"
    fi # !rgr_opt
    
fi # !ann

# Block 1 Loop 2: Climatologies based on daily means
if [ "${clm_flg}" = 'Yes' ] && [ "${clm_md}" = 'dly' ]; then

    fl_all=''
    for ((fl_idx=0;fl_idx<fl_nbr;fl_idx++)); do
	fl_all="${fl_all} ${fl_in[${fl_idx}]}"
    done # !fl

    ppn_opt="-p ${drc_in}"
    if [ ${inp_std} = 'Yes' ]; then
	# 20171208 Detect whether stdin names are full-path (i.e., contain directory prefix)
	# If full-path, then use as-is later on, otherwise prepend drc_in
	# Currently this is only done for 'dly', yet could be adapted to sbs as well
	if [ "$(basename ${fl_in[0]})" != "${fl_in[0]}" ]; then
	    ppn_opt=''
	fi # !basename
    fi # !inp_std

    let srd=${dpy}*${tpd_out}
    drn=${tpd_out}
    
    yyyy_srt=`printf "%04d" ${yr_srt}`
    yyyy_end=`printf "%04d" ${yr_end}`
# 20200625 Off-by-one bug?
#    let yr_endp1=${yr_end}+1
#    yyyy_endp1=`printf "%04d" ${yr_endp1}`
    clm_idx=0
    for mth in `seq 1 12`; do
	MM=`printf "%02d" ${mth}`
	for day in `seq 1 ${dpm[${mth}]}`; do
	    DD=`printf "%02d" ${day}`
	    fl_out[${clm_idx}]="${drc_out}/${out_nm}_${yyyy_srt}${MM}${DD}_${yyyy_end}${MM}${DD}_climo.nc"
	    fl_cat[${clm_idx}]="${drc_out}/${out_nm}_${yyyy_srt}${MM}${DD}_${yyyy_end}${MM}${DD}_ncrcat.nc"
	    fl_rgr[${clm_idx}]="${fl_out[${clm_idx}]/${drc_out}/${drc_rgr}}"
	    tm_srt="${yyyy_srt}-${MM}-${DD}T00:00:00"
	    tm_end="${yyyy_end}-${MM}-${DD}T23:59:59"
	    if [ ${tpd_out} -eq 1 ]; then
		cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncra -O ${nco_opt} ${gaa_sng} -d time,${tm_srt},${tm_end},${srd},1 ${ppn_opt} ${fl_all} ${fl_out[${clm_idx}]}"
	    else
		# 20170619: Diurnal mode is really slow on MacOSX (UDUnits parsing sluggishness?)
		cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} OMP_PROC_BIND=false ncrcat -O ${nco_opt} ${gaa_sng} -d time,${tm_srt},${tm_end},${srd},${drn} ${ppn_opt} ${fl_all} ${fl_cat[${clm_idx}]};/bin/rm -f ${fl_out[${clm_idx}]};for srt in `seq 0 $((tpd_out-1))`; do ncra --rec_apn -d time,\${srt},,${tpd_out} ${nco_opt} ${gaa_sng} ${fl_cat[${clm_idx}]} ${fl_out[${clm_idx}]};done;/bin/rm -f ${fl_cat[${clm_idx}]}"
	    fi # !tpd
	    let clm_idx=${clm_idx}+1
	done # !day
    done # !mth

    if [ -n "${rgr_opt}" ]; then
	for ((clm_idx=0;clm_idx<clm_nbr;clm_idx++)); do
	    fl_rgr[${clm_idx}]="${fl_out[${clm_idx}]/${drc_out}/${drc_rgr}}"
	    if [ "${drc_out}" = "${drc_rgr}" ]; then 
		# Append geometry suffix to regridded files in same directory as native climo
		# http://tldp.org/LDP/abs/html/string-manipulation.html
		dfl_sfx='rgr'
		rgr_sfx=`expr match "${rgr_map}" '.*_to_\(.*\).nc'`
		if [ "${#rgr_sfx}" -eq 0 ]; then
		    printf "${spt_nm}: WARNING Unable to extract geometric suffix from mapfile, will suffix regridded files with \"${dfl_sfx}\" instead\n"
		    rgr_sfx=${dfl_sfx}
		else
		    yyyymmdd_sng=`expr match "${rgr_sfx}" '.*\(\.[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\)'` # Find YYYYYMMDD
		    if [ "${#yyyymmdd_sng}" -ne 0 ]; then
			rgr_sfx=${rgr_sfx%%${yyyymmdd_sng}} # Delete YYYYYMMDD
		    fi # !strlen
		fi # !strlen
		#    rgr_sfx=`expr match "${rgr_sfx}" '\(.*\)\.[0-9][0-9][0-9][0-9][0-9][0-9]'` # 
		fl_rgr[${clm_idx}]="${fl_rgr[${clm_idx}]/.nc/_${rgr_sfx}.nc}"
	    fi # !drc_rgr
	done # !clm_idx
    fi # !rgr_opt
    
    # Begin loop over days to climatologize
    printf "Climatological daily mean...\n"
    let bch_nbr=$((clm_nbr / job_nbr))
    let bch_flg=$((clm_nbr % job_nbr))
    if [ ${bch_nbr} -eq 0 ]; then
	let bch_nbr=$((bch_nbr+1))
    elif [ ${bch_flg} -ne 0 ]; then
	let bch_nbr=$((bch_nbr+1))
    fi # !bch_flg
    let bch_nbrm1=$((bch_nbr-1))
    for ((bch_idx=0;bch_idx<bch_nbr;bch_idx++)); do
	# clm_idx is 0-based, bch_idx is 0-based
	let clm_idx_srt=$((bch_idx * job_nbr))
	let clm_idx_end=$((clm_idx_srt + job_nbr - 1))
	if [ ${bch_idx} -eq ${bch_nbrm1} ] && [ ${bch_flg} -ne 0 ] ; then
	    let clm_idx_srt=$((bch_idx * job_nbr))
	    let clm_idx_end=$((clm_nbr - 1))
	fi # !bch_flg
	for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
	    if [ ${dbg_lvl} -ge 1 ]; then
		echo ${cmd_clm[${clm_idx}]}
	    fi # !dbg
	    if [ ${dbg_lvl} -le 1 ]; then
		if [ -z "${par_opt}" ]; then
		    eval ${cmd_clm[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR daily cmd_clm[${clm_idx}] failed. Debug this:\n${cmd_clm[${clm_idx}]}\n"
			exit 1
		    fi # !err
		else # !par_opt
		    eval ${cmd_clm[${clm_idx}]} ${par_opt}
		    clm_pid[${clm_idx}]=$!
		fi # !par_opt
	    fi # !dbg
	done # !fl_idx
	
	# Block NNN: Wait
	# Parallel splitting (both Background and MPI) spawns simultaneous processes in batches of ${job_nbr}
	# Once ${job_nbr} jobs are running, wait() for all to finish before issuing another batch
	if [ -n "${par_opt}" ]; then
	    if [ ${dbg_lvl} -ge 1 ] && [ ${clm_idx_srt} -le ${clm_idx_end} ]; then
		printf "${spt_nm}: Waiting for batch ${bch_idx} to finish at clm_idx = ${clm_idx}...\n"
	    fi # !dbg
	    for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
		wait ${clm_pid[${clm_idx}]}
		if [ "$?" -ne 0 ]; then
		    printf "${spt_nm}: ERROR Failed daily average. cmd_clm[${clm_idx}] failed. Debug this:\n${cmd_clm[${clm_idx}]}\n"
		    exit 1
		fi # !err
	    done # !clm_idx
	fi # !par_opt
	
    done # !bch_idx
    
    # Begin loop over days to regrid
    if [ -n "${rgr_opt}" ]; then 
	let bch_nbr=$((clm_nbr / job_nbr))
	let bch_flg=$((clm_nbr % job_nbr))
	if [ ${bch_nbr} -eq 0 ]; then
	    let bch_nbr=$((bch_nbr+1))
	elif [ ${bch_flg} -ne 0 ]; then
	    let bch_nbr=$((bch_nbr+1))
	fi # !bch_flg
	let bch_nbrm1=$((bch_nbr-1))
	for ((bch_idx=0;bch_idx<bch_nbr;bch_idx++)); do
	    # clm_idx is 0-based, bch_idx is 0-based
	    let clm_idx_srt=$((bch_idx * job_nbr))
	    let clm_idx_end=$((clm_idx_srt + job_nbr - 1))
	    if [ ${bch_idx} -eq ${bch_nbrm1} ] && [ ${bch_flg} -ne 0 ] ; then
		let clm_idx_srt=$((bch_idx * job_nbr))
		let clm_idx_end=$((clm_nbr - 1))
	    fi # !bch_flg
	    for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
		cmd_rgr[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncks -O -t ${thr_nbr} ${nco_opt} ${rgr_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
		if [ "${mdl_typ}" = 'mpas' ]; then
		    cmd_rgr[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncremap -C -u .pid${spt_pid}.daily.${clm_idx}.tmp ${prc_opt} ${d2f_opt} ${dpt_opt} --nco_opt=\"${nco_opt}\" -t ${thr_nbr} ${map_opt} ${vrt_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
		fi # !mdl_typ
		if [ ${dbg_lvl} -ge 1 ]; then
		    echo ${cmd_rgr[${clm_idx}]}
		fi # !dbg
		if [ ${dbg_lvl} -le 1 ]; then
		    if [ -z "${par_opt}" ]; then
			eval ${cmd_rgr[${clm_idx}]}
			if [ "$?" -ne 0 ]; then
			    printf "${spt_nm}: ERROR regrid cmd_rgr[${clm_idx}] failed. Debug this:\n${cmd_rgr[${clm_idx}]}\n"
			    exit 1
			fi # !err
		    else # !par_opt
			eval ${cmd_rgr[${clm_idx}]} ${par_opt}
			rgr_pid[${clm_idx}]=$!
		    fi # !par_opt
		fi # !dbg
	    done # !clm_idx
	    
	    # Block NNN: Wait
	    # Parallel regridding (both Background and MPI) spawns simultaneous processes in batches of ${job_nbr}
	    # Once ${job_nbr} jobs are running, wait() for all to finish before issuing another batch
	    if [ -n "${par_opt}" ]; then
		for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
		    wait ${rgr_pid[${clm_idx}]}
		    if [ "$?" -eq 0 ]; then
			printf "`date`: Regridded ${cmd_rgr[${clm_idx}]##* }\n"
		    else
			printf "${spt_nm}: ERROR Failed to regrid. cmd_rgr[${clm_idx}] failed. Debug this:\n${cmd_rgr[${clm_idx}]}\n"
			# 20190323: exiting parent here creates orphans, kill live children then exit
			for ((kid_idx=clm_idx+1;kid_idx<=clm_idx_end;kid_idx++)); do
			    kill -9 ${rgr_pid[${kid_idx}]}
			done # !kid_idx
			exit 1
		    fi # !err
		done # !clm_idx
	    fi # !par_opt
	    
	done # !bch_idx
    fi # !rgr_opt
fi # !dly
				      
# Many subsequent blocks only execute for normal and incremental monthly climos, not for binary climos, or non-monthly climos
if [ "${clm_flg}" = 'Yes' ] && [ "${clm_hfc_or_mth}" = 'Yes' ] && [ "${bnr_flg}" = 'No' ]; then
    
    # Block 1 Loop 2: Execute and/or echo monthly climatology commands
    printf "Generating climatology...\n"
    [[ ${dbg_lvl} -ge 1 ]] && date_clm=$(date +"%s")

    if [ ${job_nbr} -eq 1 ] || [ ${job_nbr} -eq 2 ] || [ ${job_nbr} -eq 3 ] || [ ${job_nbr} -eq 4 ] || [ ${job_nbr} -eq 6 ] || [ ${job_nbr} -eq 12 ]; then
	echo "Successful match of job_nbr to list of permissible values" > /dev/null
    else
	echo "${spt_nm}: ERROR Job number job_nbr=${job_nbr} is invalid in monthly climo mode. Must be a factor of 12. Valid values are 1, 2, 3, 4, 6, and 12. Please re-submit with valid job_nbr."
	exit 1
    fi # !job_nbr

    # Batch number is inverse to job number, so job_nbr=1->bch_nbr=12 (serial mode, slowest), job_nbr=2->bch_nbr=6, ... job_nbr=12->bch_nbr=1 (full background mode, fastest)
    let bch_nbr=$((12 / job_nbr))
    for ((bch_idx=0;bch_idx<bch_nbr;bch_idx++)); do
	# clm_idx is 1-based, bch_idx is 0-based
	let clm_idx_srt=$(((bch_idx * job_nbr) + 1))
	let clm_idx_end=$((clm_idx_srt + job_nbr - 1))
	#printf "${spt_nm}: DEBUG job_nbr = ${job_nbr}, bch_nbr = ${bch_nbr}, bch_idx = ${bch_idx}, clm_idx_srt = ${clm_idx_end}, clm_idx_end = ${clm_idx_end}\n"
	for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
	    printf "Climatological monthly mean for month ${clm_idx} ...\n"
	    if [ ${dbg_lvl} -ge 1 ]; then
		echo ${cmd_clm[${clm_idx}]}
	    fi # !dbg
	    if [ ${dbg_lvl} -le 1 ]; then
		if [ -z "${par_opt}" ]; then
		    eval ${cmd_clm[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR monthly climo cmd_clm[${clm_idx}] failed. Debug this:\n${cmd_clm[${clm_idx}]}\n"
			exit 1
		    fi # !err
		else # !par_opt
		    eval ${cmd_clm[${clm_idx}]} ${par_opt} # eval always returns 0 on backgrounded processes
		    clm_pid[${clm_idx}]=$!
		    # Potential alternatives to eval:
		    #	eval "${cmd_clm[${clm_idx}]}" # borken
		    #       ${cmd_clm[${clm_idx}]} # borken
		    #       "${cmd_clm[${clm_idx}]}" # borken
		    #	exec "${cmd_clm[${clm_idx}]}" # borken
		    #	$(${cmd_clm[${clm_idx}]}) # borken
		    #	$("${cmd_clm[${clm_idx}]}") # works (when & inside cmd quotes)
		fi # !par_opt
	    fi # !dbg
	done # !clm_idx
	if [ -n "${par_opt}" ]; then
	    for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
		wait ${clm_pid[${clm_idx}]}
		if [ "$?" -ne 0 ]; then
		    printf "${spt_nm}: ERROR monthly climo cmd_clm[${clm_idx}] failed. Debug this:\n${cmd_clm[${clm_idx}]}\n"
		    exit 1
		fi # !err
	    done # !clm_idx
	fi # !par_opt
    done # !bch_idx
    if [ ${dbg_lvl} -ge 1 ]; then
	date_crr=$(date +"%s")
	date_dff=$((date_crr-date_clm))
	echo "Elapsed time to generate monthly climos $((date_dff/60))m$((date_dff % 60))s"
    fi # !dbg
    # Clean-up monthly inputs (important since these file can be huge)
    if [ ${clm_md} = 'hfc' ]; then
	if [ "${fl_nbr}" -eq 1 ]; then
	    /bin/rm -f ${drc_out}/${out_nm}_[01][0-9]_tms_foo_${yyyy_clm_srt}_${yyyy_clm_end}.nc
	else    
	    /bin/rm -f ${drc_out}/${out_nm}_[01][0-9]_tpd[0-9][0-9]_*.nc
	fi # !fl_nbr
    fi # !clm_md
	
    # Block 1: Loop 4: Regrid first twelve files. Load-balance by using idle nodes (nodes not used for seasonal climatologies).
    if [ -n "${rgr_opt}" ]; then 
	printf "Regrid monthly data...\n"
	[[ ${dbg_lvl} -ge 1 ]] && date_rgr=$(date +"%s")
	for ((clm_idx=1;clm_idx<=12;clm_idx++)); do
	    # NB: Months, seasons, files are 1-based ([1..12], [13..16], [1..17]), nodes are 0-based ([0..11])
	    let nd_idx=$(((clm_idx-1+csn_nbr) % nd_nbr))
	    if [ ${nd_idx} -lt ${csn_nbr} ]; then
		let nd_idx=${nd_idx}+${csn_nbr}
		# 20190315: Not necessary to modulo final result by nd_nbr to handle csn_nbr > nd_nbr
		# because (1-based) cmd_mpi array is always at least 13 elements long and each element is
		# already round-robin'ed onto existing nodes
	    fi # !nd
	    # printf "dbg: clm_idx = ${clm_idx}, nd_idx = ${nd_idx}, csn_nbr = ${csn_nbr}, nd_nbr = ${nd_nbr}\n"
	    #cmd_rgr[${clm_idx}]="${cmd_mpi[${nd_idx}]} ncks -O -t ${thr_nbr} ${nco_opt} ${rgr_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
	    cmd_rgr[${clm_idx}]="${cmd_mpi[${nd_idx}]} ncremap -u .pid${spt_pid}.climo.${clm_idx}.tmp --nco_opt=\"${nco_opt}\" -t ${thr_nbr} ${prc_opt} ${rmp_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
	    if [ "${mdl_typ}" = 'mpas' ]; then
		cmd_rgr[${clm_idx}]="${cmd_mpi[${nd_idx}]} ncremap -C -u .pid${spt_pid}.climo.${clm_idx}.tmp ${prc_opt} ${d2f_opt} ${dpt_opt} --nco_opt=\"${nco_opt}\" -t ${thr_nbr} ${map_opt} ${vrt_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
	    fi # !mdl_typ
	    if [ ${dbg_lvl} -ge 1 ]; then
		echo ${cmd_rgr[${clm_idx}]}
	    fi # !dbg
	    if [ ${dbg_lvl} -le 1 ]; then
		if [ -z "${par_opt}" ]; then
		    eval ${cmd_rgr[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR monthly regrid cmd_rgr[${clm_idx}] failed. Debug this:\n${cmd_rgr[${clm_idx}]}\n"
			exit 1
		    fi # !err
		else # !par_opt
		    eval ${cmd_rgr[${clm_idx}]} ${par_opt}
		    rgr_pid[${clm_idx}]=$!
		fi # !par_opt
	    fi # !dbg
	done # !clm_idx
	# Start seasonal means first, then wait() for monthly regridding to finish
    fi # !rgr_opt
    
    # Block 2: Climatological seasonal means
    if [ "${csn_flg}" = 'Yes' ]; then
	# Block 2 Loop 1: Generate seasonal commands
	[[ ${dbg_lvl} -ge 1 ]] && date_clm=$(date +"%s")
#	if [ ${clm_md} != 'hfc' ]; then
#	    for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
#		clm_bnd[${clm_idx}]=''
#	    done # !clm_idx
#	fi # !clm_md
	for ((csn_idx=0;csn_idx<${csn_nbr};csn_idx++)); do
	    let clm_idx=1+12+${csn_idx}
	    fl_lst=''
	    for ((mth_idx=${csn_srt_idx[${map_r2d[${csn_idx}]}]};mth_idx<=${csn_end_idx[${map_r2d[${csn_idx}]}]};mth_idx++)); do
		fl_lst="${fl_lst} ${fl_out[${mth_idx}]}"
	    done # !mth_idx
	    cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} ${cmd_mth2csn} ${clm_bnd[${clm_idx}]} -O -w ${csn_wgt[${map_r2d[${csn_idx}]}]} ${nco_opt} ${gaa_sng} ${fl_lst} ${fl_out[${clm_idx}]}"
	    # DJF specified as exception since month-order is non-consecutive (12,1,2)
	    if [ "${csn_abb[${map_r2d[${csn_idx}]}]}" = 'DJF' ]; then
		cmd_clm[${clm_idx}]="${cmd_mpi[${clm_idx}]} ${cmd_mth2csn} ${clm_bnd[${clm_idx}]} -O -w 31,31,28 ${nco_opt} ${gaa_sng} ${fl_out[12]} ${fl_out[1]} ${fl_out[2]} ${fl_out[${clm_idx}]}"
	    fi # !DJF
	done # !csn_idx
	# cmd_clm[13]="${cmd_mpi[13]} ncra --cb -O -w 31,30,31 ${nco_opt} ${gaa_sng} ${fl_out[3]} ${fl_out[4]} ${fl_out[5]} ${fl_out[13]}"
	# cmd_clm[14]="${cmd_mpi[14]} ncra --cb -O -w 30,31,31 ${nco_opt} ${gaa_sng} ${fl_out[6]} ${fl_out[7]} ${fl_out[8]} ${fl_out[14]}"
	# cmd_clm[15]="${cmd_mpi[15]} ncra --cb -O -w 30,31,30 ${nco_opt} ${gaa_sng} ${fl_out[9]} ${fl_out[10]} ${fl_out[11]} ${fl_out[15]}"
	# cmd_clm[16]="${cmd_mpi[16]} ncra --cb -O -w 31,31,28 ${nco_opt} ${gaa_sng} ${fl_out[12]} ${fl_out[1]} ${fl_out[2]} ${fl_out[16]}"

	# Block 2 Loop 2: Execute and/or echo seasonal climatology commands
	let bch_nbr=$((csn_nbr / job_nbr))
	let bch_flg=$((csn_nbr % job_nbr))
	if [ ${bch_nbr} -eq 0 ]; then
	    let bch_nbr=$((bch_nbr+1))
	elif [ ${bch_flg} -ne 0 ]; then
	    let bch_nbr=$((bch_nbr+1))
	fi # !bch_flg
	let bch_nbrm1=$((bch_nbr-1))
	for ((bch_idx=0;bch_idx<bch_nbr;bch_idx++)); do
	    # clm_idx is 1-based, bch_idx is 0-based
	    let clm_idx_srt=$((12 + (bch_idx * job_nbr) + 1))
	    let clm_idx_end=$((clm_idx_srt + job_nbr - 1))
	    if [ ${bch_idx} -eq ${bch_nbrm1} ] && [ ${bch_flg} -ne 0 ] ; then
		let clm_idx_srt=$((12 + (bch_idx * job_nbr) + 1))
		let clm_idx_end=${clm_csn_end_idx}
	    fi # !bch_flg
	    for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
		let csn_idx=$((clm_idx - 12 - 1))
		printf "Climatological seasonal mean for ${csn_abb[${map_r2d[${csn_idx}]}]} ...\n"
		if [ ${dbg_lvl} -ge 1 ]; then
		    echo ${cmd_clm[${clm_idx}]}
		fi # !dbg
		if [ ${dbg_lvl} -le 1 ]; then
		    if [ -z "${par_opt}" ]; then
			eval ${cmd_clm[${clm_idx}]}
			if [ "$?" -ne 0 ]; then
			    printf "${spt_nm}: ERROR seasonal climo cmd_clm[${clm_idx}] failed. Debug this:\n${cmd_clm[${clm_idx}]}\n"
			    exit 1
			fi # !err
		    else # !par_opt
			eval ${cmd_clm[${clm_idx}]} ${par_opt}
			clm_pid[${clm_idx}]=$!
		    fi # !par_opt
		fi # !dbg
	    done # !clm_idx
	    if [ -n "${par_opt}" ]; then
		for ((clm_idx=clm_idx_srt;clm_idx<=clm_idx_end;clm_idx++)); do
		    wait ${clm_pid[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR seasonal climo cmd_clm[${clm_idx}] failed. Debug this:\n${cmd_clm[${clm_idx}]}\n"
			exit 1
		    fi # !err
		done # !clm_idx
	    fi # !par_opt
	done # !bch_idx
	# wait() for seasonal climatologies to finish
	if [ ${dbg_lvl} -ge 1 ]; then
	    date_crr=$(date +"%s")
	    date_dff=$((date_crr-date_clm))
	    echo "Elapsed time to generate seasonal climos $((date_dff/60))m$((date_dff % 60))s"
	fi # !dbg
    fi # !csn_flg
    # wait() for monthly regridding, if any, to finish
    if [ -n "${rgr_opt}" ]; then 
	if [ -n "${par_opt}" ]; then
	    for ((clm_idx=1;clm_idx<=12;clm_idx++)); do
		wait ${rgr_pid[${clm_idx}]}
		if [ "$?" -ne 0 ]; then
		    printf "${spt_nm}: ERROR monthly regrid cmd_rgr[${clm_idx}] failed. Debug this:\n${cmd_rgr[${clm_idx}]}\n"
		    exit 1
		fi # !err
	    done # !clm_idx
	fi # !par_opt
	if [ ${dbg_lvl} -ge 1 ]; then
	    date_crr=$(date +"%s")
	    date_dff=$((date_crr-date_rgr))
	    echo "Elapsed time to regrid monthly climos $((date_dff/60))m$((date_dff % 60))s"
	fi # !dbg
    fi # !rgr_opt
    
    # Block 2: Loop 4: Regrid seasonal files. Load-balance by using idle nodes (nodes not used for annual mean).
    if [ "${csn_flg}" = 'Yes' ]; then
	if [ -n "${rgr_opt}" ]; then 
	    printf "Regrid seasonal data...\n"
	    [[ ${dbg_lvl} -ge 1 ]] && date_rgr=$(date +"%s")
	    for ((clm_idx=${clm_csn_srt_idx};clm_idx<=${clm_csn_end_idx};clm_idx++)); do
		let nd_idx=$(((clm_idx-1+csn_nbr) % nd_nbr))
		if [ ${nd_idx} -lt ${csn_nbr} ]; then
		    let nd_idx=${nd_idx}+${csn_nbr}
		fi # !nd
		#cmd_rgr[${clm_idx}]="${cmd_mpi[${nd_idx}]} ncks -O -t ${thr_nbr} ${nco_opt} ${rgr_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
		cmd_rgr[${clm_idx}]="${cmd_mpi[${nd_idx}]} ncremap -u .pid${spt_pid}.climo.${clm_idx}.tmp --nco_opt=\"${nco_opt}\" -t ${thr_nbr} ${prc_opt} ${rmp_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
		if [ "${mdl_typ}" = 'mpas' ]; then
		    cmd_rgr[${clm_idx}]="${cmd_mpi[${nd_idx}]} ncremap -C -u .pid${spt_pid}.climo.${clm_idx}.tmp ${prc_opt} ${d2f_opt} ${dpt_opt} --nco_opt=\"${nco_opt}\" -t ${thr_nbr} ${map_opt} ${vrt_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
		fi # !mdl_typ
		if [ ${dbg_lvl} -ge 1 ]; then
		    echo ${cmd_rgr[${clm_idx}]}
		fi # !dbg
		if [ ${dbg_lvl} -le 1 ]; then
		    if [ -z "${par_opt}" ]; then
			eval ${cmd_rgr[${clm_idx}]}
			if [ "$?" -ne 0 ]; then
			    printf "${spt_nm}: ERROR seasonal regrid cmd_rgr[${clm_idx}] failed. Debug this:\n${cmd_rgr[${clm_idx}]}\n"
			    exit 1
			fi # !err
		    else # !par_opt
			eval ${cmd_rgr[${clm_idx}]} ${par_opt}
			rgr_pid[${clm_idx}]=$!
		    fi # !par_opt
		fi # !dbg
	    done # !clm_idx
	    # Start annual mean first, then wait() for seasonal regridding to finish
	fi # !rgr_opt
    fi # !csn_flg
    
    # Block 3: Climatological annual mean
    if [ "${ann_flg}" = 'Yes' ]; then
	printf "Climatological annual mean...\n"
	[[ ${dbg_lvl} -ge 1 ]] && date_clm=$(date +"%s")
	fl_lst="${fl_out[${clm_csn_mam_idx}]} ${fl_out[${clm_csn_jja_idx}]} ${fl_out[${clm_csn_son_idx}]} ${fl_out[${clm_csn_djf_idx}]}"
	cmd_clm[${clm_nbr}]="${cmd_mpi[${clm_nbr}]} ${cmd_csn2ann} ${clm_bnd[${clm_nbr}]} -O -w 92,92,91,90 ${nco_opt} ${gaa_sng} ${fl_lst} ${fl_out[${clm_nbr}]}"
	#    cmd_clm[17]="${cmd_mpi[17]} ncra --c2b -O -w 92,92,91,90 ${nco_opt} ${gaa_sng} ${fl_out[13]} ${fl_out[14]} ${fl_out[15]} ${fl_out[16]} ${fl_out[17]}"
	if [ ${dbg_lvl} -ge 1 ]; then
	    echo ${cmd_clm[${clm_nbr}]}
	fi # !dbg
	if [ ${dbg_lvl} -le 1 ]; then
	    if [ -z "${par_opt}" ]; then
		eval ${cmd_clm[${clm_nbr}]}
		if [ "$?" -ne 0 ]; then
		    printf "${spt_nm}: ERROR annual climo cmd_clm[${clm_nbr}] failed. Debug this:\n${cmd_clm[${clm_nbr}]}\n"
		    exit 1
		fi # !err
	    else # !par_opt
		eval ${cmd_clm[${clm_nbr}]} ${par_opt}
		clm_pid[${clm_nbr}]=$!
	    fi # !par_opt
	fi # !dbg
    fi # !ann_flg
    # wait() for seasonal regridding, if any, to finish
    if [ "${csn_flg}" = 'Yes' ]; then
	if [ -n "${rgr_opt}" ]; then 
	    if [ -n "${par_opt}" ]; then
		for ((clm_idx=${clm_csn_srt_idx};clm_idx<=${clm_csn_end_idx};clm_idx++)); do
		    wait ${rgr_pid[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR seasonal regrid cmd_rgr[${clm_idx}] failed. Debug this:\n${cmd_rgr[${clm_idx}]}\n"
			exit 1
		    fi # !err
		done # !clm_idx
	    fi # !par_opt
	    if [ ${dbg_lvl} -ge 1 ]; then
		date_crr=$(date +"%s")
		date_dff=$((date_crr-date_rgr))
		echo "Elapsed time to regrid seasonal climos $((date_dff/60))m$((date_dff % 60))s"
	    fi # !dbg
	fi # !rgr_opt
    fi # !csn_flg
    if [ "${ann_flg}" = 'Yes' ]; then
	clm_idx=${clm_nbr}
	# wait() for annual climatology to finish
	if [ -n "${par_opt}" ]; then
	    wait ${clm_pid[${clm_idx}]}
	    if [ "$?" -ne 0 ]; then
		printf "${spt_nm}: ERROR annual climo cmd_clm[${clm_idx}] failed. Debug this:\n${cmd_clm[${clm_idx}]}\n"
		exit 1
	    fi # !err
	fi # !par_opt
	if [ ${dbg_lvl} -ge 1 ]; then
	    date_crr=$(date +"%s")
	    date_dff=$((date_crr-date_clm))
	    echo "Elapsed time to generate annual climos $((date_dff/60))m$((date_dff % 60))s"
	fi # !dbg
	
	# Block 5: Regrid climatological annual mean
	if [ -n "${rgr_opt}" ]; then 
	    printf "Regrid annual data...\n"
	    [[ ${dbg_lvl} -ge 1 ]] && date_rgr=$(date +"%s")
	    #cmd_rgr[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncks -O -t ${thr_nbr} ${nco_opt} ${rgr_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
	    cmd_rgr[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncremap -u .pid${spt_pid}.climo.${clm_idx}.tmp --nco_opt=\"${nco_opt}\" -t ${thr_nbr} ${prc_opt} ${rmp_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
	    if [ "${mdl_typ}" = 'mpas' ]; then
a		cmd_rgr[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncremap -C -u .pid${spt_pid}.climo.${clm_idx}.tmp ${prc_opt} ${d2f_opt} ${dpt_opt} --nco_opt=\"${nco_opt}\" -t ${thr_nbr} ${map_opt} ${vrt_opt} ${fl_out[${clm_idx}]} ${fl_rgr[${clm_idx}]}"
	    fi # !mdl_typ
	    if [ ${dbg_lvl} -ge 1 ]; then
		echo ${cmd_rgr[${clm_idx}]}
	    fi # !dbg
	    if [ ${dbg_lvl} -le 1 ]; then
		# NB: Do not background climatological mean regridding since it is last step
		eval ${cmd_rgr[${clm_idx}]}
		if [ "$?" -ne 0 ]; then
		    printf "${spt_nm}: ERROR annual regrid cmd_rgr[${clm_idx}] failed. Debug this:\n${cmd_rgr[${clm_idx}]}\n"
		    exit 1
		fi # !err
	    fi # !dbg
	    if [ ${dbg_lvl} -ge 1 ]; then
		date_crr=$(date +"%s")
		date_dff=$((date_crr-date_rgr))
		echo "Elapsed time to regrid annual climo $((date_dff/60))m$((date_dff % 60))s"
	    fi # !dbg
	fi # !rgr_opt
    fi # !ann_flg
    
    # Link E3SM-climo to AMWG-climo filenames
    # drc_pwd is always fully qualified path but drc_out and drc_rgr may be relative paths
    # Strategy: Start in drc_pwd, cd to drc_rgr, then link so return code comes from ln not cd
    if [ ${lnk_flg} = 'Yes' ]; then
	printf "Link E3SM-climo to AMWG-climo filenames...\n"
	for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
	    if [ -n "${rgr_opt}" ]; then 
		cmd_lnk[${clm_idx}]="cd ${drc_pwd};cd ${drc_rgr};ln -s -f ${fl_rgr[${clm_idx}]/${drc_rgr}\//} ${fl_amwg[${clm_idx}]/${drc_rgr}\//}"
	    else
		cmd_lnk[${clm_idx}]="cd ${drc_pwd};cd ${drc_out};ln -s -f ${fl_out[${clm_idx}]/${drc_out}\//} ${fl_amwg[${clm_idx}]/${drc_out}\//}"
	    fi # !rgr_opt
	    if [ ${dbg_lvl} -ge 1 ]; then
		echo ${cmd_lnk[${clm_idx}]}
	    fi # !dbg
	    if [ ${dbg_lvl} -le 1 ]; then
		eval ${cmd_lnk[${clm_idx}]}
		if [ "$?" -ne 0 ]; then
		    printf "${spt_nm}: ERROR linking E3SM to AMWG filename cmd_lnk[${clm_idx}] failed. Debug this:\n${cmd_lnk[${clm_idx}]}\n"
		    exit 1
		fi # !err
	    fi # !dbg
	done # !clm_idx
	cd ${drc_pwd}
    fi # !lnk_flg
fi # !clm_md !bnr_flg

# Extended climos
if [ "${clm_flg}" = 'Yes' ] && [ "${xtn_flg}" = 'Yes' ]; then
    if [ -n "${drc_prv}" ] && [ ! -d "${drc_prv}" ]; then 
	cmd_mkd="mkdir -p ${drc_prv}"
	eval ${cmd_mkd}
	if [ "$?" -ne 0 ]; then
	    printf "${spt_nm}: ERROR Failed to create previous climatology directory. Debug this:\n${cmd_mkd}\n"
	    printf "${spt_nm}: HINT Creating a directory requires proper write permissions\n"
	    exit 1
	fi # !err
    fi # !drc_prv
    if [ -n "${drc_xtn}" ] && [ ! -d "${drc_xtn}" ]; then 
	cmd_mkd="mkdir -p ${drc_xtn}"
	eval ${cmd_mkd}
	if [ "$?" -ne 0 ]; then
	    printf "${spt_nm}: ERROR Failed to create extended climatology directory. Debug this:\n${cmd_mkd}\n"
	    printf "${spt_nm}: HINT Creating a directory requires proper write permissions\n"
	    exit 1
	fi # !err
    fi # !drc_xtn
    
    trim_leading_zeros ${yr_srt_prv}
    yr_srt_rth_prv=${sng_trm}
    yyyy_srt_prv=`printf "%04d" ${yr_srt_rth_prv}`
    yyyy_clm_srt_dec_prv=${yyyy_srt_prv}
    let yr_srtm1_prv=${yr_srt_rth_prv}-1
    if [ "${ncr_flg}" = 'Yes' ]; then
	let yr_end_prv=${yr_srt_rth}-1
    fi # !ncr_flg
    trim_leading_zeros ${yr_end_prv}
    yr_end_rth_prv=${sng_trm}
    yyyy_end_prv=`printf "%04d" ${yr_end_rth_prv}`
    let yr_endm1_prv=${yr_end_rth_prv}-1
    let yr_nbr_prv=${yr_end_rth_prv}-${yr_srt_rth_prv}+1
    let yr_nbr_xtn=${yr_nbr_prv}+${yr_nbr}

    wgt_prv=$(echo "${yr_nbr_prv}/${yr_nbr_xtn}" | bc -l)
    wgt_crr=$(echo "${yr_nbr}/${yr_nbr_xtn}" | bc -l)
    if [ "${bnr_flg}" = 'Yes' ]; then
	printf "Produce extended climatology as weighted average of two previously computed climatologies:\n"
    else # !bnr_flg
	printf "Produce extended climatology as weighted average of previously computed and incremental/new climatologies:\n"
    fi # !bnr_flg

    # Replace yr_srt by yr_srt_prv in "yrs_averaged" attribute
    nco_opt="${nco_opt/${yr_srt}-/${yr_srt_prv}-}"

    if [ "${clm_md}" = 'ann' ]; then
	printf "Previous/first climatology is ${yr_nbr_prv} years from ${yyyy_srt_prv} to ${yyyy_end_prv}, weight = ${wgt_prv}\n"
	printf "Current/second climatology is ${yr_nbr} years from ${yyyy_srt} to ${yyyy_end}, weight = ${wgt_crr}\n"
	printf "Extended climatology is ${yr_nbr_xtn} years from ${yyyy_srt_prv} to ${yyyy_end}\n"
    fi # !clm_md

    if [ "${clm_md}" = 'mth' ]; then

	printf "Previous/first climatology is ${yr_nbr_prv} years from ${yyyy_clm_srt_dec_prv}${mm_ann_srt} to ${yyyy_end_prv}${mm_ann_end}, weight = ${wgt_prv}\n"
	printf "Current/second climatology is ${yr_nbr} years from ${yyyy_clm_srt_dec}${mm_ann_srt} to ${yyyy_end}${mm_ann_end}, weight = ${wgt_crr}\n"
	printf "Extended climatology is ${yr_nbr_xtn} years from ${yyyy_clm_srt_dec_prv}${mm_ann_srt} to ${yyyy_end}${mm_ann_end}\n"
    
	clm_idx=0
	for mth in {01..12}; do
	    let clm_idx=${clm_idx}+1
	    MM=`printf "%02d" ${clm_idx}`
	    fl_prv[${clm_idx}]="${drc_prv}/${out_nm}_${MM}_${yyyy_srt_prv}${MM}_${yyyy_end_prv}${MM}_climo.nc"
	    fl_xtn[${clm_idx}]="${drc_xtn}/${out_nm}_${MM}_${yyyy_srt_prv}${MM}_${yyyy_end}${MM}_climo.nc"
	done # !mth
	if [ ${dec_md} = 'scd' ]; then 
	    yyyy_clm_srt_dec_prv=`printf "%04d" ${yr_srtm1_prv}`
	    yyyy_clm_end_dec_prv=`printf "%04d" ${yr_endm1_prv}`
	    clm_idx=12
	    MM=`printf "%02d" ${clm_idx}`
	    fl_prv[${clm_idx}]="${drc_prv}/${out_nm}_${MM}_${yyyy_clm_srt_dec_prv}${MM}_${yyyy_clm_end_dec_prv}${MM}_climo.nc"
	    fl_xtn[${clm_idx}]="${drc_xtn}/${out_nm}_${MM}_${yyyy_clm_srt_dec_prv}${MM}_${yyyy_clm_end_dec}${MM}_climo.nc"
	fi # !scd
	
	for ((csn_idx=0;csn_idx<${csn_nbr};csn_idx++)); do
	    # Produce names like "${out_nm}_MAM_${yyyy_srt_prv}03_${yyyy_end_prv}05_climo.nc"
	    let clm_idx=1+12+${csn_idx}
	    fl_prv[${clm_idx}]="${drc_prv}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_srt_prv}${csn_srt_mm[${map_r2d[${csn_idx}]}]}_${yyyy_end_prv}${csn_end_mm[${map_r2d[${csn_idx}]}]}_climo.nc"
	    fl_xtn[${clm_idx}]="${drc_xtn}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_srt_prv}${csn_srt_mm[${map_r2d[${csn_idx}]}]}_${yyyy_end}${csn_end_mm[${map_r2d[${csn_idx}]}]}_climo.nc"
	    if [ "${csn_abb[${map_r2d[${csn_idx}]}]}" = 'DJF' ]; then
		fl_prv[${clm_idx}]="${drc_prv}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_clm_srt_dec_prv}${mm_djf_srt}_${yyyy_end_prv}${mm_djf_end}_climo.nc"
		fl_xtn[${clm_idx}]="${drc_xtn}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_clm_srt_dec_prv}${mm_djf_srt}_${yyyy_end}${mm_djf_end}_climo.nc"
	    fi # !DJF
	    if [ "${csn_abb[${map_r2d[${csn_idx}]}]}" = 'ANN' ]; then
		fl_prv[${clm_idx}]="${drc_prv}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_clm_srt_dec_prv}${mm_ann_srt}_${yyyy_end_prv}${mm_ann_end}_climo.nc"
		fl_xtn[${clm_idx}]="${drc_xtn}/${out_nm}_${csn_abb[${map_r2d[${csn_idx}]}]}_${yyyy_clm_srt_dec_prv}${mm_ann_srt}_${yyyy_end}${mm_ann_end}_climo.nc"
	    fi # !DJF
	done # !csn_idx

#	fl_prv[13]="${drc_prv}/${out_nm}_MAM_${yyyy_srt_prv}03_${yyyy_end_prv}05_climo.nc"
#	fl_prv[14]="${drc_prv}/${out_nm}_JJA_${yyyy_srt_prv}06_${yyyy_end_prv}08_climo.nc"
#	fl_prv[15]="${drc_prv}/${out_nm}_SON_${yyyy_srt_prv}09_${yyyy_end_prv}11_climo.nc"
#	fl_prv[16]="${drc_prv}/${out_nm}_DJF_${yyyy_clm_srt_dec_prv}${mm_djf_srt}_${yyyy_end_prv}${mm_djf_end}_climo.nc"
#	fl_xtn[13]="${drc_xtn}/${out_nm}_MAM_${yyyy_srt_prv}03_${yyyy_end}05_climo.nc"
#	fl_xtn[14]="${drc_xtn}/${out_nm}_JJA_${yyyy_srt_prv}06_${yyyy_end}08_climo.nc"
#	fl_xtn[15]="${drc_xtn}/${out_nm}_SON_${yyyy_srt_prv}09_${yyyy_end}11_climo.nc"
#	fl_xtn[16]="${drc_xtn}/${out_nm}_DJF_${yyyy_clm_srt_dec_prv}${mm_djf_srt}_${yyyy_end}${mm_djf_end}_climo.nc"
	
	if [ "${ann_flg}" = 'Yes' ]; then
	    fl_prv[${clm_nbr}]="${drc_prv}/${out_nm}_ANN_${yyyy_clm_srt_dec_prv}${mm_ann_srt}_${yyyy_end_prv}${mm_ann_end}_climo.nc"
	    fl_xtn[${clm_nbr}]="${drc_xtn}/${out_nm}_ANN_${yyyy_clm_srt_dec_prv}${mm_ann_srt}_${yyyy_end}${mm_ann_end}_climo.nc"
	fi # !ann_flg
	
	# Derive all regridded and AMWG names from output names
	for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
	    fl_rgr_prv[${clm_idx}]="${fl_rgr[${clm_idx}]/${drc_rgr}/${drc_rgr_prv}}"
	    fl_rgr_prv[${clm_idx}]="${fl_rgr_prv[${clm_idx}]/_${yyyy_srt}/_${yyyy_srt_prv}}"
	    fl_rgr_prv[${clm_idx}]="${fl_rgr_prv[${clm_idx}]/_${yyyy_end}/_${yyyy_end_prv}}"
	    
	    fl_rgr_xtn[${clm_idx}]="${fl_rgr[${clm_idx}]/${drc_rgr}/${drc_rgr_xtn}}"
	    fl_rgr_xtn[${clm_idx}]="${fl_rgr_xtn[${clm_idx}]/_${yyyy_srt}/_${yyyy_srt_prv}}"
	    
	    fl_amwg_xtn[${clm_idx}]=`expr match "${fl_xtn[${clm_idx}]}" '\(.*\)_.*_.*_climo.nc'` # Prune _YYYYYMM_YYYYMM_climo.nc
	    fl_amwg_xtn[${clm_idx}]="${fl_amwg[${clm_idx}]}_climo.nc" # Replace with _climo.nc
	    fl_amwg_xtn[${clm_idx}]="${fl_amwg[${clm_idx}]/${drc_xtn}\//}" # Delete prepended path to ease symlinking
	    if [ ${dec_md} = 'scd' ] ; then
		# Handle Dec, DJF, and ANN
		let csn_idx=${clm_idx}-12-1
		let ann_idx=${clm_csn_end_idx}+1
		if [ ${csn_idx} -ge 0 ]; then
		    if [ "${csn_abb[${map_r2d[${csn_idx}]}]}" = 'DJF' ] || [ ${clm_idx} -eq ${ann_idx} ] ; then
			flg_djf_or_ann='Yes'
		    fi # !djf or ann
		fi # !csn_idx
		if [ ${clm_idx} -eq 12 ] || [ ${flg_djf_or_ann} = 'Yes' ] ; then 
		    fl_rgr_prv[${clm_idx}]="${fl_rgr[${clm_idx}]/${drc_rgr}/${drc_rgr_prv}}"
		    fl_rgr_prv[${clm_idx}]="${fl_rgr_prv[${clm_idx}]/_${yyyy_clm_srt_dec}/_${yyyy_clm_srt_dec_prv}}"
		    if [ ${clm_idx} -eq 12 ] ; then 
			fl_rgr_prv[${clm_idx}]="${fl_rgr_prv[${clm_idx}]/_${yyyy_clm_end_dec}/_${yyyy_clm_end_dec_prv}}"
		    else
			fl_rgr_prv[${clm_idx}]="${fl_rgr_prv[${clm_idx}]/_${yyyy_end}/_${yyyy_end_prv}}"
		    fi # !Dec
		    
		    fl_rgr_xtn[${clm_idx}]="${fl_rgr[${clm_idx}]/${drc_rgr}/${drc_rgr_xtn}}"
		    fl_rgr_xtn[${clm_idx}]="${fl_rgr_xtn[${clm_idx}]/_${yyyy_clm_srt_dec}/_${yyyy_clm_srt_dec_prv}}"
		fi # !Dec, DJF, ANN
	    fi # !dec_md
	done # !clm_idx
	
	printf "Weight input climos to produce extended climo...\n"
	for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
	    cmd_xtn[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncflint -O ${nco_opt} ${gaa_sng} -w ${wgt_prv},${wgt_crr} ${fl_prv[${clm_idx}]} ${fl_out[${clm_idx}]} ${fl_xtn[${clm_idx}]}"
	    if [ ${dbg_lvl} -ge 1 ]; then
		echo ${cmd_xtn[${clm_idx}]}
	    fi # !dbg
	    if [ ${dbg_lvl} -le 1 ]; then
		if [ -z "${par_opt}" ]; then
		    eval ${cmd_xtn[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR extended climo cmd_xtn[${clm_idx}] failed. Debug this:\n${cmd_xtn[${clm_idx}]}\n"
			exit 1
		    fi # !err
		else # !par_opt
		    eval ${cmd_xtn[${clm_idx}]} ${par_opt} # eval always returns 0 on backgrounded processes
		    xtn_pid[${clm_idx}]=$!
		fi # !par_opt
	    fi # !dbg
	done # !clm_idx
	if [ -n "${par_opt}" ]; then
	    for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
		wait ${xtn_pid[${clm_idx}]}
		if [ "$?" -ne 0 ]; then
		    printf "${spt_nm}: ERROR extended climo cmd_xtn[${clm_idx}] failed. Debug this:\n${cmd_xtn[${clm_idx}]}\n"
		    exit 1
		fi # !err
	    done # !clm_idx
	fi # !par_opt
	wait
	
	if [ -n "${rgr_opt}" ]; then 
	    printf "Weight input climos to produce extended regridded climo...\n"
	    for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
		cmd_rgr_xtn[${clm_idx}]="${cmd_mpi[${clm_idx}]} ncflint -O ${nco_opt} -w ${wgt_prv},${wgt_crr} ${fl_rgr_prv[${clm_idx}]} ${fl_rgr[${clm_idx}]} ${fl_rgr_xtn[${clm_idx}]}"
		if [ ${dbg_lvl} -ge 1 ]; then
		    echo ${cmd_rgr_xtn[${clm_idx}]}
		fi # !dbg
		if [ ${dbg_lvl} -le 1 ]; then
		    if [ -z "${par_opt}" ]; then
			eval ${cmd_rgr_xtn[${clm_idx}]}
			if [ "$?" -ne 0 ]; then
			    printf "${spt_nm}: ERROR extended climo cmd_rgr_xtn[${clm_idx}] failed. Debug this:\n${cmd_rgr_xtn[${clm_idx}]}\n"
			    exit 1
			fi # !err
		    else # !par_opt
			eval ${cmd_rgr_xtn[${clm_idx}]} ${par_opt} # eval always returns 0 on backgrounded processes
			rgr_xtn_pid[${clm_idx}]=$!
		    fi # !par_opt
		fi # !dbg
	    done # !clm_idx
	    if [ -n "${par_opt}" ]; then
		for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
		    wait ${rgr_xtn_pid[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR extended climo cmd_rgr_xtn[${clm_idx}] failed. Debug this:\n${cmd_rgr_xtn[${clm_idx}]}\n"
			exit 1
		    fi # !err
		done # !clm_idx
	    fi # !par_opt
	    wait
	fi # !rgr_opt
	
	# Link E3SM-climo to AMWG-climo filenames
	# drc_pwd is always fully qualified path but drc_out and drc_rgr may be relative paths
	# Strategy: Start in drc_pwd, cd to drc_rgr, then link so return code comes from ln not cd
	if [ ${lnk_flg} = 'Yes' ]; then
	    printf "Link extended E3SM-climo to AMWG-climo filenames...\n"
	    for ((clm_idx=1;clm_idx<=clm_nbr;clm_idx++)); do
		if [ -n "${rgr_opt}" ]; then 
		    cmd_lnk_xtn[${clm_idx}]="cd ${drc_pwd};cd ${drc_rgr_xtn};ln -s -f ${fl_rgr_xtn[${clm_idx}]/${drc_rgr_xtn}\//} ${fl_amwg[${clm_idx}]/${drc_rgr_xtn}\//}"
		else
		    cmd_lnk_xtn[${clm_idx}]="cd ${drc_pwd};cd ${drc_xtn};ln -s -f ${fl_xtn[${clm_idx}]/${drc_xtn}\//} ${fl_amwg[${clm_idx}]/${drc_xtn}\//}"
		fi # !rgr_opt
		if [ ${dbg_lvl} -ge 1 ]; then
		    echo ${cmd_lnk_xtn[${clm_idx}]}
		fi # !dbg
		if [ ${dbg_lvl} -le 1 ]; then
		    eval ${cmd_lnk_xtn[${clm_idx}]}
		    if [ "$?" -ne 0 ]; then
			printf "${spt_nm}: ERROR linking E3SM to AMWG filename cmd_lnk_xtn[${clm_idx}] failed. Debug this:\n${cmd_lnk_xtn[${clm_idx}]}\n"
			exit 1
		    fi # !err
		fi # !dbg
	    done # !clm_idx
	    cd ${drc_pwd}
	fi # !lnk_flg
    fi # !clm_md
    
else # !xtn_flg extended climos
    
    yr_nbr_xtn=${yr_nbr}
    
fi # !xtn_flg extended climos

if [ "${clm_flg}" = 'Yes' ]; then
    if [ "${clm_md}" = 'dly' ]; then
	echo "Quick plots of last climatological daily mean:"
	let idx_lst=${clm_nbr}-1
    else
	echo "Quick plots of last computed climatology:"
	let idx_lst=${clm_nbr}
    fi # !dly
    
    if [ -n "${yr_srt_prv}" ]; then
	if [ -n "${rgr_opt}" ]; then 
	    echo "ncview ${fl_rgr_xtn[${idx_lst}]} &"
	    echo "panoply ${fl_rgr_xtn[${idx_lst}]} &"
	else
	    echo "ncview ${fl_xtn[${idx_lst}]} &"
	    echo "panoply ${fl_xtn[${idx_lst}]} &"
	fi # !rgr_opt    
    else
	if [ -n "${rgr_opt}" ]; then 
	    echo "ncview ${fl_rgr[${idx_lst}]} &"
	    echo "panoply ${fl_rgr[${idx_lst}]} &"
	else
	    echo "ncview ${fl_out[${idx_lst}]} &"
	    echo "panoply ${fl_out[${idx_lst}]} &"
	fi # !rgr_opt    
    fi # !yr_srt_prv
fi # !clm_flg
if [ "${sbs_flg}" = 'Yes' ]; then
    echo "Quick plots of last timeseries segment of last variable split:"
    let idx_lst=${var_nbr}-1
    if [ -n "${rgr_opt}" ] && [ ${no_ntv_tms} = 'No' ] ; then 
	echo "ncview ${fl_rgr[${idx_lst}]} &"
	echo "panoply ${fl_rgr[${idx_lst}]} &"
    else
	echo "ncview ${fl_out[${idx_lst}]} &"
	echo "panoply ${fl_out[${idx_lst}]} &"
    fi # !rgr_opt    
fi # !sbs_flg
date_end=$(date +"%s")
if [ -n "${caseid}" ]; then
    printf "Completed ${yr_nbr_xtn}-year climatology operations for dataset with caseid = ${caseid} at `date`\n"
else # !caseid
    printf "Completed ${yr_nbr_xtn}-year climatology operations for input data at `date`\n"
fi # !caseid
date_dff=$((date_end-date_srt))
echo "Elapsed time $((date_dff/60))m$((date_dff % 60))s"

exit 0
