Запись строки в файл приводит к странным символам - PullRequest
1 голос
/ 04 декабря 2010

Мое домашнее задание - создать оболочку (выполнено) с функцией истории (выполнено), а затем записать историю в файл (где у меня проблемы), чтобы его можно было загрузить при следующем запуске.

Обычно я человек Java, поэтому мне нужно искать почти все функции c. В любом случае, у меня проблемы с записью моего массива строк в файл. По какой-то причине я продолжаю получать странные символы в зависимости от того, какую кодировку я использую. Например, если я запускаю программу и сразу же завершаю работу, файл истории содержит:

??_?

Если я запускаю ls, а затем завершаю работу, файл истории содержит:

ls
_?

Я удалил процедуры, которые не связаны. Заранее спасибо за любые указатели. Это третий день парирования. Я в своем уме.

#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 &lt 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 ]

3 голосов
/ 04 декабря 2010

Проблема может заключаться в том, что вы не инициализируете память в history перед использованием.В отличие от Java, C не инициализирует память, когда вы объявляете ее, поэтому указатель history будет указывать на мусор, а не на массив нулей.

C имеет совершенно другую модель памяти, чем Java, поэтому, вероятнобыть похожим на ошибки в остальной части вашего кода.Я бы порекомендовал вам прочитать о том, как управлять памятью в C.

1 голос
/ 04 декабря 2010

При самом первом запуске, когда файл истории не был создан, вы не загружаете историю. Однако вы, похоже, не очищаете массив history (значения по умолчанию не все нули, в отличие от Java), поэтому он содержит некоторый мусор. На выходе, когда вы записываете свои значения, вы помещаете в файл истории этот мусор, а затем читаете его обратно.

Вы должны убедиться, что все ваши строки имеют нулевое окончание. Всегда.


Пожалуйста, попробуйте сделать следующее:

  1. Удалить существующий файл истории
  2. В начале main инициализировать history, чтобы каждая строка содержала NULL в начале
  3. Всякий раз, когда вы сохраняете что-либо в истории, убедитесь, что вы используете некоторые стандартные функции копирования строк (например, strcpy) или также копируете завершающий NULL.

Кстати, проверка 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 с r x. Очевидно, что эти два условия являются взаимоисключающими, поэтому, если вы поставите между ними &&, результат никогда не будет истинным.

Многие конструкции кода в данном коде не соответствуют вашим намерениям. Я предлагаю вам медленно пройтись по коду в отладчике, проверяя каждую переменную и любой результат вызова функции, держу пари, вы будете удивлены.

Одной из самых больших проблем с кодом является работа со строками и общее управление памятью. Пожалуйста, прочитайте что-нибудь на эту тему: это жизненно важно для программ на Си. Несколько заметок:

  1. Указатель указывает на некоторую необработанную память. Компилятор не проверяет, действительно ли память под указателем char* содержит char с или нет.
  2. Что еще хуже, существует соглашение, что строка примерно такая же, как указатель на свой первый символ. Конец строки всегда равен 0. Других способов определения конца строки нет. Если вы как-то перезапишете конечный 0, конец строки будет в следующем 0 в памяти.
  3. Обычно непрактично работать со строками на уровне одного символа. Существует множество полезных функций, таких как strcmp или strcpy, которые могут сделать это за вас. Обычно эти функции более эффективны и не содержат ошибок.
  4. Я до сих пор не могу найти код в setup, который поместил данные в историю. Есть часть с комментарием «проверяет, является ли команда командой извлечения истории. Если это не так, добавьте ее в историю», но эта часть этого не делает (частично потому, что она никогда не выполняется из-за неправильная проверка в начале, частично потому, что код внутри ничего не копирует с inputBuffer, только на это).
0 голосов
/ 04 декабря 2010

Вам будет трудно заставить людей починить вашу домашнюю работу для вас. Обучение программированию - это постоянно разбивать голову о стену и пытаться снова, даже если это кажется безнадежным. Не сдавайся.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...