2015-06-29 19 views
10

ho avuto questa comoda funzione in Python:Go: "tail -f" generatore -come

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 

Si fa qualcosa di simile a UNIX tail -f: si ottiene ultime righe di un file come vengono. È conveniente perché è possibile ottenere il generatore senza bloccare e passarlo a un'altra funzione.

Poi ho dovuto fare la stessa cosa in Go. Sono nuovo in questa lingua, quindi non sono sicuro se quello che ho fatto sia idiomatico/corretto abbastanza per Go.

Ecco il codice:

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 
} 

Esiste un modo più pulito per fare questo in Go? Ho la sensazione che usare una chiamata asincrona per una cosa del genere sia eccessivo, e mi infastidisce davvero.

+2

Usa 'os.SEEK_END' piuttosto che' 2'. 'time.Sleep (10)' dorme per 10 nanosecondi, probabilmente intendevi 'time.Sleep (10 * time.Millisecond)'. Una volta che un 'io.Reader' ti dà' io.EOF' non dovresti aspettarti che ti dia mai più dati; EOF == ** fine ** di file/vapore, non più dati. –

+0

Grazie, ho modificato il codice. – oopcode

risposta

7

Suggerisco di creare un involucro attorno a un lettore che dorme su 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 
} 

Questo lettore può essere utilizzato ovunque un io.Reader può essere utilizzato. Ecco come un ciclo su linee con 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) 
} 

Il lettore può essere utilizzato anche per un ciclo su valori JSON allegati al file:

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) 
} 

Ci sono un paio di vantaggi di questo approccio ha il goroutine approccio delineato nella domanda. Il primo è che l'arresto è facile. Chiudi il file. Non è necessario segnalare alla goroutine che dovrebbe uscire. Il secondo vantaggio è che molti pacchetti funzionano con io.Reader.

+0

Ci sono due cose che vedo essere un errore .... [1] il time.Sleep (10 * time.Millisecond) farà sì che il sistema generi calore invece di stare seduto in silenzio finché non ci sono effettivamente dei dati. [2] Quando sei alla fine del file non c'è segnale per il loop principale in modo che tu possa decidere di uscire. (Ho un caso d'uso in cui mando alcuni comandi a un servizio, quindi controllo i registri in attesa di una risposta positiva, poiché non c'è risposta negativa ho bisogno di esaurire i dati o controllare il timer.Goroutine e la chiusura dell'origine dati sono non un'opzione. – Richard

Problemi correlati