Home > database >  How do I read from a file and output specific strings in c
How do I read from a file and output specific strings in c

Time:10-25

I'm writing a program that will read from /etc/passwd and output the username and shell.

For example, here is the first line of the /etc/passwd file:

root:x:0:0:root:/root:/bin/bash

I need to only output the user and the shell. In this instance it would print:

root:/bin/bash

The values are separated by ':' so I just need to print the string before the first ':' and the string after the 6th ':'

Here is the code I have so far:

#include <string.h>

#define BUFFERSIZE 4096

int printf(const char *text, ...);

int main(void) {
    int fd;
    int buff_size = 1;
    char buff[BUFFERSIZE];
    int size;

    fd = open("/etc/passwd", O_RDONLY);
    if (fd < 0) {
        printf("Error opening file \n");
        return -1;
    }

    size = strlen(buff - 17);
    size = size   1;

    while ((size = read(fd, buff, 1)) > 0) {
        buff[1] = '\0';
        write(STDOUT_FILENO, buff, size);
    }
}

(I am creating prototypes for printf because one of the requirements was to write the program without including <stdio.h> or <stdlib.h>)

CodePudding user response:

Your program has undefined behavior when you evaluate strlen(buff - 17). It is unclear why you do this.

You can solve the problem with these simple steps:

  • read one byte at a time
  • count the ':' on the line
  • output the byte if the count is equal to 0 or equal to 6.
  • reset the count at newline (and print the newline)

Note that read(fd, &b, 1) and write(1, &b, 1) return -1 in case of error or interruption and should be restarted if errno is EINTR.

Here is a modified version:

#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void) {
    int fd;
    unsigned char b;
    int count;
    ssize_t ret;

    fd = open("/etc/passwd", O_RDONLY);
    if (fd < 0) {
        write(2, "Error opening /etc/password\n", 28);
        return 1;
    }

    count = 0;
    for (;;) {
        ret = read(fd, &b, 1);
        if (ret == 0) { // end of file
            break;
        }
        if (ret < 0) { // error
            if (errno == EINTR)
                continue;
            write(2, "Read error on /etc/password\n", 28);
            return 1;
        }
        if (b == '\n') {
            // reset count, print b
            count = 0;
        } else
        if (b == ':') {
            // increment count, print ':' only if count == 1
            count = count   1;
            if (count != 1)
                continue;
        } else
        if (count != 0 && count != 6) {
            // print b only if count is 0 or 6
            continue;
        }
        for (;;) {
            ret = write(1, &b, 1);
            if (ret == 1)
                break;
            if (ret < 0 && errno = EINTR)
                continue;
            write(2, "Write error\n", 12);
            return 1;
        }
    }
    close(fd);
    return 0;
}

CodePudding user response:

Another approach is to use a single loop and a state variable to track the state of where you are in each line based on the number of colons read. The state-variable ncolon does that below. Essentially you read every character and check whether the loop is in a state where you should write the character as output or not. You condition the write on the number of colons, whether you are before the 1st or after the last.

Putting it altogether, you could do:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main (int argc, char **argv) {
  
  int fd,                   /* file descriptor */
      ofd = STDOUT_FILENO,  /* output file descriptor */
      ncolon = 0;           /* counter - number of colons seen */
  
  /* open file given on command line or read from stdin otherwise */
  if ((fd = argc > 1 ? open (argv[1], O_RDONLY) : STDIN_FILENO) == -1) {
    return 1;
  }
  
  for (;;) {                              /* loop continually */
    unsigned char c;                      /* storage for character */
    int rtn;                              /* var to save return */
  
    if ((rtn = read (fd, &c, 1)) < 1) {   /* validate read of 1 char */
      if (rtn == -1) {                    /* return on error */
        return 1;
      }
      break;                              /* break read loop on EOF */
    }
    
    if (ncolon < 1 || ncolon == 6) {      /* if before 1st or after last */
      write (ofd, &c, 1);                 /* output char */
    }
    
    if (c == '\n') {                      /* reset ncolon on newline */
      ncolon = 0;
    }
    else if (c == ':') {                  /* increment on colon */
      ncolon  = 1;
    }
  }
  
  if (fd != STDIN_FILENO) {   /* close file */
    close (fd);
  }
}

Example Use/Output

$ ./read_etc-passwd /etc/passwd
root:/bin/bash
messagebus:/usr/bin/false
systemd-network:/usr/sbin/nologin
systemd-timesync:/usr/sbin/nologin
nobody:/bin/bash
mail:/usr/sbin/nologin
chrony:/usr/sbin/nologin
...

Confirm the Format

$ diff <(./read_etc-passwd /etc/passwd) <(awk -F: '{print $1":"$7}' /etc/passwd)

(no output means program output and awk output were identical)

  •  Tags:  
  • c
  • Related