英文:
Best way to bind shell output to a struct in Go?
问题
我有一些在某些硬件上运行的芯片,并且我想将shell命令的输出绑定到一个结构体上,以便进行报告/日志记录。
Num item1: 2
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
1 2 3 4 50 600 700 1 1
1a 2b 3c 4c 5d 6e 7f 2 2
Num item2: 2
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
2a 2b 2c 3 0 0 0 1 1
1 0 0 0 0 0 0 2 2
**************************************************
尝试
cat out.txt | grep -i "Num $1" -A 3 | grep -i nvme | tr -s ' ' | cut -d' ' -f1-7
这实际上还不错,我可以传入一个参数,比如decoders
或encoders
,并获取每个芯片的负载指标。然而,我现在很好奇如何将其绑定到Go中的结构体上的最佳方法。
目前,我可以编写一个自定义的反序列化器,类似于:
func main() {
out, err := exec.Command("/bin/sh", "metrics.sh", "encoders").Output()
if err != nil {
fmt.Println(err)
log.Fatal(err)
}
fmt.Println(string(out))
}
但我觉得肯定有更好的方法,比如将输出转换为JSON并绑定到一个结构体之类的。
英文:
I have some chips running on some hardware and I want to bind the output of a shell command to a struct to do reporting/logging on.
Num item1: 2
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
1 2 3 4 50 600 700 1 1
1a 2b 3c 4c 5d 6e 7f 2 2
Num item2: 2
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
2a 2b 2c 3 0 0 0 1 1
1 0 0 0 0 0 0 2 2
**************************************************
Attempt
cat out.txt | grep -i "Num $1" -A 3 | grep -i nvme | tr -s ' ' | cut -d' ' -f1-7
This actually isn't too bad, I can pass in an arg like decoders
or encoders
and get the load metrics for each chip. However, I'm curious now the best way to bind this to a struct in Go.
Currently, what I can do is code up a custom deserializer from something like:
func main() {
out, err := exec.Command("/bin/sh", "metrics.sh", "encoders").Output()
if err != nil {
fmt.Println(err)
log.Fatal(err)
}
fmt.Println(string(out))
}
But I feel like there has to be a better way, like outputting as JSON and binding to a struct or something.
答案1
得分: 2
我会将您的输入文件转换为CSV格式,因为它适用于原始的表格数据,而且Go语言的标准库中有一个CSV编码器/解码器:
awk -v OFS=',' '
$1 == "Num" {
count = $3
type = $2
getline
if ( !header++ ) {
$(NF+1) = "ID"
print
}
for ( id = 1; id <= count; id++ ) {
getline
$(NF+1) = type id
print
}
}
' file.txt
<sup>**警告:**该代码没有对字段进行CSV转义</sup>
INDEX,LOAD,MODEL_LOAD,INST,MEM,SHARE_MEM,P2P_MEM,DEVICE,NAMESPACE,ID
1,2,3,4,50,600,700,/dev/nvme0,/dev/nvme0n1,decoders:1
1a,2b,3c,4c,5d,6e,7f,/dev/nvme1,/dev/nvme1n1,decoders:2
2a,2b,2c,3,0,0,0,/dev/nvme0,/dev/nvme0n1,encoders:1
1,0,0,0,0,0,0,/dev/nvme1,/dev/nvme1n1,encoders:2
0,0,0,0,0,0,0,/dev/nvme0,/dev/nvme0n1,scalers:1
1,0,0,0,0,0,0,/dev/nvme1,/dev/nvme1n1,scalers:2
**注意:**使用Go编写解析器来处理您的输入格式应该不难。
英文:
I would convert your input file to CSV, because it suits the original tabular data and also because the Go language have a CSV encoder/decoder in its standard library:
awk -v OFS=',' '
$1 == "Num" {
count = $3
type = $2
getline
if ( !header++ ) {
$(NF+1) = "ID"
print
}
for ( id = 1; id <= count; id++ ) {
getline
$(NF+1) = type id
print
}
}
' file.txt
<sup>Warning: the code doesn't CSV-escape the fields</sup>
INDEX,LOAD,MODEL_LOAD,INST,MEM,SHARE_MEM,P2P_MEM,DEVICE,NAMESPACE,ID
1,2,3,4,50,600,700,/dev/nvme0,/dev/nvme0n1,decoders:1
1a,2b,3c,4c,5d,6e,7f,/dev/nvme1,/dev/nvme1n1,decoders:2
2a,2b,2c,3,0,0,0,/dev/nvme0,/dev/nvme0n1,encoders:1
1,0,0,0,0,0,0,/dev/nvme1,/dev/nvme1n1,encoders:2
0,0,0,0,0,0,0,/dev/nvme0,/dev/nvme0n1,scalers:1
1,0,0,0,0,0,0,/dev/nvme1,/dev/nvme1n1,scalers:2
N.B. Writing a parser in Go for your input format shouldn't be that difficult
答案2
得分: 2
如何直接使用Go语言处理你关心的文本呢?在Go语言中,你将比使用shell工具拥有更多的控制权。
下面是一个小型状态机,它会查找以"Num"开头的文本,以指示一个新的项目的开始。下一行是标题,会被跳过,接下来的行会被转换为一个行对象,并添加到该项目中。在项目之间的边界以及输入文本/文件的末尾,最后一个项目会被添加到所有项目的集合中。
package main
import (
"bufio"
"fmt"
"regexp"
"strings"
)
var txt = `
Num item1: 2
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
1 2 3 4 50 600 700 1 1
1a 2b 3c 4c 5d 6e 7f 2 2
Num item2: 2
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
2a 2b 2c 3 0 0 0 1 1
1 0 0 0 0 0 0 2 2
Num item3: 1
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
i iib iic iii zero zero zero i i
**************************************************
`
var columns = regexp.MustCompile(`\s+`)
type Row struct {
Index,
Load,
Model_Load,
Inst_Mem,
Share_Mem,
P2p_Mem,
Device,
Namespace string
}
type Item []Row
func main() {
r := strings.NewReader(txt)
scanner := bufio.NewScanner(r)
items := make([]Item, 0)
var item Item
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSpace(line)
if len(line) == 0 ||
strings.HasPrefix(line, "***") {
continue
}
// find beginning of an "item": if any previous item, save it and
// reset item to append future rows; skip header line; continue
if strings.HasPrefix(line, "Num item") {
if len(item) > 0 {
items = append(items, item)
item = make(Item, 0)
}
scanner.Scan() // skip header
continue
}
cols := columns.Split(line, -1)
row := Row{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]}
item = append(item, row)
}
// deal with last/trailing item
if len(item) > 0 {
items = append(items, item)
}
for i, item := range items {
fmt.Printf("Item %d\n", i+1)
for _, row := range item {
fmt.Println(row)
}
}
}
运行以上代码会输出以下结果:
Item 1
{1 2 3 4 50 600 700 1}
{1a 2b 3c 4c 5d 6e 7f 2}
Item 2
{2a 2b 2c 3 0 0 0 1}
{1 0 0 0 0 0 0 2}
Item 3
{i iib iic iii zero zero zero i}
我不知道有更好的方法来创建这个结构体,但它是直接的,而且相当清晰。
英文:
How about starting directly with the text you care about in Go? You'll have far more control in Go than you'll ever have with shell utilities.
This is a little state machine that looks for the leading text, "Num", to indicate the beginning of a new Item. The next line is the header, which is skipped, and following lines are converted to a Row, which are added to that Item. At the boundary between items and at the end of the input text/file, the last Item is added to the set of all Items.
package main
import (
"bufio"
"fmt"
"regexp"
"strings"
)
var txt = `
Num item1: 2
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
1 2 3 4 50 600 700 1 1
1a 2b 3c 4c 5d 6e 7f 2 2
Num item2: 2
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
2a 2b 2c 3 0 0 0 1 1
1 0 0 0 0 0 0 2 2
Num item3: 1
INDEX LOAD MODEL_LOAD INST MEM SHARE_MEM P2P_MEM DEVICE NAMESPACE
i iib iic iii zero zero zero i i
**************************************************
`
var columns = regexp.MustCompile(`\s+`)
type Row struct {
Index,
Load,
Model_Load,
Inst_Mem,
Share_Mem,
P2p_Mem,
Device,
Namespace string
}
type Item []Row
func main() {
r := strings.NewReader(txt)
scanner := bufio.NewScanner(r)
items := make([]Item, 0)
var item Item
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSpace(line)
if len(line) == 0 ||
strings.HasPrefix(line, "***") {
continue
}
// find beginning of an "item": if any previous item, save it and
// reset item to append future rows; skip header line; continue
if strings.HasPrefix(line, "Num item") {
if len(item) > 0 {
items = append(items, item)
item = make(Item, 0)
}
scanner.Scan() // skip header
continue
}
cols := columns.Split(line, -1)
row := Row{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]}
item = append(item, row)
}
// deal with last/trailing item
if len(item) > 0 {
items = append(items, item)
}
for i, item := range items {
fmt.Printf("Item %d\n", i+1)
for _, row := range item {
fmt.Println(row)
}
}
}
That prints the following:
Item 1
{1 2 3 4 50 600 700 1}
{1a 2b 3c 4c 5d 6e 7f 2}
Item 2
{2a 2b 2c 3 0 0 0 1}
{1 0 0 0 0 0 0 2}
Item 3
{i iib iic iii zero zero zero i}
I don't know of a better way to create the struct, but it is direct, and fairly clean.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论