【发布时间】:2010-09-22 05:30:02
【问题描述】:
我正在编写一个简单的程序来浏览本地网络并使用“系统”将文件名传递给 mplayer。但是,有时文件名包含空格或引号。 显然我可以编写自己的函数来转义这些,但我不确定哪些字符需要转义或不需要转义。
CRT 或 linux 头文件中是否有可用的函数来安全地转义字符串以传递到命令行?
【问题讨论】:
我正在编写一个简单的程序来浏览本地网络并使用“系统”将文件名传递给 mplayer。但是,有时文件名包含空格或引号。 显然我可以编写自己的函数来转义这些,但我不确定哪些字符需要转义或不需要转义。
CRT 或 linux 头文件中是否有可用的函数来安全地转义字符串以传递到命令行?
【问题讨论】:
其他答案包括这个 fork 和 exec 解决方案,但我声称这是唯一正确的方法。
转义 shell 参数容易出现错误和浪费时间,就像在存在更安全和更有效的参数绑定 API 时试图转义 SQL 参数是一个愚蠢的想法一样。
这是一个示例函数:
void play(const char *path)
{
/* Fork, then exec */
pid = fork();
if( pid < 0 ) {
/* This is an error! */
return;
}
if( pid == 0 ) {
/* This is the child */
freopen( "/dev/null", "r", stdin );
freopen( "/dev/null", "w", stdout );
freopen( "/dev/null", "w", stderr );
execlp( "mplayer", "mplayer", path, (char *)0 );
/* This is also an error! */
return;
}
}
【讨论】:
没有一个单一的解决方案适用于任何地方,因为不同的 shell 对于特殊字符是什么以及如何解释它们有不同的想法。对于 bash,在将文件名中的每个单引号替换为 '"'"' 之后,您可能可以将整个文件名用单引号括起来(第一个单引号停止序列,"'" 将文字单引号附加到字符串,最后的单引号再次开始引用的序列)。更好的解决方案是找到一种在不使用系统的情况下调用程序的方法,例如将 fork 与其中一个 exec 函数一起使用,这样就没有 shell 插值。
【讨论】:
虽然我不知道执行此操作的函数,但您可以用'...' 包围您的每个参数,并将原始参数中的任何' 替换为'"'"'。喜欢
system("mplayer 'foo'\"'\"' bar'"); 将为 mplayer 提供一个参数,即 foo'bar 并且允许包含 " 或 \n 之类的奇怪内容。注意上面" (\") 之前的转义只是为了使其有效C++。
您应该考虑使用单独接受参数的函数,从而避免此类问题。 Wikipedia 上有一篇关于著名的 fork-and-exec 模式的好文章。 http://en.wikipedia.org/wiki/Fork-exec
【讨论】:
现在这里是shell逃逸问题的完整解决方案。虽然这 没有回答为 shell 转义字符串的确切问题。它解决了将参数传递给程序的问题。此解决方案是一种 POSIX 可移植方式,可以使用正确传递给命令的参数来执行命令,而无需担心需要转义它们。
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
std::vector<std::string> split(std::string delimiter, std::string str){
std::size_t nextPos = 0;
std::size_t delimiterSize = delimiter.size();
std::vector<std::string> list;
while(true){
std::size_t pos = str.find(delimiter, nextPos);
std::string subStr;
if(pos == std::string::npos){
list.push_back(str.substr(nextPos));
break;
}
subStr = str.substr(nextPos, pos - nextPos);
list.push_back(subStr);
nextPos = pos + delimiterSize;
}
return list;
}
bool isFileExecutable(const std::string &file)
{
struct stat st;
if (stat(file.c_str(), &st) < 0)
return false;
if ((st.st_mode & S_IEXEC) != 0)
return true;
return false;
}
std::string ensureEndsWithSlash(std::string path){
if(path[path.length()-1] != '/'){
path += "/";
}
return path;
}
std::string findProgram(std::string name){
// check if it's relative
if(name.size() > 2){
if(name[0] == '.' && name[1] == '/'){
if(isFileExecutable(name)){
return name;
}
return std::string();
}
}
std::vector<std::string> pathEnv = split(":", getenv("PATH"));
for(std::string path : pathEnv){
path = ensureEndsWithSlash(path);
path += name;
if(isFileExecutable(path)){
return path;
}
}
return std::string();
}
// terminal condition
void toVector(std::vector<std::string> &vector, const std::string &str){
vector.push_back(str);
}
template<typename ...Args>
void toVector(std::vector<std::string> &vector, const std::string &str, Args ...args){
vector.push_back(str);
toVector(vector, args...);
}
int waitForProcess(pid_t processId){
if(processId == 0){
return 0;
}
int status = 0;
int exitCode = -1;
while(waitpid(processId, &status, 0) != processId){
// wait for it
}
if (WIFEXITED(status)) {
exitCode = WEXITSTATUS(status);
}
return exitCode;
}
/**
Runs the process and returns the exit code.
You should change it so you can detect process failure
vs this function actually failing as a process can return -1 too
@return -1 on failure, or exit code of process.
*/
template<typename ...Args>
int mySystem(Args ...args){
std::vector<std::string> command;
toVector(command, args...);
command[0] = findProgram(command[0]);
if(command[0].empty()){
// handle this case by returning error or something
// maybe std::abort() with error message
return -1;
}
pid_t pid = fork();
if(pid) {
// parent wait for child
return waitForProcess(pid);
}
// we are child make a C friendly array
// this process will be replaced so we don't care about memory
// leaks at this point.
std::vector<char*> c_command;
for(int i = 0; i < command.size(); ++i){
c_command.push_back(strdup(command[i].c_str()));
}
// null terminate the sequence
c_command.push_back(nullptr);
execvp(c_command[0], &c_command[0]);
// just incase
std::abort();
return 0;
}
int main(int argc, char**argv){
// example usage
mySystem("echo", "hello", "world");
}
【讨论】: