如何使用sscanf忽略字段(%*被拒绝)

huangapple go评论85阅读模式
英文:

How to ignore fields with sscanf (%* is rejected)

问题

我希望在处理字符串时忽略特定的字段。

sscanf的man页面上说:

“可选的'*'赋值抑制字符:scanf()根据转换说明读取输入,但会丢弃输入。不需要相应的指针参数,并且此规范不包括在scanf()返回的成功赋值计数中。”

尝试在Golang中使用此功能来忽略第三个字段:

if c, err := fmt.Sscanf(str, " %s %d %*d %d ", &iface.Name, &iface.BTx, &iface.BytesRx); err != nil || c != 3 {

编译没有问题,但在运行时,err被设置为:

“bad verb %* for integer”

Golang的文档没有明确提到%*转换说明,但它确实说:

“fmt包实现了类似于C的printf和scanf的格式化I/O。”

它没有指示%*未实现,那么...我做错了吗?还是它只是被悄悄地省略了?...但是,为什么它能编译通过呢?

英文:

I wish to ignore a particular field whilst processing a string with sscanf.

Man page for sscanf says

An optional '*' assignment-suppression character: scanf() reads input as directed by the conversion specification, but discards the input. No corresponding pointer argument is required, and this specification is not included in the count of successful assignments returned by scanf().

Attempting to use this in Golang, to ignore the 3rd field:

if c, err := fmt.Sscanf(str, " %s %d %*d %d ", &iface.Name, &iface.BTx, &iface.BytesRx); err != nil || c != 3 {

compiles OK, but at runtime err is set to:

bad verb %* for integer

Golang doco doesn't specifically mention the %* conversion specification, but it does say,

Package fmt implements formatted I/O with functions analogous to C's printf and scanf.

It doesn't indicate that %* is not implemented, so... Am I doing it wrong? Or has it just been quietly omitted? ...but then, why does it compile?

答案1

得分: 4

据我所知,在这个任务中没有这样的动词(格式说明符在fmt包中被称为)。然而,你可以指定一些动词并忽略它的值。虽然这样做不太友好,但理想情况下应该可以工作:

fmt.Scan(&a, _, &b)

可惜,它不起作用。所以你下一个最好的选择是声明变量并忽略你不想要的变量:

var a, b, c int
fmt.Scanf("%d %v %d", &a, &b, &c)
fmt.Println(a, c)

%v会读取一个以空格分隔的标记。根据你正在扫描的内容,你可以快进到你需要扫描的位置。有关在缓冲区中寻找的详细信息,请参见此答案。如果你使用stdio或者不知道输入的长度,那么你似乎就没有办法了。

它没有指示%*没有被实现,所以...我做错了吗?还是它只是被悄悄地省略了?...但是,为什么它能编译通过?

它能编译通过是因为对于编译器来说,格式字符串只是一个像其他字符串一样的字符串。该字符串的内容在运行时由fmt包的函数进行评估。一些C编译器可能会检查格式字符串的正确性,但这是一个特性,而不是规范。在Go中,go vet命令将尝试警告您有关格式字符串错误和不匹配参数的问题。

编辑

对于需要解析一行整数并只关心其中一些的特殊情况,可以使用fmt.Scan与整数切片结合使用。以下示例从stdin读取3个整数并将它们存储在名为vals的切片中:

ints := make([]interface{}, 3)
vals := make([]int, len(ints))

for i, _ := range ints {
    ints[i] = interface{}(&vals[i])
}

fmt.Scan(ints...)
fmt.Println(vals)

这可能比传统的split/trim/strconv链更短。它创建了一个指向vals中每个值的指针切片。然后,fmt.Scan填充这些指针。通过这种方式,你甚至可以通过反复分配相同的指针来忽略大多数值:

ignored := 0

for i, _ := range ints {
    if(i == 0 || i == 2) {
        ints[i] = interface{}(&vals[i])
    } else {
        ints[i] = interface{}(&ignored)
    }
}

上面的示例将ignore的地址分配给除第一个和第二个之外的所有值,从而通过覆盖它们来有效地忽略它们。

英文:

To the best of my knowledge there is no such verb (as the format specifiers are called in the fmt package) for this task. What you can do however, is specifying some verb and ignoring its value. This is not particularly memory friendly, though. Ideally this would work:

fmt.Scan(&a, _, &b)

Sadly, it doesn't. So your next best option would be to declare the variables and ignore the one
you don't want:

var a,b,c int
fmt.Scanf("%d %v %d", &a, &b, &c)
fmt.Println(a,c)

%v would read a space separated token. Depending on what you're scanning on, you may fast forward the
stream to the position you need to scan on. See this answer
for details on seeking in buffers. If you're using stdio or you don't know which length your input may
have, you seem to be out of luck here.

> It doesn't indicate that %* is not implemented, so... Am I doing it
> wrong? Or has it just been quietly omitted? ...but then, why does it
> compile?

It compiles because for the compiler a format string is just a string like any other. The content of that string is evaluated at run time by functions of the fmt package. Some C compilers may check format strings
for correctness, but this is a feature, not the norm. With go, the go vet command will try to warn you about format string errors with mismatched arguments.

Edit:

For the special case of needing to parse a row of integers and just caring for some of them, you
can use fmt.Scan in combination with a slice of integers. The following example reads 3 integers
from stdin and stores them in the slice named vals:

ints := make([]interface{}, 3)
vals := make([]int, len(ints))

for i, _ := range ints {
	ints[i] = interface{}(&vals[i])
}

fmt.Scan(ints...)
fmt.Println(vals)

This is probably shorter than the conventional split/trim/strconv chain. It makes a slice of pointers
which each points to a value in vals. fmt.Scan then fills these pointers. With this you can even
ignore most of the values by assigning the same pointer over and over for the values you don't want:

ignored := 0

for i, _ := range ints {
	if(i == 0 || i == 2) {
		ints[i] = interface{}(&vals[i])
	} else {
		ints[i] = interface{}(&ignored)
	}
}

The example above would assign the address of ignore to all values except the first and the second, thus
effectively ignoring them by overwriting.

huangapple
  • 本文由 发表于 2013年4月5日 10:40:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/15825007.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定