Reputation: 1335
I want to parse /etc/passwd file using sscanf . Currently trying below snippet
sscanf(buf,"%s:%*s:%*u:%*u:%*s:%*s",
szName, &ncUser_gid);
its wrong. i need to parse only username and respective group Id of that user
Upvotes: 2
Views: 2829
Reputation: 755114
The basic answer is to use (negated) 'scan sets' — read the manual for sscanf()
.
if (sscanf(buf, "%[^:]:%*[^:]:%*[^:]:%u", szName, &ncUser_gid) != 2)
This reads a sequence of non-colons into szName
, and then skips a colon, the password field, a colon, the UID field, a colon, and reads the number in the next field into ncUser_gid
. It also checks that you got both values, while ignoring the other trailing fields (comment, home, shell).
Note that because you're using sscanf()
, there really isn't a need to process any of the trailing fields. Also, there are 7 fields, not 6, in a password file entry. With sscanf()
, this isn't a problem. If you were reading from a file, it would be. Also, if you were reading from a file, you'd have to worry about scan sets not skipping leading white space, which would be the newline left over from the previous line of input. For file-stream parsing, you'd need to use:
int rc;
if ((rc = fscanf(fp, " %[^:]:%*[^:]:%u:%u:%[^:]:%[^:]:%[^:]",
username, &uid, &gid, comment, homedir, shell)) != 5 && rc != 6)
…handle format error…
if (rc == 5)
shell[0] = '\0';
Note that there does not have to be any data for the shell field. This would run foul of an empty comment field, too, but that is normally populated. Note that it skipped the password; it is seldom interesting in modern versions of Unix.
sscanf()
example#include <stdio.h>
int main(void)
{
char buf[] = "root:*:0:1:System Administrator:/var/root:/bin/sh";
char szName[10] = "Pygmalion"; // Make sure it isn't empty!;
unsigned int ncUser_gid = 23456; // Make sure it isn't zero!
if (sscanf(buf, "%[^:]:%*[^:]:%*[^:]:%u", szName, &ncUser_gid) != 2)
printf("Ooops!\n");
else
printf("User: [%s]; GID = %u\n", szName, ncUser_gid);
return 0;
}
Output:
User: [root]; GID = 1
(I hacked the entry so the UID and GID are different.)
fscanf()
example#include <stdio.h>
int main(void)
{
const char passwd[] = "/etc/passwd";
FILE *fp = fopen(passwd, "r");
if (fp == 0)
{
fprintf(stderr, "failed to open '%s' for reading\n", passwd);
return 1;
}
char username[64];
unsigned uid;
unsigned gid;
char comment[64];
char homedir[64];
char shell[64];
int rc;
while (!feof(fp))
{
if ((rc = fscanf(fp, " %63[^:\n]:%*[^:\n]:%u:%u:%63[^:\n]:%63[^:\n]:%63[^:\n]",
username, &uid, &gid, comment, homedir, shell)) != 5 && rc != 6)
{
int c;
while ((c = getc(fp)) != EOF && c != '\n')
;
}
else
{
if (rc == 5)
shell[0] = '\0';
printf("[%s] %u %u [%s] [%s] [%s]\n", username, uid, gid, comment, homedir, shell);
}
}
return 0;
}
Note that on a Mac, the password file starts with a number of lines of #
comments. The %[^:\n]
notation, or something similar, is required to avoid problems parsing that section of the file. On sane systems without such comment lines in the file, you can probably get away without them. Note too that the code protects itself from overflow in the string fields.
Also, I continued with unsigned
integers for UID and GID, but nobody
has a negative value -2
for both UID and GID, so a signed type might be better.
Example output:
[nobody] 4294967294 4294967294 [Unprivileged User] [/var/empty] [/usr/bin/false]
[root] 0 0 [System Administrator] [/var/root] [/bin/sh]
[daemon] 1 1 [System Services] [/var/root] [/usr/bin/false]
…
All users have a specific shell specified on my Mac so the 'rc == 5' code hasn't really been tested.
Sample output:
[# Open Directory.
##
nobody] 4294967294 4294967294 [Unprivileged User] [/var/empty] [/usr/bin/false
JFTR: Tested on a Mac running macOS 10.12.5 using GCC 7.1.0. Compilation command line like:
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
> -Wstrict-prototypes pw89.c -o pw89
$
Upvotes: 4