In my C program, I use printf to print a formatted string to stdout then readline to get input from user with the ability to move cursor and navigate history. The issue is that if I enter a character then press backspace, the whole line, including the string printed by printf, gets deleted. Is there a way to fix this bad behavior? Should I report this as a bug to readline developers? Or should I print my formatted text to a buffer then use it as a prompt to readline?
A sample similar to the code:
...
printf("Some formatted text",...);
foo(buffer,length);
....
Inside foo:
{
...
temp=readline(NULL);
//Checking length...
...
strcpy(buffer,temp);
free(temp);
....
}
If you want a string to appear on the same terminal line as the line you're entering and reading with readline, you should make the string the 'prompt' argument to readline. This is difficult with the way you have things set up (the prompt printed outside of a function that calls readline), but if you can change that you can fix it:
char prompt[64]; // or some larger size
snprintf(prompt, sizeof(prompt), "Some formatted test", ...
foo(prompt, buffer, length);
foo(const char *prompt, char *buffer, size_t length) {
...
temp = readline(prompt);
// Checking length
Related
When you read from stdin using getchar, fgets or some similar function, if you type some text and then put an eof (control+d in linux) you cannot delete the previous text. For example, if I type 'program' and then enter eof by pressing control+d, I can't delete what I typed before, i.e. program.
#include<string.h>
#include<stdlib.h>
int main() {
char buffer[1024] = "";
printf("> ");
if(fgets(buffer,sizeof(buffer),stdin) == NULL){
puts("eof");
}
else{
puts(buffer);
}
return 0;
}
How can this be avoided?
The readline function of The GNU Readline Library I think is my best option to do the job. It's pretty simple to use but it uses dynamic memory to host the string so you have to use the free function to free up the memory. You can find more information by opening a terminal and typing 'man readline'.
The code would look like this:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <readline/readline.h>
int main() {
char *ptr = readline("> ");
if(!ptr){
puts("eof");
}
else{
puts(ptr);
}
free(ptr);
return 0;
}
To be able to use readline with gcc you must pass it -lreadline
When fgets reads a line, what will happen is that it will read characters from the specified stream until it encounters a '\n' or EOF, until it has read the specified maximum size to read or a read error occurs. It does not see what you are doing on your keyboard at all. It only sees the stream, but it is the terminal that sends the data to the stream.
What's happening when you are editing the input has absolutely nothing to do with fgets. That's the terminals job.
As Eric Postpischil wrote in the comments:
Pressing control-D in Linux does not signal EOF. It actually means “Complete the current read operation.” At that point, if characters have been typed, they are immediately sent to the program, whereas the system would usually wait until Enter is pressed. If no characters have been typed, the read operation completes with zero characters read, which some I/O routines treat as an EOF, and that is why programs may seem to receive an EOF when control-D is pressed at the start of a line of input. Since the data is sent to the program, of course there is no way to undo it—it has already been sent.
I guess there is some way to alter the behavior of pressing C-d, but then you need to decide what it should do instead. If you want it to do "nothing" instead of sending the data to stdin I cannot really see what you have won. The only use case I can see with this is if you for some reason are having a problem with accidentally pressing C-d from time to time.
One thing you could do is to take complete control of every keystroke. Then you would have to write code to move the cursor every time the user presses a key, and also write code to remove characters when the user is pressing backspace. You can use a library like ncurses for this.
It can't be avoided. Simply put, Ctrl+D ends the current read operation.
If you want to ignore this, make your own fgets based on fgetc and have it ignore end-of-file.
I am using the crypt function in C, where I am giving the command line input an encrypted word. I use the words in /usr/share/dict/words and encrypt them using the crypt function and then compare the encrypted output of the crypt function with the command line input. If the words are the same, then I give out the non-encrypted code as the output using a printf statement.
The code is given below.
#include<stdio.h>
#define _XOPEN_SOURCE
#include<unistd.h>
#include<cs50.h>
#include<string.h>
int
main(int argc, string argv[]){
char line[80];
string crypto;
if(argc>2||argc<2)
{
printf("ERROR. Enter only one crypt");
return 1;
}
string crypti=argv[1];
FILE *fr;
string as;
fr=fopen("/usr/share/dict/words","r");
if(!fr)
{
printf("File can't be read");
exit(-1);
}
while(fgets(line,80,fr)!=NULL)
{
as=crypt(line,"50");
if(strcmp(as,crypti)==0)
{
printf("%s",line);
break;
}
}
fclose(fr);
}
The code seems to work fine just for 1 input i.e when I give "./a.out 50q.zrL5e0Sak"(without quotes). However, if I use any other input for the crypt, the code seems to fail. Another example for password:encrypted password is abaca:50TZxhJSbeG1I. The word abaca is present in the list but fails to identify. I am not able to fix this code to work for all inputs.
Add the following snippet to the beginning of while (fgets...) body:
size_t len = strlen(line);
if (len)
line[len-1]='\0';
There is usually a newline \n (when it was read) in the end of the buffer read by fgets.
Your original code works with "password" because only 8 first characters of the key are actually used by crypt. It would work with any word of length 8 or more as well.
Also, make sure that the output is flushed after you print the result, by adding a newline to your format string or (if you don't want to output an extra newline) calling fflush(stdout):
printf("%s\n",line);
/* or */
printf("%s",line);
fflush(stdout);
int main()
{
int i;
FILE *list,*file;
char temp[30];
list=fopen("filelist","rb");
while(fgets(temp,30,list)!=NULL)
{
file=fopen(temp,"r");
{
fclose(list);
return 0;
}
This is my code I basically want to open all files in filelist but my fopen call (exept the first one always returns a NULL am i missing something also this is my filelist
file1
file2
file3
file4
also i dont use file extensions and files exist in the same directory wtih executable.
fgets() stores the new-line character into the buffer it is populating so you need to remove it before calling fopen() within the while.
From the linked reference page for fgets():
Reads at most count - 1 characters from the given file stream and stores them in str. The produced character string is always NULL-terminated. Parsing stops if end-of-file occurs or a newline character is found, in which case str will contain that newline character.
Example code to remove the new-line:
char* nl = strrchr(temp, '\n');
if (nl) *nl = 0;
fgets leaves the newline on the end of the string, which you can plainly see if you add the following line afterwards:
printf ("[%s]\n", temp);
You'll see something like:
[file1
]
You need to remove it before use, which you can do this with something like:
size_t sz = strlen (temp);
if (sz > 0)
if (temp[sz-1] == '\n')
temp[sz-1] = '\0';
You can see this effect in action in the following complete program:
#include <stdio.h>
#include <string.h>
int main (void) {
size_t sz;
char temp[30];
printf ("\n> ");
while (fgets (temp, sizeof(temp), stdin) != NULL) {
printf ("before: [%s]\n", temp);
sz = strlen (temp);
if (sz > 0) {
if (temp[sz-1] == '\n') {
temp[sz-1] = '\0';
}
}
printf ("after : [%s]\n", temp);
printf ("\n> ");
}
return 0;
}
It basically uses your exact method to get a line using fgets (but from standard input) and then outputs the result both before and after removal of the trailing newline. A sample run follows:
pax> ./testprog
> hello
before: [hello
]
after : [hello]
> goodbye
before: [goodbye
]
after : [goodbye]
> [CTRL-D]
pax> _
You may also want to look at a few other things in that code segment:
the use of an open brace { at the end of the while loop.
the fact that you're opening the files within the loop and not doing anything with them (including closing them).
the use of "rb" open mode. Usually this is unnecessary, it's certainly unnecessary if you know it's a text file.
you should always check the return codes of functions that can fail (like fopen) before using them.
the canonical form of main in C where no arguments are needed is int main (void).
I'll state my case of which I am still uncertain: I thought my problem was with "fopen", but after trying every single solution, I ran into the extension problem, which I'm facing in Windows 10. It appears that Windows puts ".txt" automatically but, if you put ".txt" as extension, the name becomes ".txt.txt" at the end. So I left the file name with no extension, and put "file.txt" as argument of "fopen", and that was the only way it has worked for me.
A friend of mine needs to use MATLAB for one of his classes, so he called me up (a Computer Science Major) and asked if I could teach him C. I am familiar with C++, so I am also familiar with the general syntax, but had to read up on the IO library for C.
I was creating some simple IO programs to show my friend, but my third program is causing me trouble. When I run the program on my machine using Eclipse (with the CDT) Eclipse's console produces a glitchy output where instead of prompting me for the data, it gets the input and then prints it all at once with FAILURE.
The program is supposed to get a filename from user, create the file, and write to it until the user enters a blank line.
When I compile/run it on my machine via console (g++ files2.c) I am prompted for the data properly, but FAILURE shows up, and there is no output file.
I think the error lies with how I am using the char arrays, since using scanf to get the filename will create a functional file (probably since it ignores whitespace), but not enter the while loop.
#include <stdio.h>
#define name_length 20
#define line_size 80
int main() {
FILE * write_file; // pointer to file you will write to
char filename[name_length]; // variable to hold the name of file
char string_buffer[line_size]; // buffer to hold your text
printf("Filename: "); // prompt for filename
fgets(filename, name_length, stdin); // get filename from user
if (filename[name_length-1] == '\n') // if last char in stream is newline,
{filename[name_length-1] = '\0';} // remove it
write_file = fopen(filename, "w"); // create/overwrite file user named
if (!write_file) {printf("FAILURE");} // failed to create FILE *
// inform user how to exit
printf("To exit, enter a blank line (no spaces)\n");
// while getting input, print to file
while (fgets(string_buffer, line_size, stdin) != NULL) {
fputs(string_buffer, write_file);
if (string_buffer[0] == '\n') {break;}
}
fclose(write_file);
return 0;
}
How should I go about fixing the program? I have found next to nothing on user-terminated input being written to file.
Now if you will excuse me, I have a couple of files to delete off of my University's UNIX server, and I cannot specify them by name since they were created with convoluted filenames...
EDIT------
Like I said, I was able to use
scanf("%s", filename);
to get a working filename (without the newline char). But regardless of if I use scanf or fgets for my while loop, if I use them in conjunction with scanf for the filename, I am not able to write anything to file, as it does not enter the while loop.
How should I restructure my writing to file and my while loop?
Your check for the newline is wrong; you're looking at the last character in filename but it may be before that if the user enters a filename that's shorter than the maximum. You're then trying to open a file that has a newline in it's name.
These lines seem to be incorrect:
if (filename[name_length-1] == '\n') // if last char in stream is newline,
{filename[name_length-1] = '\0';} // remove it
You verify the name_length - 1 character,, which is 19 in your case without any regard of the introduced filename's length. So if your file name's length is less then 18 you won't replace the '\n' character at the end of your string. Obviously the file name can't contain '\n' character.
You need to get the size of you file name first with strlen() as an example.
if (filename[strlen(filename) - 1] == '\n')
{
filename[strlen(filename) - 1] = '\0';
}
(Don't forget to include the string.h header)
I hope I was able to help with my weak english.
Simple question I hope. I have a function that keeps prompting for user input (characters) and returns a character once it finds that the input is valid under certain conditions. I'm writing tests for this and other similar functions, but don't know how to fake user input. By the way, I'm using scanf() to get user input.
You can change the behaviour of standard input to read from a file with freopen. Place the test input in a file and call this before your test.
freopen("filename", "r", stdin);
You can do something like
echo -e "Test string\nAnother string" | ./a.out
The string of echo command should be in the sequence which the program requires
cat test_str_file | ./a.out
The file test_str_file should contain the test strings in the sequence the program requires
On the other hand you can simply replace the code's input section with some dummy sections. If you have a separate module for input, then replace it with dummy.
If you're on unix or cygwin, you can invoke your executable and ask it to use a text file as stdin. For example:
bash$ ./a.out < input_file
Why not just call the function with a default value?
This is a very naive solution, but it may do what you want:
char getFakeUserInput()
{
static char* fakeInput = "This is some user input\n";
static int pos = 0;
if(pos >= strlen(fakeInput))
return '\0';
return fakeInput[pos++]
}
Move the validation to another function. And test it independently. Or fake the user input refactor the remainder to take in a function pointer that is called back to collect some letters. That should enable a simple fake.
I've not written C for a long time but something like could also work
/* test this independentaly */
int isvalid(char* chars){
/* do stuff and return result */
}
/*real function */
char* getinput() {
scanf(....)
return stuff_from_scanf;
}
/*fake/mock function */
char* getinputFake(char * testString) {
return testString;
}
test() {
int result = isvalue(getinputFake("test data"));
/* rest of test */
}
You don't need to test the scanf function, but you could replace it with a fscanf and pass in the stream with the chars like this
stdin
char* getinput(FILE * stream) {
fscanf(stream....)
return stuff_from_fscanf;
}
test() {
FILE * stream = .....; /*create a dummy stream say from */
int result = isvalue(getinput(stream);
/* rest of test */
}