2017-02-13 21 views
1

我想從命令中過濾STDOUT,這樣我只保留\ r終止行的任何連續塊的第一行和最後一行(很大程度上忽略進度指示器)。在Golang中過濾字節流的正確方法?

這是我嘗試(原稿代碼做多,這是一個簡化版本,但基本的濾波具有發生作爲輸入進來的,而不是在結尾):

package main 

import (
    "bytes" 
    "fmt" 
    "os/exec" 
) 

var cr = []byte("\r") 
var lf = []byte("\n") 

func main() { 
    input1 := []byte("a\nb\n\nprogress 98%\r") 
    input2 := []byte("progress 99%\r") 
    input3 := []byte("progress 100%\r") 
    input4 := []byte("\n\nc\n") 

    var stream []byte 
    stream = append(stream, input1...) 
    stream = append(stream, input2...) 
    stream = append(stream, input3...) 
    stream = append(stream, input4...) 

    fmt.Printf("stream:\n%s\n", stream) 

    streamer := &myFilter{} 
    streamer.Write(input1) 
    streamer.Write(input2) 
    streamer.Write(input3) 
    streamer.Write(input4) 
    final := streamer.Bytes() 

    fmt.Printf("streamer:\n%s\n\n", final) 

    cmd := exec.Command("bash", "-c", "perl -e '$|++; print qq[a\nb\n\nprogress: 98%\r]; for (99..100) { print qq[progess: $_%\r]; sleep(1); } print qq[\n\nc\n]'") 
    cmd.Stdout = &myFilter{} 
    cmd.Start() 
    cmd.Wait() 
    fromCmd := cmd.Stdout.(*myFilter).Bytes() 

    fmt.Printf("fromCmd:\n%s\n", fromCmd) 
} 

type myFilter struct { 
    partialLine []byte 
    storage  []byte 
} 

func (w *myFilter) Write(p []byte) (n int, err error) { 
    // in order to filter out all but the first and last line of a set of \r 
    // terminated lines (a progress bar), we need to collect whole \n terminated 
    // lines 
    lines := bytes.SplitAfter(p, lf) 

    if len(w.partialLine) > 0 || (len(lines) == 1 && !bytes.HasSuffix(p, lf)) { 
     w.partialLine = append(w.partialLine, lines[0]...) 

     partialComplete := false 
     if len(lines) > 1 { 
      lines = lines[1:] 
      partialComplete = true 

     } else { 
      lines = nil 
      if bytes.HasSuffix(p, lf) { 
       partialComplete = true 
      } 
     } 

     if partialComplete { 
      w.filterCR(w.partialLine) 
      w.partialLine = nil 
     } 
    } 

    lastLineIndex := len(lines) - 1 
    if lastLineIndex > -1 && !bytes.HasSuffix(p, lf) { 
     w.partialLine, lines = lines[lastLineIndex], lines[:lastLineIndex] 
    } 

    for _, line := range lines { 
     w.filterCR(line) 
    } 

    return len(p), nil 
} 

func (w *myFilter) filterCR(p []byte) { 
    if bytes.Contains(p, cr) { 
     lines := bytes.Split(p, cr) 
     w.store(lines[0]) 
     w.store(lf) 

     if len(lines) > 2 { 
      w.store(lines[len(lines)-2]) 
      w.store(lf) 
     } 
    } else { 
     w.store(p) 
    } 
} 

func (w *myFilter) store(p []byte) { 
    w.storage = append(w.storage, p...) 
} 

func (w *myFilter) Bytes() []byte { 
    if len(w.partialLine) > 0 { 
     w.filterCR(w.partialLine) 
    } 
    return w.storage 
} 

我的輸出是:

stream: 
a 
b 

progress 100% 

c 

streamer: 
a 
b 

progress 98% 
progress 100% 

c 


fromCmd: 
a 
b 

ss: 100% 
progess: 100% 

c 

我想要的是從「fromCmd」看到的輸出與我從「streamer」得到的輸出相匹配。

我在做什麼錯,爲什麼我的實際輸出看起來「損壞」,爲什麼真正的命令運行的行爲與我的「流測試器」測試不同,以及什麼是更好的方法來過濾STDOUT?

回答

2

您的部分線算法對所有輸入都不正確。

您可以用bufio.Scanner,將正確處理局部行緩衝對你來說,和[]bytebytes.Buffer更換myFilter累積輸出。

var out bytes.Buffer 
scanner := bufio.NewScanner(stdout) 
for scanner.Scan() { 
    p := scanner.Bytes() 
    lines := bytes.Split(p, cr) 
    out.Write(lines[0]) 
    out.Write(lf) 
    if len(lines) > 1 { 
     out.Write(lines[len(lines)-1]) 
     out.Write(lf) 
    } 
} 
+0

謝謝。我試圖做這個過濾器作爲(我的副本)os/exec的prefixSuffixSaver的一部分,我不太清楚如何在這種情況下使用你的代碼。 – sbs

+0

@sbs:我的意思是你使用掃描器和緩衝器_instead_的'myFilter'。這需要緩衝整條生產線,但您的實施具有相同的限制。 – JimB

相關問題