Writing a string to a file results in strange characters
My homework assignment is to create a shell (done) with a history function (done) and then write the history to a file (this is where I am having problems) so that it can be loaded on the next run.
I'm normally a Java person, so I need to look up almost every c function. Anyways, I'm having issues write my string array to the file. For some reason I keep getting weird symbols depending on which encoding I use. For example, if I run the program and quit immediately, the history file contains:
??_?
If I run ls then quit, the history file contains:
ls _?
I removed the procedures that aren't related. Thanks in advance for any pointers. This is day 3 of hairpulling. I'm at my wits end.
#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 */
...
}
Dump of history[][]:
./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 _? ??_?
The rest of my code. loadHistory and saveHistory are before this:
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;
sigac开发者_运维知识库tion(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;
}
The problem could be that your are not initialising the memory in history
before use. Unlike Java, C does not initialise memory when you declare it so the history
pointer will be pointing to garbage, not an array of zeroes.
C has a very different memory model to Java so there are likely to be similar gotchas in the rest of your code. I'd recommend that you read up on how to manage memory in C.
At the very first run, when the history file wasn't created, you don't load the history. However you seem to not clean the history
array (the default values are not all zeroes, contrary to Java), so it contains some garbage. At the exit, when you write your values out, you put into the history file that garbage, and than read it back.
You should ensure that all your strings are NULL-terminated. Always.
Please try to do the following:
- Delete the existing history file
- At the beginning of
main
, initializehistory
so that each line contains NULL at the beginning - Whenever storing anything to the history, make sure that you are either using some standard string copying functions (like
strcpy
), or copy the terminating NULL as well.
By the way, the check if(history[i] != '\0')
is wrong, as history[i]
points to the already allocated part of history buffer. Did you want to put if (history[i][0] == '\0')
(which would check if the i-th history line is empty)?
Well, fixed sizes of buffers are evil, but this is other story.
To the newly posted code: there are massive problems everywhere.
One of them: the check (strcmp(inputBuffer, "r\n\0") != 0) && (strncmp(inputBuffer, "r x", 2) != 0)
in setup
is totally wrong. First, you don't need to add explicitly \0
, because each string literal ends implicitly by \0
. Second: the first part (strcmp(inputBuffer, "r\n\0") != 0)
checks if the inputBuffer
's content is r\n
, and the second part (strncmp(inputBuffer, "r x", 2) != 0)
checks if inputBuffer
starts with r x
. Clearly these two conditions are mutually exclusive, so if you put &&
between them, the result will never be true.
Many code constructs in the given code do not what you seem to intend. I suggest that you step slowly through the code in debugger, inspecting every variable and any result of the function call, I bet you'll be surprised.
One of the biggest problems with the code is the work with strings, and overall memory management. Please read something on this topic: it's vitally important for the C programs. A few notes:
- Pointer points to some raw memory. The compiler doesn't check whether the memory under the
char*
pointer really containschar
s or not. - To make it worse, there is a convention that string is kind of the same as pointer to its first character. The end of the string is 0, always. There is no other means to determine the end of the string. If you somehow overwrite the trailing 0, the string's end will be at the next 0 in the memory.
- It's usually impractical to work with strings on single character level. There are numerous utility functions like
strcmp
orstrcpy
which can do this for you. Usually those functions are more efficient, and have no bugs. - I still cannot find the code in
setup
which put the data into the history. There is a part with the comment "checks to see if the command is a history retrieval command. If it isn't then add it to the history", but this part doesn't do it (partially because it's never executed because of the wrong check at the beginning, partially because the code inside doesn't copy anything from theinputBuffer
, only to it).
精彩评论