2017-02-13 6 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 
} 

私の出力は次のとおりです:

ここ

は(ORIGコードは、より多くの処理を行い、これは簡易版ですが、基本的にフィルタリングは、入力が入ってくるようではない最後に、起こることがあります)私の試みです

stream: 
a 
b 

progress 100% 

c 

streamer: 
a 
b 

progress 98% 
progress 100% 

c 


fromCmd: 
a 
b 

ss: 100% 
progess: 100% 

c 

「fromCmd」から出力される出力は、「ストリーマー」からの出力と一致する必要があります。

実際の出力が「壊れている」ように見えるのはどうして間違っているのですか?実際のコマンド実行は「ストリーマー」テストとは異なる動作をし、STDOUTをフィルタする良い方法は何ですか?

答えて

2

部分線アルゴリズムがすべての入力に対して正しくない。

あなたは出力を蓄積するために、あなたのために正しく部分のラインバッファリングを処理しますbufio.Scanner、および[]byteまたはbytes.BuffermyFilterを置き換えることができます。

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:私が言ったのは、スキャナーを使って、 'myFilter'の_instead_をバッファすることです。これには行全体をバッファリングする必要がありますが、実装には同じ制限があります。 – JimB

関連する問題