Providing variable length args to `run` command?

Hey all. I’m having some trouble using the run command with a variable number of args to the thing I’m trying to run. For context, I’m playing around with some ways to very directly interact with Gnuplot scripts. I’m aware that there are several existing interfaces and I’m not trying to do anything serious here, but now I want to know how to do this correctly. Here’s my little snippet:

using DelimitedFiles

# Default kwargs. Probably eventually needs to be a dict of dicts, with the
# outer index being the "scriptname" arg, like :lineplot used in ARGORDER below.
const DEF = Dict(:terminal=>"pngcairo", :xsize=>500, :ysize=>500, 
                 :font=>"Verdana", :fontsize=>10, :outputname=>"output.png",
                :tmpfile=>nothing)

# Could use this to automatically splat out the args in the right order without
# having to touch the plot function internals.
const ARGORDER = Dict(:lineplot=>(:terminal, :xsize, :ysize, :font, :fontsize, :outputname))

# Common list of errors.
const ERRORS = Dict(:argerror=>ArgumentError("Here's a helpful error message."))

function plot(x, y; scriptname=:lineplot, kwargs...)
  args = merge(DEF, kwargs) # TODO: check that no kwarg is silently ignored.
  scriptfile = string(scriptname, ".gp")
  tmpfile = isnothing(args[:tmpfile]) ? mktemp()[1] : args[:tmpfile]
  writedlm(tmpfile, hcat(x,y), ' ')
  if haskey(ARGORDER, scriptname)
    # not sure if string conversion is necessary, but just trying to cover all my bases.
    gp_args = map(x->string(args[x]), ARGORDER[scriptname])
  else
    haskey(args, :argorder) || throw(ERRORS[:argerror])
    gp_args = map(x->string(args[x]), args[:argorder])
  end
  # (i): What is the correct way to supply these args? The help seems to suggest
  # that this is a fine way to do it, but clearly I'm misreading ?run.
  # run(`gnuplot -c $scriptfile $tmpfile`, gp_args...)
  #
  # (ii): Alternatively, I have tried this, but this leaves an extra set of
  # backticks, so I somehow am not splatting these args correctly.
  # run(`gnuplot -c $scriptfile $tmpfile $(map(x->string(x, " "), gp_args)...)`)
  run(`xdg-open $(args[:outputname])`)
end

And the corresponding lineplot.gp file is

set terminal ARG2 size ARG3,ARG4 enhanced font 'ARG5,ARG6'

set autoscale xfix
set autoscale yfix

set output ARG7
plot ARG1 using 1:2 with linespoints pointtype 6 notitle
unset output

The big comment chunk of the first snippet show two things that I’ve tried to do to splat the args provided by ARGORDER[:lineplot] into the run command, but I am clearly both misinterpreting what args... means in the help provided by ?run (attempt i) AND not understanding how to properly splat in a string interpolation-type thing for (attempt ii).

here’s an example call that should (at least on linux) work once these args get parsed properly:

plot(1:10, map(abs2, 1:10))

If anybody has pointers, I would really appreciate it. Thanks!

You can inspect the command without executing it:

julia> scriptfile = "lineplot.gp";

julia> tmpfile = "/tmp/jl_XXXXXX";

julia> gp_args = ("pngcairo", "500", "500", "Verdana", "10", "output.png");

julia> cmd = `gnuplot -c $scriptfile $tmpfile $(map(x->string(x, " "), gp_args)...)`
`gnuplot -c lineplot.gp /tmp/jl_XXXXXX 'pngcairo 500 500 Verdana 10 output.png '`

Not good: many values will be passed to gnuplot as a single parameter: 'pngcairo 500 500 Verdana 10 output.png '

What you want is simply:

julia> cmd = `gnuplot -c $scriptfile $tmpfile $gp_args`
`gnuplot -c lineplot.gp /tmp/jl_XXXXXX pngcairo 500 500 Verdana 10 output.png`

This works because a collection (here gp_args) will be interpolated as a list of command parameters. See the documentation for details and examples.

Your first idea run(`gnuplot -c $scriptfile $tmpfile`, gp_args...) doesn’t work because command arguments must be part of the command object. It’s true that run accepts additional arguments but they are unrelated (the documentation could be improved in this regard).

Your second idea is… interesting :slight_smile: It’s basically doing this:

julia> `echo $(("a", "b")...)`
`echo ab`

What does it mean to splat values in $(...)? I’m not sure how this works… And the result is different from

julia> `echo $("a", "b")`
`echo a b`
1 Like

My hero. Thanks so much for the explanation and doc links, which of course do provide examples of exactly what I wanted to do. Oof. I always think that I googled the right words and ended up on the right docs, but here we are. Thanks again!

1 Like