Command line arguments in NCL

Parameterising NCL

From the user forums on NCL, command line arguments are implemented like so:

ncl myarg=1 myscript.ncl

This will make the myarg integer variable available to myscript as if you had written it at the top of the script. The same works for floats and logicals (booleans).

ncl myfloat=-1.0 mybool=True myscript.ncl

For strings, it gets a little complex...

ncl 'mystring="thestring"' myscript.ncl

That's right, you have to wrap the whole variable definition in single quotes. This is usually ok until you are wrapping your NCL call in Bash, where for the wonderful laws of Bash everything inside single quotes is interpreted literally, no variable substitution for you!

ncl 'mystring="${mystring}"' myscript.ncl

To NCL, mystring will literally be ${mystring}, super useful. Oh, and there are no escape characters in NCL strings. so you can't do that either.

Thanks to some forum trawling, it appears that there is another way to feed string arguments into NCL by putting the argument after the script name. Being in double quotes, we can also do Bash variable substitution as well!

ncl myscript.ncl "mystring=\"${mystring}\""

You can combine the two approaches to form a convoluted but working parameterised NCL call, where fixed strings go on the left and dynamic (Bash-substituted) strings go on the right.

ncl myint=1 mybool=True myfloat=-1.0 myscript.ncl "mystring=\"${mystring}\""

What about default values?

If there is a variable that I am likely to want to change without changing the script, such as an incoming file path or switching a plot layer on or off, I generally make it a parameter that can be passed in to the script as above. This has the added benefit of having dynamic code that can be run in parallel for different arguments, without having to wait for the first execution to complete before tweaking one of the parameters slightly. The above methods are fine, if you are always going to be passing the arguments to the script (which can be good if you absolutely require a specific value). However, if you want to be able to run the script with some default (but occasionally different) values then it gets a little trickier.

The easiest way that I have found to parameterise my scripts with defaults is to but a block of arguments at the top of the script with some suitable defaults, to run the script without any of the tweaks.

(in myscript.ncl)

; Script arguments
if (.not.isdefined("myint")) then
    myint = 1
end if


This way I can run the script as normal ncl myscript.ncl or with an overridden value for myint ncl myint=2 myscript.ncl and still get a result.

CAUTION: NCL is generally not clever enough to check the data type of the value without extra code, so make sure you set and pass values of the correct type before trying to use them later in the script!

If you are like me and hate having extraneous junk at the top of the script before the real work begins, you can collapse the conditionals into a nice block at the top of the script.

; Script arguments
if (.not.isdefined("myint")) then myint=1 end if
if (.not.isdefined("myfloat")) then myfloat=1.0 end if
if (.not.isdefined("mybool")) then mybool=True end if
if (.not.isdefined("string")) then mystring="thestring" end if

Lastly, the .not.isdefined() condition can be useful in changing the dynamic flow of your script rather than just setting parameters. For instance, if you wanted to be able to optionally set contour levels then you could try something like this:

res = True

if (.not.isdefined("levels")) then

    res@ncLevelSelectionMode = "AutomaticLevels"


    res@cnLevelSelectionMode = "ExplicitLevels"

    ; Using some string manipulation-fu to make an easy override.
    levels := tofloat(str_split(levels,","))

    ; Add levels to the script with 'levels="min,max,steps"'
    res@cnLevels = fspan(levels(0),levels(1),levels(2))

end if

And there you go, optional contour level tweaking without having to change the resource object in the code.

That's all on this one, enjoy.