Standard input stream behaviour on Linux

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Nate Eldredge

    #16
    Re: Standard input stream behaviour on Linux

    Tomás Ó hÉilidhe <toe@lavabit.co mwrites:
    Is there any way of finding out how many character there are
    "lingering" in stdin?
    >
    If so, I could flush it as follows:
    >
    void FlushStdin(void )
    {
    unsigned i = AmountLingering ();
    >
    while (i--)
    getchar();
    }
    >
    I could call FlushStdin before I use fgets.
    No, there isn't.

    My guess is you're imagining that the user types a number and then a
    newline, and that stdin has a buffer that contains these characters. It
    might, but it might also contain fewer (if the terminal is in a "raw"
    mode, for example), or more (if the user types ahead).

    What exactly do you mean by "lingering" and "flush"? I think if you try
    to pin down these definitions, you'll find that either it isn't what
    you want, it's impossible, or it's easily accomplished another way.

    In this case, I think you really want to discard everything until the
    end of the line, so you could just do that.

    while (getchar() != '\n') ;

    This isn't much of an improvement over just calling fgets() in the first
    place, however, which IMHO would be less confusing.

    Comment

    • Keith Thompson

      #17
      Re: Standard input stream behaviour on Linux

      Nate Eldredge <nate@vulcan.la nwrites:
      [...]
      In this case, I think you really want to discard everything until the
      end of the line, so you could just do that.
      >
      while (getchar() != '\n') ;
      [...]

      And if getchar() returns EOF before the end of the line?

      --
      Keith Thompson (The_Other_Keit h) kst-u@mib.org <http://www.ghoti.net/~kst>
      Nokia
      "We must do something. This is something. Therefore, we must do this."
      -- Antony Jay and Jonathan Lynn, "Yes Minister"

      Comment

      • Nate Eldredge

        #18
        Re: Standard input stream behaviour on Linux

        Keith Thompson <kst@sdsc.eduwr ites:
        Nate Eldredge <nate@vulcan.la nwrites:
        [...]
        >In this case, I think you really want to discard everything until the
        >end of the line, so you could just do that.
        >>
        >while (getchar() != '\n') ;
        [...]
        >
        And if getchar() returns EOF before the end of the line?
        Oops.

        int c;
        while ((c = getchar()) != EOF && c != '\n') ;

        Comment

        • =?ISO-8859-1?Q?Tom=E1s_=D3_h=C9ilidhe?=

          #19
          Re: Standard input stream behaviour on Linux


          Thanks everyone for the helpful replies.

          So let me see if I understand, (keep in mind that I want my program to
          be fully-portable and to function as intended on every system).

          There's a quick-and-dirty solution as follows:

          int c;
          char buf[20];
          unsigned age;

          scanf("%u",&age );

          while ((c = getchar()) != EOF && c != '\n');

          fgets(buf,sizeo f buf,stdin);

          Will this *definitely* do what I want it to do on *every* system?
          (Forget for the moment that I should be checking the return values to
          see whether the input command was successful).

          And then there's the clean solution of always using fgets:

          char buf[20];
          unsigned age;

          fgets(buf,sizeo f buf,stdin);
          age = strtoul(buf);

          fgets(buf,sizeo f buf,stdin);

          And this will work perfectly all the time too, yeah?

          Comment

          • Barry Schwarz

            #20
            Re: Standard input stream behaviour on Linux

            On Oct 23, 12:01 am, Tomás Ó hÉilidhe <t...@lavabit.c omwrote:
            Thanks everyone for the helpful replies.
            >
            So let me see if I understand, (keep in mind that I want my program to
            be fully-portable and to function as intended on every system).
            >
            There's a quick-and-dirty solution as follows:
            >
                int c;
                char buf[20];
                unsigned age;
            >
                scanf("%u",&age );
            >
                while ((c = getchar()) != EOF && c != '\n');
            >
                fgets(buf,sizeo f buf,stdin);
            >
            Will this *definitely* do what I want it to do on *every* system?
            You made it a point to stress definitely and every.

            If c == EOF, you may need to check for an error. If so, the stream
            may be hosed and no longer useless. If not an error and stdin is
            accessing a file, attempting to read after EOF is pointless.
            (Forget for the moment that I should be checking the return values to
            see whether the input command was successful).
            >
            And then there's the clean solution of always using fgets:
            >
                char buf[20];
                unsigned age;
            >
                fgets(buf,sizeo f buf,stdin);
                age = strtoul(buf);
            >
                fgets(buf,sizeo f buf,stdin);
            >
            And this will work perfectly all the time too, yeah?
            If the first input UINT_MAX, you will never know the value has been
            reduced to a modulus. You also don't check it this input is numeric.


            Comment

            • Nick Keighley

              #21
              Re: Standard input stream behaviour on Linux

              On 22 Oct, 23:56, Nate Eldredge <n...@vulcan.la nwrote:
              Tomás Ó hÉilidhe <t...@lavabit.c omwrites:
              <snip>
              Just as an aside, is a new line always represented as a single byte
              (i.e. '\n'), coz I've seen how on some systems you have "\r\n".
              >
              Yes, that's the C definition of "newline". Systems that use "\r\n"
              (e.g. DOS/Windows) will generally silently convert "\r\n" to and from
              "\n" when the file is opened in text mode ("t" flag to fopen()).
              I think they *have* to do that conversion if they want to
              be a conformant implementations .
              On
              such systems it is especially important to get that mode right.
              yes

              --
              Nick Keighley

              Comment

              • Eric Sosman

                #22
                Re: Standard input stream behaviour on Linux

                Tomás Ó hÉilidhe wrote:
                On Oct 23, 4:34 am, Martin Ambuhl <mamb...@earthl ink.netwrote:
                >
                > scanf("%u", &siblings); /* mha: note change */
                >
                >
                Did you mean to write?:
                >
                "%u\n"
                Highly unlikely. This would read and convert the number (or
                try to, anyhow), and then would read and read and read and read
                and keep on reading until it found something that wasn't a white
                space character. It would then leave the non-white character
                unread. Result: The user must start typing his name before he
                sees the prompt for it.

                If you *must* use scanf() for interactive input -- not a
                wonderful idea, but chacun à son goût -- it is probably better
                to do the white-space skipping before consuming the desired
                input item rather than after consuming it.

                --
                Eric.Sosman@sun .com

                Comment

                • Eric Sosman

                  #23
                  [OT] Re: Standard input stream behaviour on Linux

                  Barry Schwarz wrote:
                  [...] the stream
                  may be hosed and no longer useless. [...]
                  "No longer useless" -- Something we can all aspire to. ;-)

                  --
                  Eric.Sosman@sun .com

                  Comment

                  • Flash Gordon

                    #24
                    Re: Standard input stream behaviour on Linux

                    Nate Eldredge wrote, On 22/10/08 23:56:
                    Tomás Ó hÉilidhe <toe@lavabit.co mwrites:
                    <snip>
                    >Just as an aside, is a new line always represented as a single byte
                    >(i.e. '\n'), coz I've seen how on some systems you have "\r\n".
                    >
                    Yes, that's the C definition of "newline". Systems that use "\r\n"
                    (e.g. DOS/Windows) will generally silently convert "\r\n" to and from
                    "\n" when the file is opened in text mode ("t" flag to fopen()). On
                    such systems it is especially important to get that mode right.
                    The "t" flag is non-standard. That standard specifies that you use the
                    "b" flag to specify binary and if you don't it is text. When open as
                    text the implementation is *required* to do the conversion from however
                    it indicates the new-line to a \n. Note though that *some*
                    implementations on Windows (I'm specifically thinking of Cygwin as
                    installed by default) use a text file format that uses just a \n to
                    indicate the new-line
                    --
                    Flash Gordon
                    If spamming me sent it to smap@spam.cause way.com
                    If emailing me use my reply-to address
                    See the comp.lang.c Wiki hosted by me at http://clc-wiki.net/

                    Comment

                    • Antoninus Twink

                      #25
                      Re: Standard input stream behaviour on Linux

                      On 23 Oct 2008 at 16:41, Eric Sosman wrote:
                      -- not a wonderful idea, but chacun à son goût --
                      If you're going to be as pretentious as to litter your posts with
                      snippets of French, at least do it correctly.

                      Either "à chacun son goût", or else "chacun a son goût" with no accent.

                      Comment

                      • John Bode

                        #26
                        Re: Standard input stream behaviour on Linux

                        On Oct 22, 4:37 pm, Tomás Ó hÉilidhe <t...@lavabit.c omwrote:
                        I have a fully-portable C program (or at least I think I do). It works
                        fine on Windows, but malfunctions on Linux. I suspect that there's
                        something I don't know about the standard input stream that's causing
                        the problem.
                        >
                        Here's how I wrote the program originally:
                        >
                        #include <stdio.h>
                        #include <string.h>
                        >
                        void TrimNewLineOffE nd(char *const str)
                        {
                            str[strlen(str) - 1] = 0;
                        >
                        }
                        >
                        int main(void)
                        {
                            char buf[20];
                        >
                            unsigned age, siblings;
                        >
                            printf("What age are you? ");
                        >
                            scanf("%u",&age );
                        >
                            printf("How many siblings do you have? ");
                        >
                            scanf("%u", &siblings);
                        >
                            printf("What's your name? ");
                        >
                            fgets(buf,15,st din);
                        >
                            TrimNewLineOffE nd(buf);
                        >
                            printf("\n\nYou r name is %s, you're %u years old and you have %u
                        siblings!\n\n",
                                   buf,age,sibling s);
                        >
                            return 0;
                        >
                        }
                        >
                        This original code didn't work as intended on either Windows or Linux.
                        On both systems, when control reached "fgets", the user wasn't given a
                        chance to enter their name; instead, fgets returned immediately with
                        an empty string.
                        >
                        To remedy this problem on Windows, I put in "fflush(std in)" right
                        before the call to "fgets". This fixed the problem and the program
                        worked as intended. This didn't work on Linux however.
                        >
                        What am I doing wrong?
                        The problem is the calls to scanf() with the %u conversion specifier
                        leave the trailing newline from the last entry in the input stream,
                        which causes fgets() to return immediately.

                        Let's walk through the code. First you prompt for the age, and then
                        type in a number (say 42) and hit Return. The scanf() call with the
                        %u conversion specifier will skip over any leading whitespace, then
                        read and convert the '4' and '2' characters, stopping at the first non-
                        numeric character in the input stream, which in this case is the
                        newline character generated when you hit return. Then you type in the
                        number of siblings (say 2). Again, the scanf() call will skip over
                        any leading whitespace (the newline character left over from the last
                        entry), then read and convert the 2, stopping again at the trailing
                        newline, which is left in the input stream.

                        Now here's the problem. You call fgets(), and the first thing it sees
                        the newline left over from the last input, so it returns immediately
                        instead of waiting for you to enter a name. Calling scanf() with the
                        "%s" or "%c" conversion specifiers will have the same effect.

                        fflush(stdin) won't work, because fflush() is not defined on input
                        streams. You have to change how you're reading your inputs.

                        What I've found to work best is to read *everything* as a string using
                        fgets() (since it will consume the trailing newline), then convert and
                        assign as necessary. It makes for more robust error handling.

                        Example (untested - no warranties express or implied):

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

                        /**
                        * Since the integer values we're reading are likely very
                        * small (most people don't live past 999 years old or have
                        * more than 999 siblings), we're constraining our input
                        * buffer to hold 3 characters + a newline character +
                        * the 0 terminator.
                        */
                        #define BUFSIZE 3

                        /**
                        * Get a number from user input
                        *
                        * @param prompt [in] -- prompt string
                        * @param val [out] -- the desired value
                        * @return int -- 1 if value successfully read and converted
                        * 0 if error
                        */
                        int getUnsignedValu e(const char *prompt, usigned int *val)
                        {
                        char inBuf[BUFSIZE]; // input buffer
                        char *chk; // used for error checking
                        int ret = 0;

                        printf("%s : ", prompt);
                        fflush(stdout);

                        if (fgets(inBuf, sizeof inBuf, stdin))
                        {
                        /**
                        * first, look for a newline in the buffer; if it's
                        * not there, then the user entered a string that was
                        * longer than we were expecting and we need to
                        * reject it
                        */
                        char *newline = strchr(inBuf, '\n');
                        if (!newline)
                        {
                        /**
                        * newline not found, string too long. However,
                        * we don't want to leave garbage in the input
                        * stream so we'll read until we see the newline
                        * or until there's a read error
                        */
                        while (!newline && fgets(inBuf, sizeof inBuf, stdin))
                        {
                        newline = strchr(inBuf, '\n');
                        }
                        fprintf(stderr, "Error -- Input too long\n");
                        }
                        else
                        {
                        /**
                        * String was not too long, so we attempt to convert
                        * it to an unsigned integer using the strtoul()
                        * function.
                        */
                        *val = (unsigned) strtoul(inBuf, &chk, 10);
                        if (!isspace(*chk) || *chk != 0)
                        {
                        fprintf(stderr, "Error -- input was not a valid number
                        \n");
                        }
                        else
                        {
                        ret = 1; // Success! val now contains the converted
                        value
                        }
                        }
                        }
                        else
                        {
                        fprintf(stderr, "Error occurred while reading from stdin\n");
                        }

                        return ret;
                        }

                        int main(void)
                        {
                        unsigned age, sibs;
                        char name[15];

                        if (!getUnsignedVa lue("What is your age", &age))
                        {
                        /* handle error */
                        }

                        if (!getUnsignedVa lue("How many siblings do you have", &sibs))
                        {
                        /* handle error */
                        }

                        printf("What is your name? ");
                        fflush(stdout);
                        if (fgets(name, sizeof name, stdin))
                        {
                        char *newline = strchr(name, '\n');
                        if (newline)
                        *newline = 0;

                        printf("Your name is %s, you are %u years old and you have %u
                        siblings\n");
                        }
                        else
                        {
                        fprintf(stderr, "Error while reading name from stdin\n");
                        }

                        return 0;
                        }

                        Obviously there are better ways to structure the code to give the user
                        opportunities to re-enter data. This is just a quick-n-dirty to show
                        how much interactive input can suck.

                        Comment

                        • John Bode

                          #27
                          Re: Standard input stream behaviour on Linux

                          On Oct 23, 5:17 pm, John Bode <jfbode1...@gma il.comwrote:

                          [snip]
                          #define BUFSIZE 3
                          Goddammit, I knew I was going to screw up. Obviously, that 3 should
                          be a 5.

                          Grump.

                          Comment

                          • John Bode

                            #28
                            Re: Standard input stream behaviour on Linux

                            On Oct 23, 5:17 pm, John Bode <jfbode1...@gma il.comwrote:
                            [snip]
                                     printf("Your name is %s, you are %u years old and you have %u
                            siblings\n");
                            And the other fuckup. Obviously, there should be three additional
                            arguments to that call:

                            printf("Your name is %s, you are %u years old and you have %u
                            siblings\n",
                            name, age, sibs);

                            Comment

                            Working...