我无法找到如何使用 popen()从Linux中的子程序中获取stdout到主C ++程序。我环顾四周,发现这段代码完成了我想做的事情。但我无法理解这些东西是如何运作的。我知道c ++编程的基础知识(我现在已经做了好几个月了)但是我很难过,所以有人可以帮我解释一下吗?
提前谢谢。
#include <vector>
#include <string>
#include <stdio.h>
#include <iostream>
using namespace std;
void my_popen( const string& cmd ,vector<string>& out ){
FILE* fp;
const int sizebuf=1234;
char buff[sizebuf];
out = vector<string> ();
if ((fp = popen (cmd.c_str (), "r"))== NULL){
}
string cur_string = "";
while (fgets(buff, sizeof (buff), fp)) {
cur_string += buff;
}
out.push_back (cur_string.substr (0, cur_string.size() -1));
pclose(fp);
}
int main () {
vector<string> output;
my_popen("which driftnet", output);
for (vector<string>::iterator itr = output.begin();
itr != output.end();
++itr) {
cout << *itr << endl;
}
return 0;
}
答案 0 :(得分:2)
如何使用popen()从Linux中的子程序中获取stdout到 主要的C ++程序
您熟悉UNIX理念吗?您可能会阅读维基百科文章的开头&#34; UNIX Philosophy&#34;。这是一个片段:
来自Bell的Doug McIlroy [1]记录了UNIX哲学 1978年系统技术期刊:[2]
1)让每个程序做好一件事。做一份新工作,重新建立 而不是通过添加新的&#34;功能和#34;使旧程序复杂化。
2)期望每个程序的输出成为另一个程序的输入,如 尚未知,程序。不要把输出弄得乱七八糟 信息。避免严格的柱状或二进制输入格式。唐&#39;吨 坚持互动投入。
......还有更多......
我更喜欢使用popen来访问我认为有用的许多Linux工具。示例包括sort,unique和sha256sum以及其他几个。但是您可能会注意到许多等价物正在进入C ++库(std :: sort等),并且经过努力,我发现了sha256sum的代码,适合在我的c ++应用程序中进行编译。
1)启动时,popen会被赋予一个字符串,它可以在一个部分中识别要运行的应用程序,或者在多个部分中,第一个是应用程序,而添加字符串是传递给该应用程序的参数,就好像命令行输入。就像在shell中一样(提示)
2)在&#39; r&#39;模式,sha256sum的stdout管道被定向到你调用popen打开的FILE *句柄。因此输出被反馈&#39;到您的计划。
但我无法理解这些东西是如何运作的。
不是真正有用的评论,而不是问题。你的代码片段直接向我看(虽然基本上是c代码)。也许你应该选择一行代码并询问一些特定的代码?
但是,在您对代码进行过多努力之前,让我先进行一些初步调查,我们将手动运行我们希望与popen一起使用的代码,并根据应用的输出捕获结果以供审核。
调查1 -
在你的代码片段中,你正在尝试命令,#34;哪个漂移网&#34;。当PATH上不存在程序漂移网时,这是一个不幸的选择。
我的系统上的结果:
$ which driftnet
$
因为该命令不存在,所以命令&#39;&#39;只返回一个空结果。
很难说我们是否做错了。没有漂网,我也无法帮助你。
调查2 - 一个更有趣的试验。
sha256sum需要一个输入文件...在这里,我提供了我的C ++ Hello World&#39;程序,你可以捕获到文件&#34; HelloWorld.cc&#34; (或任何你想要的地方),或使用你自己的任何文件。
// If your C++ 'Hello World' has no class ... why bother?
#include <iostream>
class Hello_t {
public:
Hello_t() { std::cout << "\n Hello" << std::flush; }
~Hello_t() { std::cout << "World!" << std::endl; }
void operator() () { std::cout << " C++ "; }
};
int main(int, char**) { Hello_t()(); }
总共10行代码,以注释开头,在单个include之后有1个空行。我的编辑器设置为自动神奇地删除尾随空格。如果您留下空间,您的结果可能会有所不同。
那么,当你计算文件的sha256sum时会发生什么&#34; HelloWorld.cc&#34;从命令行?无需推测......只是尝试一下!
在命令行上,调用该命令并在下一行中查看其响应:
$ sha256sum HelloWorld.cc
f91c6c288325fb4f72546482a9b4f1fa560f05071a12aa2a1268474ea3eeea3e HelloWorld.cc
也许您可以尝试使用sha256sum和已知文件你的 popen代码?
现在您可以看到(popen代码的)输出可能包含的内容。对于此调查,**命令sha256sum结果是1个长字符串,其中包含空格。
仅供参考 - 几个月前我在我的图书馆大修期间打破了我的C ++ popen代码。当我修复它时,我打算重新考虑你的努力,看看你是如何进步的。
如果您的代码开始工作......我建议您提交自己的答案!
如果您可以识别更具体的问题,请优化问题。
我建议你转向C ++代码...如果你要学习一些东西,并且你想学习C ++,那就不用研究你已经找到的东西。
我的代码再次运行。 (更新2017/8/2)
但是,首先让我分享UNIX哲学的一些后果,没有特别的顺序:
结果1 - 管道和过滤器
过滤器是做一件好事的程序。它们接受输入流,并产生输出流,通常是“删除”输出流。东西。
在代码指标领域,管理层可以查看行数以获得进度感(行数增长放缓)和问题(不会减慢)。
每个人都对代码指标有意见......不应该计算文件中的每一行。例如,我认为空白区域很重要,但不应将一行空白区域视为代码。同样,完整的评论不是代码。 IMHO。
在以下命令中
f1 < pfn | f2 | f3 | f4
f1..fn是过滤器,全部作为一个命令提交给shell。在我的代码指标工具中,我创建了
f1: ebl erase blank lines
f2: ecppc erase cpp comments
f3: ecc erase c comments
and several more.
因此
ebl < HelloWorld.cc | ecppc | ecc | ebl | wc
是一个单独的字符串提交给shell,最终cout的3个数字。
UNIX like命令&#39; wc&#39;可能存在于您的系统上。在Ubuntu 15.10上:
$ wc < HelloWorld.cc
10 52 309
表示HelloWorld.cc有10行(没有过滤)。
结果2 - 命令列表
命令列表是一个shell命令列表,每个命令都做得很好。该列表标识了执行的时间顺序。比管道和过滤器更普遍的想法。
例如,我已使用指定的密钥(F8)设置我的编辑器(emacs)以提供以下编译命令:
USER_FLAGS='-O0 ' ; export USER_FLAGS ; time make CC='g++-5 -m64 ' dumy514 ; ./dumy514
特定文件pfn可能只输入一次,但保存在emacs内的历史缓冲区中,因此无需重新输入。
注意3个分号。它们在提交给Linux命令shell的一个字符串中分隔多个命令。 3元素命令列表:设置环境变量以覆盖默认选项;导出新标志以使其可供编译器使用;并发布一个品牌。
所以? popen()接受命令列表。 A&#39;命令列表&#39;像shell可能会接受。
我终于读到了&#34; man popen&#34;反应得非常谨慎。
来自说明:
popen()函数通过创建管道,分叉和来打开一个过程 调用 shell 。
你给popen的命令,在shell中运行。
但我无法理解这些东西是如何运作的。
这个&#39;东西&#39;现在应该开始有意义了。我们正在访问shell来处理命令/命令列表/管道过滤器。这启动了其他&#39;进程,并使用PIPE与第一个进程交互。
那么shell中处理的命令输出如何传递给我的代码?理解后续的一些线索。包含一些单元测试演示,您可以轻松更改popen cmd,然后编译并运行新的cmd。
以下提供了在两种模式之一中使用popen的代码。是的他们是3级,基础和两个派生。希望这允许您一次运行多个这样的运行。 (在我的2核机器上并不是非常有用。)随意重构代码,保持你需要的东西。其余的将在这里继续为您提供指导。
#include <iostream>
#include <sstream>
#include <string>
#include <cstring> // sterror
#include <cstdio> // popen, FILE, fgets, fputs
#include <cassert>
class POpen_t // access to ::popen
{
protected:
FILE* m_FILE;
std::string m_cmd;
public:
POpen_t(void) : m_FILE(nullptr)
{ }
virtual ~POpen_t(void) {
if (m_FILE) (void)close();
m_FILE = 0;
}
// on success: 0 == return.size(), else returns error msg
std::string close()
{
std::stringstream errSS;
do // poor man's try block
{
// pclose() returns the term status of the shell cmd
// otherwise -1 and sets errno.
assert(nullptr != m_FILE); // tbr - some sort of logic error
// tbr if(0 == m_FILE) break; // success?
int32_t pcloseStat = ::pclose(m_FILE);
int myErrno = errno;
if (0 != pcloseStat)
{
errSS << "\n POpen_t::close() errno " << myErrno
<< " " << std::strerror(myErrno) << std::endl;
break;
}
m_FILE = 0;
}while(0);
return(errSS.str());
} // std::string close(void)
}; // class POpen_t
我认为将复制/粘贴分成3个部分会更容易。但请记住,我将所有代码都放在一个文件中。不是单个头文件,但它适用于我。如果愿意,您可以重构为.h和.cc文件。
class POpenRead_t : public POpen_t // access to ::popen read-mode
{
public:
POpenRead_t(void) { }
// ::popen(aCmd): opens a process (fork), invokes shell,
// and creates a pipe
// returns NULL if the fork or pipe calls fail,
// or if it cannot allocate memory.
// on success: 0 == return.size(), else returns error msg
std::string open (std::string aCmd)
{
std::stringstream errSS; // 0 == errSS.str().size() is success
assert (aCmd.size() > 0);
assert (0 == m_FILE); // can only use serially
m_cmd = aCmd; // capture
do // poor man's try block
{
if(true) // diagnosis only
std::cout << "\n POpenRead_t::open(cmd): cmd: '"
<< m_cmd << "'\n" << std::endl;
// ::popen(aCmd): opens a process by creating a pipe, forking,
// and invoking the shell.
// returns NULL if the fork or pipe calls fail,
// or if it cannot allocate memory.
m_FILE = ::popen (m_cmd.c_str(), "r"); // create 'c-stream' (FILE*)
// ^^ function is not in namespace std::
int myErrno = errno;
if(0 == m_FILE)
{
errSS << "\n POpenRead_t::open(" << m_cmd
<< ") popen() errno " << myErrno
<< " " << std::strerror(myErrno) << std::endl;
break;
}
} while(0);
return (errSS.str());
} // std::string POpenRead_t::open(std::string aCmd)
// success when 0 == errStr.size()
// all outputs (of each command) captured into captureSS
std::string spinCaptureAll(std::stringstream& captureSS)
{
const int BUFF_SIZE = 2*1024;
std::stringstream errSS; // capture or error
do
{
if(0 == m_FILE)
{
errSS << "\n ERR: POpenRead_t::spinCaptureAll(captureSS) - m_FILE closed";
break;
}
size_t discardedBlankLineCount = 0;
do
{
// allocate working buff in auto var, fill with nulls
char buff[BUFF_SIZE] = { 0 };
if(true) { for (int i=0; i<BUFF_SIZE; ++i) assert(0 == buff[i]); }
// char * fgets ( char * str, int num, FILE * c-stream );
// Reads characters from c-stream and stores them as a C string
// into buff until
// a) (num-1) characters have been read
// b) a newline or
// c) the end-of-file is reached
// whichever happens first.
// A newline character makes fgets stop reading, but it is considered
// a valid character by the function and included in the string copied
// to str
// A terminating null character is automatically appended after the
// characters copied to str.
// Notice that fgets is quite different from gets: not only fgets
// accepts a c-stream argument, but also allows to specify the maximum
// size of str and includes in the string any ending newline character.
// fgets() returns buff or null when feof()
char* stat = std::fgets(buff, // char*
BUFF_SIZE, // count - 1024
m_FILE); // c-stream
assert((stat == buff) || (stat == 0));
int myErrno = errno; // capture
if( feof(m_FILE) ) { // c-stream eof detected
break;
}
// when stat is null (and ! feof(m_FILE) ),
// even a blank line contains "any ending newline char"
// TBD:
// if (0 == stat) {
// errSS << "0 == fgets(buff, BUFF_SIZE_1024, m_FILE) " << myErrno
// << " " << std::strerror(myErrno) << std::endl;
// break;
// }
if(ferror(m_FILE)) { // file problem
errSS << "Err: fgets() with ferror: " << std::strerror(myErrno);
break;
}
if(strlen(buff)) captureSS << buff; // additional output
else discardedBlankLineCount += 1;
}while(1);
if(discardedBlankLineCount)
captureSS << "\n" << "discarded blank lines: " << discardedBlankLineCount << std::endl;
} while(0);
return (errSS.str());
} // std::string POpenRead_t::spinCaptureAll(std::stringstream& ss)
}; // class POpenRead_t
现在POpenWrite_t:
class POpenWrite_t : public POpen_t // access to ::popen
{
public:
POpenWrite_t(void) { }
// ::popen(aCmd): opens a process (fork), invokes the non-interactive shell,
// and creates a pipe
// returns NULL if the fork or pipe calls fail,
// or if it cannot allocate memory.
// on success: 0 == return.size(), else returns error msg
std::string open (std::string aCmd)
{
std::stringstream errSS; // 0 == errSS.str().size() is success
assert (aCmd.size() > 0);
assert (0 == m_FILE);
m_cmd = aCmd; // capture
do // poor man's try block
{
if(true) // diagnosis only
std::cout << "\n POpenWrite_t::open(cmd): cmd: \n '"
<< "'" << m_cmd << std::endl;
m_FILE = ::popen (m_cmd.c_str(), "w"); // use m_FILE to write to sub-task std::in
int myErrno = errno;
if(0 == m_FILE)
{
errSS << "\n POpenWrite_t::open(" << m_cmd
<< ") popen() errno " << myErrno
<< " " << std::strerror(myErrno) << std::endl;
break;
}
} while(0);
return (errSS.str());
} // std::string POpenWrite_t::open(std::string aCmd)
// TBR - POpenWrite_t::write(const std::string& s)
// work in progress - see demo write mode
}; // class POpenWrite_t
接下来,我得到了我称之为T514_t的内容,这是一个定义我的单元测试或演示工作的类。条目在&#39; exec()&#39;。
事实证明,popen中使用的shell可能与您在终端中遇到的shell不同。在会话启动期间,shell的一部分决定它是处于交互式(终端)还是非交互式(无终端)模式。在我的系统上,终端/交互式shell是&#39; bash&#39;并且popen /非交互式shell是&#39; sh&#39;。在为popen开发shell命令时请记住这一点。
POpenRead_t的有趣用法在方法&#34; std :: string shellCheck(std :: stringstream&amp; rsltSS)&#34;中。
class T514_t
{
const std::string line = "\n------------------------------------------------------";
const char escape = 27;
public:
T514_t() = default;
~T514_t() = default;
std::string exec (int argc, char* argv[],
std::stringstream& resultSS )
{
std::string errStr;
errStr = shellCheck(resultSS); // when not bash, make a choice
if (0 == errStr.size()) // shell is expected
{
// bash reported, continue
switch (argc)
{
case 2 : { errStr = exec (argv, resultSS); } break;
default: { errStr = " User error"; usage(argv); } break;
} // switch (argc)
}
return (errStr); // when no err, errStr.size() is 0
} // int exec()
private:
std::string exec(char* argv[], std::stringstream& resultSS)
{
std::string errStr;
std::cout << clrscr() << std::flush;
switch (std::atoi(argv[1]))
{
case 1:
{
std::cout << clrscr() << boldOff() << line;
errStr = demoReadMode(resultSS);
std::cout << boldOff() << std::endl;
} break;
case 2:
{
std::cout << clrscr() << boldOn() << line;
errStr = demoWriteMode();
std::cout << boldOff() << std::endl;
} break;
default: { usage(argv); } break;
} // switch (std::atoi(argv[1]))
return(errStr);
} // int exec(char* argv[], std::stringstream& resultSS)
// Ubuntu has 5 (or more) shells available
// 1) Bourne shell (' sh'), 2) C shell (' csh'), 3) TC shell (' tcsh'),
// 4) Korn shell (' ksh'), 5) Bourne Again shell (' bash')
//
// bash is my interactive shell
// sh is my non-interactive shell
// which (mildly) influences the cmds of the demo's
//
// when not bash, what do you want to do?
std::string shellCheck(std::stringstream& rsltSS)
{
std::stringstream errValSS;
std::string cmd;
cmd += "ps -p \"$$\" ";
{
POpenRead_t popenR;
errno = 0;
std::string rStat = popenR.open(cmd);
int myErrno = errno;
if(0 != rStat.size()) {
errValSS << "\n Err: " << cmd << " failed. rStat: "
<< std::strerror(myErrno) << std::endl;
return(errValSS.str());
}
do
{
errno = 0;
std::string errSS = popenR.spinCaptureAll (rsltSS);
myErrno = errno;
if (false) { // dianosis only
std::cout << "\n demoReadMode() ss/myErrno/s:\n"
<< rsltSS.str() << " "
<< myErrno << " '"
<< errSS << "'" << std::endl;
}
break;
if(0 != myErrno)
{
errValSS << "\n Err: popenR.spinCaputureAll(): cmd / strerror(myErrno): "
<< cmd << " / " << std::strerror(myErrno) << std::endl;
return(errValSS.str());
}
if (0 == rsltSS.str().size()) { // TBR
std::cout << "\n demoReadMode: ss.str().size() is 0, indicating completed" << std::endl;
} break;
}while(1);
// NOTE: pclose() returns the termination status of the shell command
// otherwise -1 and sets errno.
// errno = 0;
(void)popenR.close(); // return value is term status of the shell command
if(errno != 0)
{
errValSS << "\n Err: POpen_t::close() - cmd: " << cmd
<< " err:" << std::strerror(errno) << std::endl;
}
}
std::string s = rsltSS.str();
std::string nishell (" sh"); // non-interactive shell expected is: " sh"
size_t indx = s.find(nishell); // my interactive shell is bash
// clear
rsltSS.str(std::string()); rsltSS.clear(); // too much info
if (std::string::npos != indx)
{
// normally I would not include a 'success' message (not the 'UNIX pholosopy'),
// but here I have added to the (success) results:
if(true) //<-- feel free to comment out or delete this success action
rsltSS << "\n the reported non-interactive shell is the required '"
<< nishell << "' ... continuing.\n" << std::endl;
}
else
{
// TBR - when non-interactive shell is unexpectedly different that nishell,
// this demo code aborts ... (with no results) and the error msg:
errValSS << "\n the reported non-interactive shell is not " << nishell
<< " \n" << s << "\n ... aborting\n";
// alternative actions are _always_ possible
// untested examples:
// the shell can be temporarily changed as part of cmd (untested)
// the user could change the non-interactive shell
// your use of popen (POpen_t classes above)
// can change 'cmd's based on the discovered shell
}
return(errValSS.str());
} // std::string shellCheck(std::stringstream& rsltSS)
std::string demoReadMode(std::stringstream& rsltSS)
{
std::stringstream errValSS;
std::string cmd;
int i = 1;
cmd += "./HelloWorld ; ";
cmd += "echo " + std::to_string(i++) + " ; ";
cmd += "./HelloWorld ; ";
cmd += "sha256sum HelloWorld.cc ; ";
cmd += "echo " + std::to_string(i++) + " ; ";
cmd += "./HelloWorld ; ";
cmd += "echo TEST WORKS ; ";
cmd += "./HelloWorld";
{
POpenRead_t popenR;
errno = 0;
std::string rStat = popenR.open(cmd);
int myErrno = errno;
if(0 != rStat.size()) {
errValSS << "\n Err: " << cmd << " failed. rStat: "
<< std::strerror(myErrno) << std::endl;
return(errValSS.str());
}
do
{
errno = 0;
std::string errSS = popenR.spinCaptureAll (rsltSS);
myErrno = errno;
if (false) { // dianosis only
std::cout << "\n demoReadMode() ss/myErrno/s:\n"
<< rsltSS.str() << " "
<< myErrno << " '"
<< errSS << "'" << std::endl;
}
break;
if(0 != myErrno)
{
errValSS << "\n Err: popenR.spinCaputureAll(): cmd / strerror(myErrno): "
<< cmd << " / " << std::strerror(myErrno) << std::endl;
return(errValSS.str());
}
if (0 == rsltSS.str().size()) { // TBR
std::cout << "\n demoReadMode: ss.str().size() is 0, indicating completed" << std::endl;
} break;
}while(1);
// NOTE: pclose() returns the termination status of the shell command
// otherwise -1 and sets errno.
// errno = 0;
(void)popenR.close(); // return value is term status of the shell command
if(errno != 0)
{
errValSS << "\n Err: POpen_t::close() - cmd: " << cmd
<< " err:" << std::strerror(errno) << std::endl;
}
}
return(errValSS.str());
} // std::string demoReadMode(std::stringstream& rsltSS)
std::string demoWriteMode()
{
std::stringstream errValSS;
std::string cmd;
int i = 1;
cmd += "./HelloWorld ; ";
cmd += "echo " + std::to_string(i++) + " ; ";
cmd += "./HelloWorld ; ";
cmd += "sha256sum HelloWorld.cc ; ";
cmd += "echo " + std::to_string(i++) + " ; ";
cmd += "./HelloWorld ; ";
{
POpenWrite_t popenW; // popen in write mode
// errno = 0;
std::string wStat = popenW.open(cmd);
int myErrno = errno;
if (0 != wStat.size()) {
errValSS << "\n Err: " << cmd << "\n failed. wStat: "
<< std::strerror(myErrno) << std::endl;
return(errValSS.str());
}
// tbd - Work in Progress - what command to receive what data from here
// login - needs root, tbr - can cmd contain sudo? probably
//
//
// NOTE: pclose() returns the termination status of the shell command
// otherwise -1 and sets errno.
// errno = 0;
(void)popenW.close(); // return value is term status of the shell command
if(errno != 0)
{
errValSS << "\n Err: POpen_t::close() - cmd: " << cmd
<< " err:" << std::strerror(errno) << std::endl;
}
}
return(errValSS.str());
} // std::string demoWriteMode()
void usage(char* argv[])
{
std::cout << " executable: "<< argv[0]
<< "\n USAGE - user must select test mode"
<< "\n test"
<< "\n 1 - demoReadMode"
<< "\n 2 - demoWriteMode"
<< std::endl;
}
std::string boldOff(){std::string rV; rV.push_back(escape); rV += "[21m"; return(rV); } // bold off
std::string boldOn() {std::string rV; rV.push_back(escape); rV += "[1m"; return(rV); } // bold on
inline std::string clrscr(void) {
std::stringstream ss;
ss << static_cast<char>(escape) << "[H" // home
<< static_cast<char>(escape) << "[2J"; // clrbos
return(ss.str());
}
}; // class T514_t
最后,我的主要......我在这里只做了几件事。
int main(int argc, char* argv[])
{
std::string errStr;
std::stringstream resultSS;
{
T514_t t514;
errStr = t514.exec(argc, argv, resultSS);
}
// display result
if (resultSS.str().size())
std::cout << "\n\n "<< resultSS.str() << std::endl;
if(errStr.size())
std::cerr << errStr << std::endl;
return(static_cast<int>(errStr.size()));
}
所以,构建并使用std :: string cmd;在你感兴趣的模式中。祝你好运。
欢迎提出问题。