将字符串写入文件会导致奇怪的字符

时间:2010-12-04 00:34:56

标签: c file-io

我的家庭作业是创建一个带有历史记录功能(完成)的shell(完成),然后将历史记录写入文件(这是我遇到问题的地方),这样就可以在下次运行时加载它。 / p>

我通常是一个Java人,因此我需要查找几乎每个c函数。无论如何,我有问题将我的字符串数组写入文件。出于某种原因,我依旧根据我使用的编码获得奇怪的符号。 例如,如果我运行程序并立即退出,则历史文件包含:

??_?

如果我运行ls然后退出,则历史文件包含:

ls
_?

我删除了无关的程序。提前感谢任何指针。这是脱毛的第3天。我的智慧结束了。

#include 
/*Had to use \ so that the includes would show here. */
#include \stdlib.h>
#include \signal.h>
#include \sys/types.h>
#include \unistd.h>
#include \string.h>
#include \errno.h>
/*
* Constant Declarations
*/
#define MAX_LINE 80
#define BUFFER_SIZE 50
#define HIST_SIZE 10

static char buffer[BUFFER_SIZE];
char history[HIST_SIZE][BUFFER_SIZE];
int count = 0;
int caught = 0;
char historyFileLoc[] = "./name.history";

void loadHistory() {
    int i;
    char histCommand[BUFFER_SIZE];

    i = 0;
    FILE *hisFile = fopen(historyFileLoc, "r");

    if( hisFile ) {
        /*If a user edits the history file, only the first ten entries will be loaded */
        while(!feof(hisFile)) {
            if(fscanf(hisFile, "%s\n", histCommand) == 1){
                strcpy(history[i], histCommand);
                i++;
                count++;
            }
        }
    }

    if(hisFile != NULL){
        if(fclose(hisFile) != 0) {
            perror("History file (r) was not closed correctly");
        }
    }
}

void saveHistory() {
    int i;
    char buffer[MAX_LINE];

    FILE *hisFile = fopen(historyFileLoc, "w");

    for(i=0; i < HIST_SIZE; i++){
        if(history[i] != '\0') {
            strcpy(buffer, history[i]);
        /*fwrite(history[i], 1, strlen(history[i]), hisFile);*/
        /*  fputs(history[i], hisFile);*/
            if(buffer != NULL) {
                fprintf(hisFile,"%s\n", buffer);
            }
        }
    }
    if(fclose(hisFile) != 0) {
    perror("History file was not closed correctly");
    }
}


/*
* The main() function presents the prompt "sh>" and then invokes setup(), which waits for the
*  user to enter a command. The contents of the command entered by the user are loaded into the
*  args array. For example, if the user enters ls Ðl at the COMMAND-> prompt, args[0] will be set
*  to the string ls and args[1] will be set to the string Ðl. (By ÒstringÓ, we mean a
*  null-terminated, C-style string variable.)
*/
int main(void) {
    char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
    int background, status; /* equals 1 if a command is followed by '&' */
    char *args[MAX_LINE / 2 + 1]; /* command line arguments */
    pid_t pid; /* the process's id */
...
}

历史转储[] []:

./660_Lab04.c.out 

sh ->ls
660_Lab03_Tests.txt             Lab 03 Documentation.docx
660_Lab04.c                 buffer.h
660_Lab04.c.out                 name.history
660__Lab01.c                    main.c
660__Lab03.c                    main_not_mine.c
CSE_660Lab01Documentation.doc

This is where the dump begins minus sh->
sh ->ls
ls
_?
??_?

我的其余代码。 loadHistory和saveHistory在此之前:


void printHistory() {
    int i;
    int j = 0;
    int histcount = count;

    printf("\n");
    for (i=0; i < HIST_SIZE; i++) {
        printf("%d.   ",histcount); /* Used to print the correct hitory number */
        while (history[i][j] != '\n' && history[i][j] != '\0') {
            printf("%c",history[i][j]);
            j++;
        }
        printf("\n");
        j=0;

        histcount--;
        if(histcount == 0) {
            break;
        }
    }
    printf("\n");
    printf("sh -> ");
}

/* the signal handler function */
void handle_SIGINT() {
    write(STDOUT_FILENO,buffer,strlen(buffer));
    printHistory();
    caught = 1;
}

void setup(char inputBuffer[], char *args[], int *background) {
    int length, /* # of characters in the command line */
        i, /* loop index for accessing inputBuffer array */
        start, /* index where beginning of next command parameter is */
        ct, /* index of where to place the next parameter into args[] */
        k; /* Generic counter */

    ct = 0;

/* read what the user enters on the command line */
    length = read(STDIN_FILENO, inputBuffer, MAX_LINE);

    if(caught == 1) {
        length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
        caught = 0;
    }

/* checks to see if the command is a history retrieval command. If it isn't then add it to the history */
    if((strcmp(inputBuffer, "r\n\0") != 0) && (strncmp(inputBuffer, "r x", 2) != 0) ) {
        for(i= (HIST_SIZE - 1); i>0; i--) {
            strcpy(history[i], history[i-1]);
        }
        strcpy(history[0], inputBuffer);
        count++;
    }
    start = -1;
    if (length == 0) {
        saveHistory();
        exit(0); /* ^d was entered, end of user command stream */
    } else if ((length < 0) && (errno != EINTR)) {
        perror("error reading the command");
        saveHistory();
        exit(-1); /* terminate with error code of -1 */
    }

/* Checks to see if r was entered. If so, it copies the command most recently in the input buffer */
    if(strcmp(inputBuffer, "r\n\0") == 0) {
        strcpy(inputBuffer,history[0]);
        /* Checks to see if r x was entered. If so then it searches for the most recent command that begins with x */   
        } else if(strncmp(inputBuffer, "r x", 2) == 0) {
            for(k=0; k < 10; k++){
                if(inputBuffer[2] == history[k][0]) {
                    strcpy(inputBuffer,history[k]);
                    break;
                }
            }
        }

        length = strlen(inputBuffer);

/* examine every character in the inputBuffer */
        for (i = 0; i < length; i++) {
            switch (inputBuffer[i]) {
                case ' ':
                case '\t': /* argument separators */
                if (start != -1) {
                    args[ct] = &inputBuffer[start]; /* set up pointer */
                    ct++;
                }
                inputBuffer[i] = '\0';
                start = -1;
                break;

                case '\n': /* should be the final char examined */
                if (start != -1) {
                    args[ct] = &inputBuffer[start];
                    ct++;
                }
                inputBuffer[i] = '\0';
                args[ct] = NULL; /* no more arguments to this command */
                break;
                case '&':
                *background = 1;
                inputBuffer[i] = '\0';
                break;

                default: /* some other character */
                if (start == -1) {
                    start = i;
                }
            }
            args[ct] = NULL; /* just in case the input line was > 80 */
        }
    }


/* The main() function presents the prompt "sh->" and then invokes setup(), which waits for the user to enter a command. The contents of the command entered by the user are loaded into the args array. For example, if the user enters ls -l at the COMMAND-> prompt, args[0] will be set to the string ls and args[1] will be set to the string -l. (By string, we mean a null-terminated, C-style string variable.) */
int main(void) {
    char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
    int background, status; /* equals 1 if a command is followed by '&' */
    char *args[MAX_LINE / 2 + 1]; /* command line arguments */
    pid_t pid; /* the process's id */

/* set up the signal handler */
    struct sigaction handler;
    handler.sa_handler = handle_SIGINT;
    sigaction(SIGINT, &handler, NULL);

    loadHistory();

    while (1) {
        background = 0;

        printf("\nsh ->");
        fflush(0);
        setup(inputBuffer, args, &background); /* get next command */
        fflush(0);
        pid = fork(); /* assign the process id */
        if (pid < 0) {
            fprintf(stderr, "ERROR: Could not properly fork.");
            saveHistory();
            exit(-1); /* unsucessful exit because the fork could not be created */
        } else if (pid == 0) { /* PID was forked successfully */
            status = execvp(*args, args); /* execute the command */
            if (status < 0) {
                fprintf(stderr, "ERROR: Could not execute %s", args[0]);
                saveHistory();
                exit(1);
            }
        } else if (background == 0) { /* if the fork is run in the foreground */
            wait(NULL);
        }
    }

    return EXIT_SUCCESS;
}

3 个答案:

答案 0 :(得分:3)

问题可能是您在使用前没有初始化history中的内存。与Java不同,C在声明时不会初始化内存,因此history指针将指向垃圾,而不是零数组。

C与Java具有非常不同的内存模型,因此在其余代码中可能存在类似的问题。我建议您阅读如何在C中管理内存。

答案 1 :(得分:1)

在第一次运行时,如果未创建历史记录文件,则不会加载历史记录。但是你似乎没有清理history数组(默认值不是全零,与Java相反),因此它包含一些垃圾。在退出处,当您将值写出来时,您将历史文件放入垃圾箱,然后将其读回。

您应该确保所有字符串都以NULL结尾。总是


请尝试执行以下操作:

  1. 删除现有的历史记录文件
  2. main的开头,初始化history,以便每行在开头包含NULL
  3. 每当将任何内容存储到历史记录中时,请确保使用某些标准字符串复制功能(如strcpy),或者复制终止NULL。
  4. 顺便说一下,检查if(history[i] != '\0')是错误的,因为history[i]指向已分配的历史缓冲区部分。您是否想要if (history[i][0] == '\0')(这会检查第i条历史记录行是否为空)?

    嗯,固定大小的缓冲区是邪恶的,但这是另一回事。


    对于新发布的代码:各处都存在大量问题。

    其中之一:(strcmp(inputBuffer, "r\n\0") != 0) && (strncmp(inputBuffer, "r x", 2) != 0)中的setup支票完全错误。首先,您不需要显式添加\0,因为每个字符串文字都由\0隐式结束。第二部分:第一部分(strcmp(inputBuffer, "r\n\0") != 0)检查inputBuffer的内容是r\n,第二部分(strncmp(inputBuffer, "r x", 2) != 0)检查inputBuffer是否以{{1}开头}。显然,这两个条件是互斥的,所以如果你在它们之间放置r x,结果永远不会成真。

    给定代码中的许多代码构造都不是您想要的。我建议你慢慢调试调试器中的代码,检查每个变量和函数调用的任何结果,我打赌你会感到惊讶。

    代码最大的问题之一是使用字符串和整体内存管理。请阅读有关此主题的内容:它对C程序至关重要。几点说明:

    1. 指针指向一些原始内存。编译器不检查&&指针下的内存是否确实包含char*
    2. 更糟糕的是,有一种惯例,即字符串与指向其第一个字符的指针相同。字符串的结尾始终为0。没有其他方法可以确定字符串的结尾。如果以某种方式覆盖尾随0,则字符串的结尾将位于内存中的下一个0。
    3. 在单个字符级别上使用字符串通常是不切实际的。有许多实用功能,例如charstrcmp,可以为您执行此操作。通常这些功能更有效,并且没有错误。
    4. 我仍然无法在strcpy中找到将数据放入历史记录的代码。有一部分注释“检查命令是否是历史检索命令。如果不是,则将其添加到历史记录中”,但这部分不执行(部分原因是因为它从未执行过错误检查开头,部分是因为里面的代码不会复制 setup,只有它。

答案 2 :(得分:-1)

您将很难让人们为您修理作业。学习编程是为了不断地将你的头撞在墙上并重新尝试,即使它感到绝望。不要放弃。