我有以下.csv
文件,其中包含有关歌曲,艺术家,发行年份(如果指定)和收听次数的信息:
看猫拖进来了什么,毒药,看猫被毒药拖进来了什么,1,0,1,0《美好时光》,《毒药》,1988年,《美好时光》,《毒药》,1,1,21,21《相信的东西》,毒药,1990年,《相信的东西》,毒药,1,1,1,1《对我说下流话》,《毒药》,1978年;《对我说下流话》,《毒药》,1,1,1,1《咸狗》,普罗科尔·哈鲁姆,1969年;《咸狗》,普罗科尔·哈鲁姆,1,1,1,1《白色的阴影》,普罗科尔·哈鲁姆,1967年;《白色的阴影》,普罗科尔·哈鲁姆,1,1,3,3模糊,泥泞的水坑,2001,泥泞的水坑模糊,1,1,1,1艾米,纯草原联盟,艾米由纯草原联盟,1,0,4,0《Another One Bites the Dust》,Queen,1980;《Another One Bites the Dust》,Queen, 1,102,102自行车比赛,女王,1978年,自行车比赛由女王,1,1,3,3
文件名称和所需年份应作为命令行参数给出,程序应打印该特定年份的所有歌曲。
。:./a.out music.csv 1978
输出:
Talk dirty to me
Bicycle Race
Kiss You All Over
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX 300
typedef struct {
char song[101], *artist, *line;
long int year;
} music;
int checkYear(char *word)
{
for (int i = 0; i < strlen(word); i++) {
if (!isdigit(word[i]))
return 0;
}
return 1;
}
int main(int argc, char **argv)
{
FILE *fin = fopen(argv[1], "r");
if (!fin)
{
printf("Error opening the file.n");
return 1;
}
char buf[MAX];
//int nLines = 0; //count the number of lines
//music *array = NULL;
while( fgets(buf, MAX, fin))
{
buf[strcspn(buf, "n")] = ' '; // strip the trailing newline
char *word = strtok(buf, ",");
while (word)
{
//printf("Word is : %sn", word);
if (checkYear(word))
{
//printf("Year : %sn", word);
music *array = (music *)malloc(sizeof(music));
char *p;
array->year = strtol(word, &p, 10);
if (array->year == atoi(argv[2]))
{
//printf("Year : %ldt%dn", array->year, atoi(argv[2]));
if (scanf("%100[^,]", array->song) == 1)
{
printf("Song : %sn", array->song);
}
}
}
word = strtok(NULL, ",");
}
}
//printf("I've read %d linesn", nLines);
fclose(fin);
return 0;
}
到目前为止,一切正常,我可以从每行提取指定的年份,但是现在我只需要从这些行打印歌曲的名称(该行上的第一个标记)。我想过使用scanf("%[^,]")
来读取和打印直到第一个逗号的所有内容,但它只是陷入了一个无尽的循环。你能给我个主意吗?提前感谢!
代码中存在多个问题:
- 您没有检查命令行上是否传递了足够的参数,如果没有,可能会调用未定义的行为。
- 您不需要分配
music
结构:您只需解析前3个字段,检查年份并直接输出歌曲名称。 strtok()
不适合从csv中分割字段因为它将一系列分隔符视为单个分隔符,如果某些字段为空,这是不正确的,并且会导致解析无效。sscanf("%[^,]", ...)
将无法转换空字段。
从csv中分离字段行,我建议您使用类似strtok_r()
的实用程序函数,但针对csv进行了定制行。一个简单的版本将在,
和n
上停止并将它们替换为空字节,返回初始指针并更新下一个字段的指针。更高级的版本还可以处理引号。
修改后的版本:
#include <stdio.h>
#include <string.h>
#define MAX 300
char *get_field(char **pp) {
char *p, *start;
for (p = start = *pp; *p; p++) {
if (*p == ',' || *p == 'n') {
*p++ = ' ';
break;
}
}
*pp = p;
return start;
}
int main(int argc, char *argv[]) {
char buf[MAX];
FILE *fin;
char *filename;
char *select_year;
if (argc < 3) {
printf("Missing argumentsn");
return 1;
}
filename = argv[1];
select_year = argv[2];
fin = fopen(filename, "r");
if (!fin) {
printf("Error opening the file %s.n", filename);
return 1;
}
while (fgets(buf, sizeof buf, fin)) {
char *p = buf;
char *song = get_field(&p);
char *artist = get_field(&p);
char *year = get_field(&p);
if (!strcmp(year, target_year)) {
printf("%sn", song);
}
}
fclose(fin);
return 0;
}
关于:scanf("%[^,]")
这会消耗(最多但不包括)逗号。
所以下一个指令需要像getchar()
这样的东西来消耗逗号。否则,在下一个循环中将不会读取任何内容,因为stdin
中的第一个字符是同一个逗号。