Go - from Google


Input/output di base     

nb: Windows e Linux hanno alcune differenze nella struttura dei filesi. In questo paragrafo darò solo degli accenni in attesa di pubblicare qualche cosa di più specifico in quali tali differenze non sono del tutto scevre di conseguenze in Go... Per limitare i problemi se userete come \n come limitatore delle righe molto problemi non si presenteranno. Se devo essere sincero in altri linguaggi ho trovato le cose un po' più semplici. La varietà di possibilità offerte mi ha reso difficile trovare un metodo espositivo valido. Ci riproverò. In questo paragrafo introduttivo mi limiterò a considerare i file di testo dando solo qualche base per poter lavorare.

Questo paragrafo è utile per avere le conoscenze minime per gestire l'I/O in questo linguaggio. La questione è evidentemente fondamentale ma è anche assai complessa se considerata nel suo insieme. Inizieremo quindi, come detto, dai fondamenti più semplici, per gli approfondimenti ci sarà un'apposita sezione. L'idea è quella di fornire strumenti pur se minimali, per lavorare a pieno regime.
Abbiamo già visto qualche esempio di lettura dell'input inserito via tastiera dall'utente. Vediamo passo passo come fare. Per prima cosa presentiamo la funzione di lettura dell'input. Il package fmt contiene varie funzioni per la gestione dell'I/O e tra di esse troviamo scan, scanf e scanln. La prima e l'ultima sono quelle più utili per la gestione dell'input da tastiera in quanto leggono gli argomenti separati da spazio memorizzandoli in argomenti successivi. In particolare scanln si arresta quando trova un newline, il carattere di "a capo" in pratica, che invece per scan equivale ad uno spazio.

  Esempio 14.1
1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
var (
nome string
cognome string
)
func main() {
fmt.Println("Inserisci il tuo nome e cognome ")
fmt.Scanln(&nome , &cognome)
fmt.Printf("Ciao, %s %s", nome, cognome)
}

anche Println e Printf fanno parte del package fmt il primo lo conosciamo, stampa e va a capo, il secondo stampa seguendo una formattazione. Printf restituisce anche il numero di byte stampati e un uventuale codice d'errore. Ad esempio si poteva modificare la riga 10 del 14.1 come segue:

fmt.Println(fmt.Printf("Ciao, %s %s", nome, cognome))

per ricavare le informazioni citate. Le operazioni di lettura e scrittura si basano in maniera silente sul package Os ed in particolare sulle due variabili in esse contenute Stdin (standard input) e Stdout (standard output). Da notare come gli argomenti di Scanln siano puntatori a variabili di tipo base. Provate a vedere cosa succede se togliete il simbolo & (tranquilli, niente di male, ovviamente....). Attraverso Scanln siamo in grado di accettare anche input numerici; il seguente esempio è del tutto analogo al precedente:

  Esempio 14.2
1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
var (
x1 int
x2 int
)
func main() {
fmt.Println("Inserisci due numeri ")
fmt.Scanln(&x1 , &x2)
fmt.Println(x1 + x2)
}

Come sottolineato le 3 funzioni citate leggono dallo standard input e sono pertanto estremamente comode. Ne esistono altre equivalenti col prefisso "F" ovvero Fscan, Fscanf, Fscanln che richiedono uno specifico io.Reader.

Una strada alternativa è quella di utilizzare una sorta di bufferizzazione. Per questo scopo si può usare il package, dal nome un po' particolare ma illuminante, bufio. Nella documentazione ufficiale sta scritto testualmente: Package bufio implements buffered I/O. Lo scopo è quello di aiutare alcuni tipi di elaborazione più complessi. Fedeli comunque alla impostazione di base di questo paragrafo vediamo un esempio semplice relativo all'uso:

  Esempio 14.3
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"fmt"
"bufio"
"os"
)

func main() {
inpReader := bufio.NewReader(os.Stdin)
fmt.Println("Inserisci il tuo nome: ")
input,_ := inpReader.ReadString('\n')
fmt.Println(input)
}

Alla riga 9 creiamo un "lettore" dell'input e questo andrà a lavorare sullo standard input.
Alla riga 11 invece utilizziamo la funzione ReadString che legge una stringa fino al delimitatore indicato restituendo la stringa stessa (input) o un errore (ad esempio un EOF) che qui non utilizziamo e quindi lo abbiamo rinchiuso dentro la variabile standard _ . Il delimitatore è il carattere di newline che viene inserito, come noto, premendo il tasto invio. Può essere interessante anche stampare inpreader del programma 14.3 direttamente per visualizzarne il contenuto. La forma vissta negli esempi 14.1 e 14.2 è senza dubbio più semplice per esigenze di base.

Leggere e scrivere su file

La lettura di un file di testo, di cui ci occuperemo qui, non presenta particolari difficoltà. Go è molto attento agli errori e ci permette facilmente di intercettare quelli più comuni, come vedremo nei semplici esempi che faremo. Il package necessario è os in quanto tutti i file, per assimilazione anche lo standard input e lo standard output, sono gestiti tramite esso. Di seguito il codice di un semplice programma che apre un file, di nome "b.txt", ne legge il contenuto e lo stampa a video:

  Esempio 14.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import (
"fmt"
"bufio"
"os"
"io"
)

func main() {
inpfile, inperr := os.Open("b.txt")
if inperr != nil {
fmt.Println("Errore aprendo il file")
return
}
defer inpfile.Close()
inpReader := bufio.NewReader(inpfile)
for {
  inpStr, readErr := inpReader.ReadString('\n')
  if readErr == io.EOF {
    return
  }
fmt.Printf(inpStr)
}
}

In fase di apertura, in buona filosofia Go, viene inserita la gestione di un errore generico di apertura del file. La riga 16 definisce un reader che usa inpfile anzichè lo standard input. Usare ReadString non è l'unico modo per leggere da file. Un altro sistema fa uso del potente package ioutil. Il comando per importarlo è includere il package io/ioutil. L'esempio seguente è molto semplice:

  Esempio 14.5
1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"io/ioutil"
)

func main() {
contents,_ := ioutil.ReadFile("a.txt")
println(string(contents))
fmt.Println(len(contents))
}

Un esempio interessante. Il fulcro è ovviamente la riga 9 in cui viene usata la funzione ReadFile del package ioutil. Questa funzione è definita internamente come segue:

func ReadFile(filename string) ([]byte, error)

ovvero legge una stringa e restituisce uno slice. Questo è livberamente gestibile come ogni altro slice e può risultare quindi molto utile.

Per quanto concerne invece la scrittura su file vediamo subito un esempio che vede protagonista la funzione più semplice, ovvero WriteFile:

func WriteFile(filename string, data []byte, perm os.FileMode) error

  Esempio 14.6
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"io/ioutil"
)

func main() {
contents,_ := ioutil.ReadFile("a.txt")
println(string(contents))
fmt.Println(len(contents))
ioutil.WriteFile("c.txt", contents, 0x644);
}

Eseguendo questo breve programma avremo un file di nome c.txtx copia di a.txt (attenzione a non sovrascrivere dei fil esistenti!. Bisogna sempre fare attenzione quando si opera sui propri file). Il principio è lo stesso dell'esempio precedente. Per quanto riguarda i permessi esistono varie guide facilmente accessibili via Web.

Per iniziare a lavorare, al di là di tutto consiglio di dare uno sguardo sul sito ufficiale alla libreria ioutil credo che vi semplificherà molto la vita almeno all'inizio e finchè npn saprete maneggiare anche altri package.