使用 sscanf 也有些年头了,但总是有些困惑,今天整理一下,记录下来,备忘。
- 如何一次性地读取一整行?
- sscanf 可以用于格式化扫描字符串得到有用的数据,如果字符串中间以空格作为分隔符,那么,连续的多个空格是算作一个分隔符还是需要在扫描格式串中显示地指明多个分隔符?
- 如何提取字符串中的 IP 地址?
- 如何解析 URL 的字符串?
直接上代码吧。
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
// Number of variables scanned.
int n = -1;
// Demo source string
char s[256] = {"demo string\r\n"
"default via 192.168.0.1 dev eth0\r\n"
"https://192.168.0.101:18883\r\n"
"this is \t the \t\f 4th \t\t\v line.\r\n"
"this is the 5th line."};
// Line buffer
char l[5][64] = {0, 0, 0, 0, 0};
// Line number
int ln = 0;
// IP address buffer
char ip[16] = "0.0.0.0";
// URL buffer
typedef struct URL_STRUCT
{
char protocol[6];
char ip[16];
unsigned int port;
} url;
url demo_url = {"xxx", "0.0.0.0", 0};
// Single line buffer
char line_parts[5][16] = {0, 0, 0, 0, 0};
// Pointer to source string
char *p = s;
do
{
n = sscanf(p, "%[^\r\n]%*c", l[ln]);
// Check scanned number is as expected
if (n != 1)
break;
// Output result
printf("scanned %d string %d lines as: [- %s -]\r\n", n, ln, l[ln]);
// strlen(l) +2 to skip "\r\n" to point the next line head
p += strlen(l[ln++]) + 2;
} while (1);
n = sscanf(l[1], "%*[^0-9.]%s", ip);
printf("\r\n"
"parsed ip address as %s [n=%d] from [- %s -]\r\n",
ip, n, l[1]);
n = sscanf(l[2], "%[^://]%*c%*c%*c%[^:]%*c%d",
demo_url.protocol,
demo_url.ip,
&demo_url.port);
printf("\r\n"
"parsed %d url components as \r\n"
" protocol : %s\r\n"
" ip address : %s\r\n"
" port number : %d\r\n",
n,
demo_url.protocol,
demo_url.ip,
demo_url.port);
// Scan strings from l[3] with extra WHITESPACE characters
n = sscanf(l[3], "%s %s %s %s %s",
line_parts[0],
line_parts[1],
line_parts[2],
line_parts[3],
line_parts[4]);
printf("\r\n"
"parse %d parts from [- %s -] as [%s][%s][%s][%s][%s]\r\n",
n,
l[3],
line_parts[0],
line_parts[1],
line_parts[2],
line_parts[3],
line_parts[4]);
// Scan strings from l[4] without extra WHITESPACE characters
n = sscanf(l[4], "%s %s %s %s %s",
line_parts[0],
line_parts[1],
line_parts[2],
line_parts[3],
line_parts[4]);
printf("\r\n"
"parse %d parts from [- %s -] as [%s][%s][%s][%s][%s]\r\n",
n,
l[4],
line_parts[0],
line_parts[1],
line_parts[2],
line_parts[3],
line_parts[4]);
return 0;
}
代码的运行结果如下所列:
scanned 1 string 0 lines as: [- demo string -]
scanned 1 string 1 lines as: [- default via 192.168.0.1 dev eth0 -]
scanned 1 string 2 lines as: [- https://192.168.0.101:18883 -]
scanned 1 string 3 lines as: [- this is the
4th
line. -]
scanned 1 string 4 lines as: [- this is the 5th line. -]
parsed ip address as 192.168.0.1 [n=1] from [- default via 192.168.0.1 dev eth0 -]
parsed 3 url components as
protocol : https
ip address : 192.168.0.101
port number : 18883
parse 5 parts from [- this is the
4th
line. -] as [this][is][the][4th][line.]
parse 5 parts from [- this is the 5th line. -] as [this][is][the][5th][line.]
通过以上实验,对先前的几个困惑都找到了答案。
- 在一个字符串中,sscanf 使用 "%s %s %s %s %s"解析带有空白符(注意不是空格符)作为分隔符的各个部分均可正确提取出来,而不论是单个的或者连续的空白符。这里所说的“空白符”包括:空格符(0x20),制表符(0x09),垂直制表符(0x0b),换页符(0x0c),换行符(0x0a)和回车符(0x0d)。
- 提取整行使用 "%[^\r\n]%*c",其表达式的含义是:在字符串中出现在不等于回车换行符之前的所有字符。需要稍微注意的是在移动指针时,要多两个字节。如果是Unix格式,则格式描述符就将变换为 "%[^\n]%*c",移动指针时,也只需要多一个字节就可以了。
- 提取字符串中的IPv4地址,可以用 "%*[^0-9.]%s",含义是:字符串中掠过不是数字或者 "."的字符,其余的都取出来。如果是IPv6地址,则可以用 "%*[^0-9:]%s"。
- 提取 URL 使用 "%[^://]%*c%*c%*c%[^:]%*c%d",使用 "://" 和 ":" 作为分割定位标志。注意这个描述符中的中间要有三个 "%*c",第1个和第2个略过 //,第三个才是IP地址的存储符;同样道理,"%*c%d"部分的 %*c 也是不能少的,它用户略过端口号前面的 ":"。
对sscanf的使用,总算有了些更清晰,更深入的理解。