【问题标题】:Gnuplot and std::filesystem::removeGnuplot 和 std::filesystem::remove
【发布时间】:2021-03-03 17:08:02
【问题描述】:

我正在尝试使用来自 C++ 应用程序的 gnuplot,gcc 版本 9.3.0(Ubuntu 9.3.0-17ubuntu1~20.04)。我遇到了关于打印到文件的奇怪行为。

所以,可重现的例子是:

#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;
}

输出是(很奇怪):

unsuccesfully: 0 Success

会发生什么:gnuplot 成功地将有效的 png 文件写入所需的目的地。但是,std::filesystem::remove 不会删除文件,而是返回 false 并(因此)打印有关成功的神秘消息,错误代码为 0。 在std::filesystem::remove 之前移动pclose(gp); 行可以解决问题,因此看起来gnuplot 确实保存了该文件。同样奇怪的是,如果我手动执行相同操作,我的意思是,我启动 gnuplot,发出相同的命令,而不是退出,我可以使用unlink /tmp/1.png 删除文件。我知道 gnuplot 的 set output 或 unset output 要求,并尝试了这两种变体。

为什么std::filesystem::remove 表现得这么奇怪?

【问题讨论】:

    标签: c++ ubuntu gcc gnuplot std-filesystem


    【解决方案1】:

    为什么 std::filesystem::remove 行为如此奇怪?

    您似乎误解了std::filesyste::remove() 的返回值和错误值(代码中的ec)。即使没有您要删除的文件,该函数也不会引发错误。如果没有您要删除的文件,该函数返回false;否则,它返回true。参见the draft of C++17中'std::filesystem::remove()'的文档。

    效果:如果存在(symlink_status(p, ec)),则文件 p 会像 POSIX remove() 一样被删除。

    返回:如果 p 不存在则返回 false,否则返回 true。如果发生错误,带有参数 ec 的签名将返回 false。

    由于没有文件要删除,不会引发错误,所以代码中的ec.value()会返回0,表示成功完成。

    这有点像 UNIX 命令 'rm -f' 的行为。

    您可以通过在代码中插入以下内容来检查std::filesyste::remove() 的行为。

        std::error_code ec;
        int retval = std::filesystem::remove(name1, ec);
        if ( ! ec ) { // Success
          std::cout<<"succesfully: \n";
          if ( retval ) {
            std::cout<<"file existed and removed\n";  
          }
          else {
            std::cout<<"file didn't exist\n";
          }
        } 
        else {        // No error
          std::cout<<"unsuccesfully: "<<ec.value()<<" "<<ec.message()<<"\n";
        }
    

    加法

    pclose()的位置改变结果的原因是popen()打开的流被缓冲了。

    当std::filesystem::remove()被调用时,fwrite()写的命令由于缓冲的原因还没有被gnuplot接收。因此,在这一步中,还没有创建文件“/tmp/1.png”。

    然后,当调用pclose() 时,gnuplot 接收到命令,文件“/tmp/1.png”由gnuplot 创建。您查看的文件是调用std::filesystem::remove() 后创建的文件。

    您可以使用函数fflush() 显式刷新缓冲区。但是,即使您使用fflush(),由于popen 的异步特性,仍有可能在gnuplot 命令完成之前调用std::filesystem::remove()。

    为确保 gnuplot 进程完成后文件被擦除, 您将需要 gnuplot 和 c++ 程序可以同步的实现(或包装库)。

    【讨论】:

    • 我想我理解正确。根据docs:“返回值:true如果文件被删除,false如果文件不存在。采用error_code&amp;参数的重载在错误时返回false。 "我使用重载变体,它返回false,并且文件肯定存在。因此,error_code&amp; 必须设置,但没有设置。如果函数认为没有这样的文件,那么我不明白为什么。另外,为什么pclose(gp); 行的位置很重要?
    • pclose()的位置改变结果的原因是popen()打开的流被缓冲了。我在答案中添加了解释。
    • "因此,必须设置 error_code& ..."。我不这么认为,因为规范说如果没有要删除的文件则什么都不做。
    • 非常感谢您的解释!如果文件是在pclose 之前创建的,我会在星期一检查它,使用断点和/或std::cin。
    • 你是对的:在fwrite 行之后添加fflush(gp); std::cin.get(); 可以解决问题。如果没有std::cin.get();,就会出现赛车情况,结果因跑步而异(std::filesystem::remove() 几乎总是“失败”)。再次感谢您的详细解释!
    猜你喜欢
    • 2018-02-14
    • 2020-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-23
    • 2019-09-10
    • 1970-01-01
    • 2020-09-27
    相关资源
    最近更新 更多