C popen() 改变字符串
C popen() changes the string
我在 Ubuntu 机器 (16.04 LTS) 中使用 popen()
到 运行 一个 shell 命令,出于某种原因,popen
发生了变化命令字符串然后 运行s 更改的命令字符串,导致错误,因为没有这样的命令(它添加 /021/001
到字符串的末尾)。
我 运行 在具有不同 Ubuntu 版本(14.04 LTS)和 popen
的等效机器上使用相同的代码不会更改命令字符串并且 运行完美。
知道为什么会这样吗?
我的代码是 运行ning:
char* runShellReadCmd(char* command) {
FILE *fp;
int outputCurrentSize = 0, outputArrSize = READ_INITIAL_SIZE;
char readChunk[READ_CHUNK], *tmpOutputStrArr = NULL, *data = NULL;
fp = popen(command, "r");
data = (char*)calloc(READ_INITIAL_SIZE, sizeof(char));
while (fgets(readChunk, READ_CHUNK, fp) != NULL)
{
// If string size equals to the array size, need to expand the array.
if (outputCurrentSize >= outputArrSize) {
outputArrSize *= 2;
// Re allocate array, save it in a temporary array.
tmpOutputStrArr = realloc(data, outputArrSize * sizeof(char));
// If re-allocationg failed, stop and return NULL to indicate that a problem has occured.
if (!tmpOutputStrArr) {
free(tmpOutputStrArr);
free(data);
return NULL;
}
data = tmpOutputStrArr;
}
// Concatenate the recently read 100 chars to the data arr.
strcat(data, readChunk);
// Save current string length.
outputCurrentSize += READ_CHUNK;
}
pclose(fp);
return data;
}
命令:
"/sbin/iwlist wlp2s0 scan"
funcs.c:
// Runs a shell command using given command string and returns the output.
char* runShellReadCmd(char* command) {
FILE *fp;
int outputCurrentSize = 0, outputArrSize = READ_INITIAL_SIZE, elementsRead = 0;
char readChunk[READ_CHUNK], *tmpOutputStrArr = NULL, *data = NULL;
printf("%s", command);
fflush(stdout);
fp = popen(command, "r");
data = (char*)calloc(READ_INITIAL_SIZE, sizeof(char));
// while (fgets(readChunk, READ_CHUNK, fp) != NULL)
while (elementsRead = fread(readChunk, sizeof(char), READ_CHUNK, fp) != READ_CHUNK)
{
// If string size equals to the array size, need to expand the array.
if (outputCurrentSize >= outputArrSize) {
outputArrSize *= 2;
// Re allocate array, save it in a temporary array.
tmpOutputStrArr = realloc(data, outputArrSize * sizeof(char));
// If re-allocationg failed, stop and return NULL to indicate that a problem has occured.
if (!tmpOutputStrArr) {
free(tmpOutputStrArr);
free(data);
return NULL;
}
data = tmpOutputStrArr;
}
// Concatenate the recently read 100 chars to the data arr.
strcat(data, readChunk);
// Save current string length.
outputCurrentSize += READ_CHUNK;
}
pclose(fp);
return data;
}
// Returns the number of times the given substring appears in the given string.
int getNumOfMatches(char* string, char* substring) {
int count = 0;
char* temp = string;
while ((temp = strstr(temp, substring))) {
count++;
temp++;
}
return count;
}
// Returns the first wlan device name thats available in the system.
char* getWlanDeviceName() {
int size = 0, i;
char* device, *tmpPointer, *deviceNameStartPointer;
char* str = runShellReadCmd("/sbin/ifconfig");
deviceNameStartPointer = strstr(str, WIFI_DEVICE_PREFIX);
tmpPointer = deviceNameStartPointer;
while (*tmpPointer != ' ') {
size++;
tmpPointer++;
}
device = (char*)calloc(size, sizeof(char));
tmpPointer = deviceNameStartPointer;
for(i = 0; i < size; i++) {
device[i] = *tmpPointer;
tmpPointer++;
}
free(str);
return device;
}
test_funcs.c:
// Tests whether the wifi device can find wifi networks (at least 1).
int testWifi() {
int testOk = 0, length;
char* command, *output, *device = getWlanDeviceName();
length = strlen("/sbin/iwlist ") + strlen(device) + strlen(" scan");
command = (char*) calloc(length, sizeof(char));
strncpy(command, "/sbin/iwlist ", strlen("/sbin/iwlist "));
//strcat(command, "/sbin/iwlist ");
strcat(command, device);
strcat(command, " scan");
output = runShellReadCmd(command);
printf("%s\n", output);
testOk = getNumOfMatches(output, WIFI_NETWORK_START_STR) > 0;
//printf("%d", getNumOfMatches(WIFI_NETWORK_START_STR, output));
free(device);
return testOk;
}
length = strlen("/sbin/iwlist ") + strlen(device) + strlen(" scan");
command = (char*) calloc(length, sizeof(char));
正如所怀疑的那样,您的 command
缓冲区太小(length++
会有所改进)。您缺少字符串终止符。因此,当在 popen
循环和命令 "leaks" 中分配更多内存时,字符串终止在某个时候被覆盖,因为 null char char 不在它应该在的位置了。
它能在其他机器上运行的事实纯属运气。内存分配不同。但是您的代码仍然不正确。这就是为什么它被称为 未定义的行为。
注意:您可以使用 getc()
代替 fgets()
来避免所有这些字符串处理。你所有的块大小都将变成一个,你不再需要 strlen()
:
char* runShellReadCmd(char* command) {
FILE *fp;
size_t size, used;
char *data = NULL;
int ch;
fp = popen(command, "r");
if (!fp) return NULL;
for(size=used=0; ;) {
if (used >= size) {
size = size ? 2*size : 100;
data = realloc(data, size); // TODO : check return
}
ch = getc(fp) ;
if (ch == EOF) break;
data[used++] = ch;
}
data[used] = 0;
pclose(fp);
// maybe a final resize here:
// data = realloc(data, used+1);
return data;
}
我在 Ubuntu 机器 (16.04 LTS) 中使用 popen()
到 运行 一个 shell 命令,出于某种原因,popen
发生了变化命令字符串然后 运行s 更改的命令字符串,导致错误,因为没有这样的命令(它添加 /021/001
到字符串的末尾)。
我 运行 在具有不同 Ubuntu 版本(14.04 LTS)和 popen
的等效机器上使用相同的代码不会更改命令字符串并且 运行完美。
知道为什么会这样吗?
我的代码是 运行ning:
char* runShellReadCmd(char* command) {
FILE *fp;
int outputCurrentSize = 0, outputArrSize = READ_INITIAL_SIZE;
char readChunk[READ_CHUNK], *tmpOutputStrArr = NULL, *data = NULL;
fp = popen(command, "r");
data = (char*)calloc(READ_INITIAL_SIZE, sizeof(char));
while (fgets(readChunk, READ_CHUNK, fp) != NULL)
{
// If string size equals to the array size, need to expand the array.
if (outputCurrentSize >= outputArrSize) {
outputArrSize *= 2;
// Re allocate array, save it in a temporary array.
tmpOutputStrArr = realloc(data, outputArrSize * sizeof(char));
// If re-allocationg failed, stop and return NULL to indicate that a problem has occured.
if (!tmpOutputStrArr) {
free(tmpOutputStrArr);
free(data);
return NULL;
}
data = tmpOutputStrArr;
}
// Concatenate the recently read 100 chars to the data arr.
strcat(data, readChunk);
// Save current string length.
outputCurrentSize += READ_CHUNK;
}
pclose(fp);
return data;
}
命令:
"/sbin/iwlist wlp2s0 scan"
funcs.c:
// Runs a shell command using given command string and returns the output.
char* runShellReadCmd(char* command) {
FILE *fp;
int outputCurrentSize = 0, outputArrSize = READ_INITIAL_SIZE, elementsRead = 0;
char readChunk[READ_CHUNK], *tmpOutputStrArr = NULL, *data = NULL;
printf("%s", command);
fflush(stdout);
fp = popen(command, "r");
data = (char*)calloc(READ_INITIAL_SIZE, sizeof(char));
// while (fgets(readChunk, READ_CHUNK, fp) != NULL)
while (elementsRead = fread(readChunk, sizeof(char), READ_CHUNK, fp) != READ_CHUNK)
{
// If string size equals to the array size, need to expand the array.
if (outputCurrentSize >= outputArrSize) {
outputArrSize *= 2;
// Re allocate array, save it in a temporary array.
tmpOutputStrArr = realloc(data, outputArrSize * sizeof(char));
// If re-allocationg failed, stop and return NULL to indicate that a problem has occured.
if (!tmpOutputStrArr) {
free(tmpOutputStrArr);
free(data);
return NULL;
}
data = tmpOutputStrArr;
}
// Concatenate the recently read 100 chars to the data arr.
strcat(data, readChunk);
// Save current string length.
outputCurrentSize += READ_CHUNK;
}
pclose(fp);
return data;
}
// Returns the number of times the given substring appears in the given string.
int getNumOfMatches(char* string, char* substring) {
int count = 0;
char* temp = string;
while ((temp = strstr(temp, substring))) {
count++;
temp++;
}
return count;
}
// Returns the first wlan device name thats available in the system.
char* getWlanDeviceName() {
int size = 0, i;
char* device, *tmpPointer, *deviceNameStartPointer;
char* str = runShellReadCmd("/sbin/ifconfig");
deviceNameStartPointer = strstr(str, WIFI_DEVICE_PREFIX);
tmpPointer = deviceNameStartPointer;
while (*tmpPointer != ' ') {
size++;
tmpPointer++;
}
device = (char*)calloc(size, sizeof(char));
tmpPointer = deviceNameStartPointer;
for(i = 0; i < size; i++) {
device[i] = *tmpPointer;
tmpPointer++;
}
free(str);
return device;
}
test_funcs.c:
// Tests whether the wifi device can find wifi networks (at least 1).
int testWifi() {
int testOk = 0, length;
char* command, *output, *device = getWlanDeviceName();
length = strlen("/sbin/iwlist ") + strlen(device) + strlen(" scan");
command = (char*) calloc(length, sizeof(char));
strncpy(command, "/sbin/iwlist ", strlen("/sbin/iwlist "));
//strcat(command, "/sbin/iwlist ");
strcat(command, device);
strcat(command, " scan");
output = runShellReadCmd(command);
printf("%s\n", output);
testOk = getNumOfMatches(output, WIFI_NETWORK_START_STR) > 0;
//printf("%d", getNumOfMatches(WIFI_NETWORK_START_STR, output));
free(device);
return testOk;
}
length = strlen("/sbin/iwlist ") + strlen(device) + strlen(" scan");
command = (char*) calloc(length, sizeof(char));
正如所怀疑的那样,您的 command
缓冲区太小(length++
会有所改进)。您缺少字符串终止符。因此,当在 popen
循环和命令 "leaks" 中分配更多内存时,字符串终止在某个时候被覆盖,因为 null char char 不在它应该在的位置了。
它能在其他机器上运行的事实纯属运气。内存分配不同。但是您的代码仍然不正确。这就是为什么它被称为 未定义的行为。
注意:您可以使用 getc()
代替 fgets()
来避免所有这些字符串处理。你所有的块大小都将变成一个,你不再需要 strlen()
:
char* runShellReadCmd(char* command) {
FILE *fp;
size_t size, used;
char *data = NULL;
int ch;
fp = popen(command, "r");
if (!fp) return NULL;
for(size=used=0; ;) {
if (used >= size) {
size = size ? 2*size : 100;
data = realloc(data, size); // TODO : check return
}
ch = getc(fp) ;
if (ch == EOF) break;
data[used++] = ch;
}
data[used] = 0;
pclose(fp);
// maybe a final resize here:
// data = realloc(data, used+1);
return data;
}