0

If I run the below code with scanf, it returns a string if it's in the tracks array. According to the book Head First C, this should work with fgets but returns nothing for me:

#include <stdio.h>
#include <string.h>

#define MAX 80

char tracks[][MAX] = {
    "The Girl from Ipanema",
    "Here Comes the Sun",
    "Wonderwall",
    "You Belong To Me",
    "Everlong",
};

void find_track(char search_for[])
{
    int i = 0;
    for(i = 0; i < 5; i++) {
        if(strstr(tracks[i], search_for)) {
            printf("Track %d: %s\n", i, tracks[i]);
        }
    }
}

int main()
{
    char search_for[MAX];
    printf("Search for: ");
    //scanf("%79s", search_for);
    fgets(search_for, MAX, stdin);
    find_track(search_for);
    return 0;
}

Input looks like the following:

./tracks
Search for: 
Here

Nothing happens

Ryan Mc
  • 833
  • 1
  • 8
  • 25

2 Answers2

1

It's probably because fgets will also read the newline, i.e. '\n'. In more details:

If you type Girl<enter> then search_forwill contain the characters: 'G' 'i' 'r' 'l' '\n' However, your tracks only contain 'G' 'i' 'r' 'l' without the '\n'.

Consequently you will not find a matching substring.

Try changing:

fgets(search_for, MAX, stdin);

into

fgets(search_for, MAX, stdin);
if (strlen(search_for) > 0) search_for[strlen(search_for)-1] = '\0';

to remove the trailing newline

Edit based on comments

It's true that (depending on your OS/environment) the input stream can be terminated without a newline (ctrl-z, ctrl-d on some systems). If that is done the above code is insufficient. It needs an extra check like:

if (strlen(search_for) > 0 && search_for[strlen(search_for)-1] == '\n')
    search_for[strlen(search_for)-1] = '\0';

to make sure that only a "newline" is converted to a string termination.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • Where is the newline coming from though? When I hit enter? – Ryan Mc Oct 13 '18 at 15:07
  • @RyanMc yes, when you hit enter the newline gets saved into the string – Support Ukraine Oct 13 '18 at 15:10
  • @RyanMc See http://man7.org/linux/man-pages/man3/fgetc.3.html It says: **If a newline is read, it is stored into the buffer.** – Support Ukraine Oct 13 '18 at 15:11
  • Thanks this worked. Kinda bugs me that the book said it works though. – Ryan Mc Oct 13 '18 at 15:18
  • @RyanMc Well, I don't know that book but the manual is clear about this. It's an important difference between `scanf` and `fgets` – Support Ukraine Oct 13 '18 at 15:19
  • 1
    Actually '\n' may not be present if user terminates input with Ctrl-D. Probably this also must be checked when overwriting last character with '\0'. – purec Oct 13 '18 at 15:36
  • @purec The string read will also lack a newline if the the line entered is longer than the number of characters that will fit in the buffer. – Andrew Henle Oct 13 '18 at 15:57
  • 1
    [A much simpler and more accurate way to remove a newline from a string](https://stackoverflow.com/a/28462221/4756299): `buffer[strcspn(buffer, "\n")] = 0;` – Andrew Henle Oct 13 '18 at 16:00
  • @AndrewHenle It may seem "much simpler" to you but have you ever looked what's really going on inside `strcspn`? It's correct that you save a few keyboard clicks. But the resulting code... what's more maintainable? The first time you saw that code, did you have to look up what `strcspn` was? Or did you just knew? KIS is a good principle. – Support Ukraine Oct 13 '18 at 16:11
  • @4386427 *It may seem "much simpler" to you* Your `if`statement with two conditions, one including a call to `strlen`, seems "simpler" to you? Really? *have you ever looked what's really going on inside `strcspn`?* Who cares? Have you ever looked at what's going on inside the startup code that gets called before your program even reaches `main()`? *The first time you saw that code, did you have to look up what `strcspn` was?* The first time you saw `strlen()` you had to either be told what it does, or look that up, too. As did every single person who writes C code. – Andrew Henle Oct 13 '18 at 16:28
  • @AndrewHenle If you really think that most C programmers know `strcspn` just as good as they know `strlen` then fine - just use it. It'll never go into the code base I manage. EOD. – Support Ukraine Oct 13 '18 at 16:58
  • @4386427 So your standard for code is: "If I don't know it, you can't use it." You're limiting other developers based on the limits of what you know? Seriously? Do you sit in code reviews and say "I don't know what this function does. You have to remove it."? Of course, now that you know what `strcspn` does, you'll let it be used in the code base you manage? Right? EOD indeed. – Andrew Henle Oct 14 '18 at 14:12
  • @AndrewHenle You can't end a discussion with multiple questions so I'll answer. It's not for the reasons you insinuate but for simplicity. So the answer is **no**, I don't turn down code because I don't know it. I'll turn down code that isn't written in a as simple, easy and maintainable way as possible. Our coding style guide have a whole section about "Avoid clever code" that stress the need for keeping code simple. At a review it's important to ensure that the code is functionally correct but it's equally important to ensure it's simple. A code base is maintained for decades ... – Support Ukraine Oct 20 '18 at 06:28
  • … and by different people at different levels of expertise. So letting one very skilled person make complicated clever code will hurt all other. Obvious examples to avoid are `5[arr]` for array, swapping variables without using a temp, and many more. There is always a "grey zone" where it can be discussed whether some code is too "clever" but in that case we always look for a more simple implementation. – Support Ukraine Oct 20 '18 at 06:32
-1

You may also use function memcpy to get rid of newline.

The C library function void *memcpy(void *str1, const void *str2, size_t n) copies n characters from memory area str2 to memory area str1.

fgets(s, MAX, stdin);  
memcpy(search_for, s, strlen(s)-1);    
find_track(search_for);

You may add some error handling too.

manoliar
  • 172
  • 1
  • 8