2015-06-29 5 views
10

Я имел эту удобную функцию в Python:Go: «хвост -f» -как генератор

def follow(path): 
    with open(self.path) as lines: 
     lines.seek(0, 2) # seek to EOF 

     while True: 
      line = lines.readline() 
      if not line: 
       time.sleep(0.1) 
        continue 
       yield line 

Это делает что-то похожее на UNIX tail -f: вы получите последние строки файла, как они приходят. Это удобно, потому что вы можете получить генератор без блокировки и передать его другой функции.

Тогда я должен был сделать то же самое в Go. Я новичок в этом языке, поэтому я не уверен, что то, что я сделал, является идиоматическим/правильным для Go.

Вот код:

func Follow(fileName string) chan string { 

    out_chan := make(chan string) 

    file, err := os.Open(fileName) 
    if err != nil { 
     log.Fatal(err) 
    } 

    file.Seek(0, os.SEEK_END) 
    bf := bufio.NewReader(file) 

    go func() { 
     for { 
      line, _, _ := bf.ReadLine() 

      if len(line) == 0 { 
       time.Sleep(10 * time.Millisecond) 
      } else { 
       out_chan <- string(line) 
      } 
     } 

     defer file.Close() 
     close(out_chan) 
    }() 

    return out_chan 
} 

Есть ли уборщик способ сделать это в Go? У меня такое чувство, что использование асинхронного вызова для такой вещи является излишним, и это действительно беспокоит меня.

+2

Используйте 'os.SEEK_END', а не' 2'. 'time.Sleep (10)' спит в течение 10 наносекунд, вы, вероятно, имели в виду 'time.Sleep (10 * time.Millisecond)'. Когда 'io.Reader' дает вам' io.EOF', вы не должны ожидать, что он когда-либо даст вам больше данных; EOF == ** end ** файла/пара, больше данных. –

+0

Спасибо, я редактировал код. – oopcode

ответ

7

Я предлагаю создать обертку вокруг читателя, который спит на EOF:

type tailReader struct { 
    io.ReadCloser 
} 

func (t tailReader) Read(b []byte) (int, error) { 
    for { 
     n, err := t.ReadCloser.Read(b) 
     if n > 0 
      return n, nil 
     } else if err != io.EOF { 
      return n, err 
     } 
     time.Sleep(10 * time.Millisecond) 
    } 
} 

func newTailReader(fileName string) (tailReader, error) { 
    f, err := os.Open(fileName) 
    if err != nil { 
     return tailReader{}, err 
    } 

    if _, err := f.Seek(0, 2); err != nil { 
     return tailReader{}, err 
    } 
    return tailReader{f}, nil 
} 

Этот читатель может быть использован везде, где io.Reader можно использовать. Вот как цикл по линиям с использованием bufio.Scanner:

t, err := newTailReader("somefile") 
if err != nil { 
    log.Fatal(err) 
} 
defer t.Close() 
scanner := bufio.NewScanner(t) 
for scanner.Scan() { 
    fmt.Println(scanner.Text()) 
} 
if err := scanner.Err(); err != nil { 
    fmt.Fprintln(os.Stderr, "reading:", err) 
} 

Читатель также может быть использован для перебора значений JSON прилагаемых к файлу:

t, err := newTailReader("somefile") 
if err != nil { 
    log.Fatal(err) 
} 
defer t.Close() 
dec := json.NewDecoder(t) 
for { 
    var v SomeType 
    if err := dec.Decode(&v); err != nil { 
     log.Fatal(err) 
    } 
    fmt.Println("the value is ", v) 
} 

Есть несколько преимуществ такого подхода есть над goroutine подхода, изложенного в вопросе. Во-первых, это простое завершение работы. Просто закройте файл. Нет необходимости сигнализировать горуту, что он должен выйти. Второе преимущество заключается в том, что многие пакеты работают с io.Reader.

+0

Есть две вещи, которые я вижу, это ошибка .... [1] time.Sleep (10 * time.Millisecond) заставит систему генерировать тепло вместо того, чтобы спокойно сидеть, пока на самом деле не будут какие-то данные. [2] Когда вы находитесь в конце файла, в основной цикл отсутствует сигнал, чтобы вы могли выйти. (У меня есть прецедент, когда я отправляю некоторые команды службе, а затем отслеживаю журналы, ожидающие положительного ответа, так как нет отрицательного ответа, мне нужно исчерпать данные или проверить таймер. Горуты и закрытие источника данных не вариант. – Richard

Смежные вопросы