0

I need to plot multiple files in one plot with a bash script. I can manage this by stating the location of the files manually in plot function ie

plot "$outputdir/10/values.csv" using 1:2 with lines title "10", \
"$outputdir/20/values.csv" using 1:2 with lines title "20", \
"$outputdir/30/values.csv" using 1:2 with lines title "30"

This will print all three values files in one plot. But I have a dynamic array which changes depending on how many values it is suppose to get. So lets say the array arr looks like this instead

arr=(10 20 30 40)
#Sometimes arr has more values ex; arr=(10 20 30 40 50 60 70 80)
#arr corresponds to folders that will be created
#And will contain a values.csv file that I want to plot

Then the 40/values.csv will not be printed if I don't manually change the plot code

My current attempt to fix this is a for loop in gnuplot

#The array arr is dynamically generated in another function
#Varies in size as example above

ArrLength=${#arr[@]} #Get the length of array
plot for [p=0:$ArrLength] "$outputdir/${arr[$p]}/values.csv" using 1:2 with lines title "${arr[$p]}"

I don't get a error when plotting but in plot only one value is plotted and thats the first value in array ie it will only plot $outputdir/10/values.csv.

I tried setting p=0:4 to plot the first five files and still it only plotted the first file only. What is wrong with the for loop above?

Best regards

S4M11R
  • 399
  • 2
  • 5
  • 14

1 Answers1

2

You seem to be mixing bash and gnuplot in a strange way. Using a bash script to try to generate a gnuplot script on the fly with inserted variables is a quick way to confuse yourself. It also makes it difficult to read and debug. It is easy to forget what bash is evaluating and what gnuplot is evaluating.

When I look at your first line

ArrLength=${#arr[@]} #Get the length of array

I can see that this is bash code because gnuplot would interpret a comment beginning with the first #. (This is also bash's syntax for arrays, not gnuplot's.) The dollar sign $ has a different meaning in gnuplot. Rather than mark a variable identifier, $ is a column number operator ($2 is column 2, $i is column i, etc.). So look at the line

plot for [p=0:$ArrLength] "$outputdir/${arr[$p]}/values.csv" using 1:2 with lines title "${arr[$p]}"

This is clearly a line of bash syntax, apparently inside a string trying to write a line of gnuplot. Bash will evaluate the variables $ArrLength, $outputdir, and ${arr[$p]}, and replace them with some string of their values. Also keep in mind that p is a variable in gnuplot, not a variable in bash. Bash will evaluate $p to something (an empty string if it has not been defined). You can't expect the gnuplot variable p to be used as the index in the bash evaluation of ${arr[$p]}, and then somehow result in a different string for each iteration of gnuplot's loop.

In short, what you have written is not gnuplot syntax, and it is really not a minimal and complete bash script either. It is not clear exactly how you intended bash and gnuplot to fit together like this, but it seems you have joined them too tightly.


My suggestion is to write a bash script and write a gnuplot script (as separate files). Gnuplot has its own flow control, iteration loops, and variable evaluation. You can write a self-contained gnuplot script for the general case of everything you need it to do, and then give it specifics on the command line from your bash script.

For example, it seems that your subdirectories are all multiples of 10, and always starting with 10. The only variable aspect is how many there are (what the last one is). Let's say this last value was somehow stored in a gnuplot variable last. Also, suppose we also somehow have the base output directory in outputdir:

(Inside the gnuplot script, named plot.gp):

plot for [p=10:last:10] sprintf("%s/%d/values.csv", outputdir, p) with lines title sprintf("%d", p)

The for [p=10:last:10] means to iterate from 10 through last (inclusive), adding 10 at each iteration. The first sprintf() function (like C) builds a string with the outputdir and p (both are variables in gnuplot). The using 1:2 is not necessary as the first two columns are the default columns to use with lines, but you can include them if you want to be explicit. The second sprintf() builds a title string from the iteration variable p.

Now, this assumes that outputdir and last have meaningful values. You can assign these values from your bash script when you invoke gnuplot on the command line:

(Inside the bash script, invoke the gnuplot script)

gnuplot -e "last=40" -e "outputdir=\"$outputdir\"" plot.gp

The -e option tells gnuplot to evaluate the given string before running the script in the file plot.gp. In this example, the gnuplot variable last will have the value 40 and the gnuplot variable outputdir will have whatever value bash evaluates $outputdir to be. Notice the escaped double quotes inside double quotes. The outer double quotes are to allow bash to evaluate variables inside the string ($outputdir needs to be evaluated by bash). The inner (escaped) quotes are to delimit the string within the gnuplot code. For example, if bash evaluates $outputdir to data, then gnuplot would see outputdir="data" which is a valid gnuplot assignment of a string to the variable outputdir. You could, if you want, combine these two -e options into one:

gnuplot -e "last=40;outputdir=\"$outputdir\"" plot.gp

You will likely want to use the value for last from your array in bash, rather than hard coding it like this. So in practice it may look more like

gnuplot -e "last=${arr[${#arr[@]}-1]};outputdir=\"$outputdir\"" plot.gp

Or, if you have bash 4.3 or later, you should be able to use a negative index:

gnuplot -e "last=${arr[-1]};outputdir=\"$outputdir\"" plot.gp

Notice that there are no escaped quotes around the use of the array variable. It is expected that it will evaluate to an integer (40, 90, etc.) and we want to assign last to an integer, not a string like outputdir.

If this one string seems complex, try thinking about the entire script like this. It would be easy to get confused as to what bash is doing and what gnuplot is doing.

In summary, write a bash script, and a separate gnuplot script. Gnuplot is capable of handling a general case. From bash, just give it some specifics on the fly, don't try to generate the entire script on the fly. It really does make things simpler.

Community
  • 1
  • 1
e0k
  • 6,961
  • 2
  • 23
  • 30
  • Did not know that gnuplot would not accept bash syntax in a bash script. I will have to rethink this one. Thanks for the thorough and lengthy explanation. – S4M11R Dec 22 '16 at 09:16