I'm trying to use gnuplot from C++ application, gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04). I've encountered strange behavior concerning plotting to files.
So, the reproducible example is:
#include <iostream>
#include <filesystem>
int main()
{
// whatever valid filename
std::string name1 = "/tmp/1.png";
// open gnuplot pipe
auto gp = popen("gnuplot", "w");
// plot sin(x) to file. Note "unset output" in the end.
std::string cmd="set term png\nset output '"+name1+"'\nplot sin(x)\nunset output\n";
// send the command to gnuplot
fwrite(cmd.c_str(), sizeof(char), cmd.length(), gp);
std::error_code ec;
// removing the file
if (!std::filesystem::remove(name1, ec))
std::cout<<"unsuccesfully: "<<ec.value()<<"\s"<<ec.message()<<"\n";
pclose(gp);
return 0;
}
The output is (very strange):
unsuccesfully: 0 Success
What happens: gnuplot successfully writes a valid png file to desired destination. However, std::filesystem::remove does not remove the file, returns false
and (therefore) prints cryptic message about success with error code 0.
Moving pclose(gp);
line before std::filesystem::remove
solves the problem, so it does look like gnuplot holds the file. What is also strange that if I do the same manually, I mean, I launch gnuplot, issue the same command, and not exit, I'm able to remove the file with unlink /tmp/1.png
. I'm aware about gnuplot's set output
or unset output
requirement, and tried both variants.
Why the std::filesystem::remove
acts this strange?
Why the std::filesystem::remove acts this strange?
You seem to misunderstand the return value and the error code (ec
in your code) of std::filesystem::remove()
. The function does not raise an error if the file you are trying to remove does not exist (ec
will be zero). Only the function without error_code&
returns false
if the file you are trying to remove doesn't exist and true
when it does. See the description of std::filesystem::remove()
on cppreference.com.
Effects: the file or empty directory identified by the path p is deleted as if by the POSIX remove. Symlinks are not followed (symlink is removed, not its target).
Returns:
true
if the file was deleted,false
if it did not exist. The overload that takeserror_code&
argument returnsfalse
on errors.
Since no error is raised, because there are just no files to be removed, ec.value()
in your code will return 0
, indicating successful completion.
It's a bit like the behavior of the UNIX command 'rm -f'.
You can check the behavior of std::filesyste::remove()
with inserting the following into your code.
std::error_code ec;
int retval = std::filesystem::remove(name1, ec);
if ( ! ec ) { // Success
std::cout<<"successful: \n";
if ( retval ) {
std::cout<<"file existed and removed\n";
}
else {
std::cout<<"file didn't exist\n";
}
}
else { // Error
std::cout<<"unsuccessful: "<<ec.value()<<" "<<ec.message()<<"\n";
}
The reason why the position of pclose()
changes the result is that the stream opened by popen()
is buffered.
When std::filesystem::remove()
is called, the command written by fwrite()
is not yet received by gnuplot because of the buffering. Therefore, in this step, the file "/tmp/1.png" has not been created yet.
Then, when pclose()
is called, gnuplot receives the commands, and the file "/tmp/1.png" is created by gnuplot. The file that you looked at was the one created after calling std::filesystem::remove()
.
You can flush the buffer explicitly using the function fflush()
. However, even if you use fflush()
, there is still a possibility that std::filesystem::remove()
will be called before the gnuplot command has finished due to the asynchronous nature of popen.
To ensure that the file is erased after gnuplot process finished, you will need the implementation (or wrapper libraries) that gnuplot and c++ program can be synchronized.