Làm thế nào để đọc / ghi từ / đến tập tin bằng cách sử dụng Go?


284

Tôi đã cố gắng tự học, nhưng tôi đã cố gắng đọc và ghi vào các tệp thông thường.

Tôi có thể hiểu được inFile, _ := os.Open(INFILE, 0, 0), nhưng thực sự việc lấy nội dung của tệp không có ý nghĩa gì, bởi vì chức năng đọc lấy []bytetham số.

func (file *File) Read(b []byte) (n int, err Error)

Câu trả lời:


476

Hãy tạo một danh sách tương thích với Go 1 về tất cả các cách để đọc và ghi tệp trong Go.

Bởi vì API tệp đã thay đổi gần đây và hầu hết các câu trả lời khác không hoạt động với Go 1. Họ cũng bỏ lỡ bufioIMHO quan trọng.

Trong các ví dụ sau tôi sao chép một tệp bằng cách đọc từ nó và ghi vào tệp đích.

Bắt đầu với những điều cơ bản

package main

import (
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := fi.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := fo.Write(buf[:n]); err != nil {
            panic(err)
        }
    }
}

Ở đây tôi đã sử dụng os.Openos.Createđó là các trình bao bọc thuận tiện xung quanh os.OpenFile. Chúng tôi thường không cần gọi OpenFiletrực tiếp.

Thông báo điều trị EOF. Readcố gắng điền bufvào mỗi cuộc gọi và trả về io.EOFlà lỗi nếu nó đến cuối tập tin khi thực hiện. Trong trường hợp bufnày vẫn sẽ giữ dữ liệu. Các cuộc gọi tiếp theo Readtrả về 0 là số byte được đọc và giống io.EOFnhư lỗi. Bất kỳ lỗi nào khác sẽ dẫn đến hoảng loạn.

Sử dụng bufio

package main

import (
    "bufio"
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()
    // make a read buffer
    r := bufio.NewReader(fi)

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()
    // make a write buffer
    w := bufio.NewWriter(fo)

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := r.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := w.Write(buf[:n]); err != nil {
            panic(err)
        }
    }

    if err = w.Flush(); err != nil {
        panic(err)
    }
}

bufiochỉ đóng vai trò là bộ đệm ở đây, vì chúng ta không liên quan nhiều đến dữ liệu. Trong hầu hết các tình huống khác (đặc biệt với các tệp văn bản) bufiorất hữu ích bằng cách cung cấp cho chúng tôi một API đẹp để đọc và viết dễ dàng và linh hoạt, trong khi nó xử lý bộ đệm phía sau hậu trường.

Sử dụng ioutil

package main

import (
    "io/ioutil"
)

func main() {
    // read the whole file at once
    b, err := ioutil.ReadFile("input.txt")
    if err != nil {
        panic(err)
    }

    // write the whole body at once
    err = ioutil.WriteFile("output.txt", b, 0644)
    if err != nil {
        panic(err)
    }
}

Dễ như ăn bánh! Nhưng chỉ sử dụng nó nếu bạn chắc chắn rằng bạn không xử lý các tệp lớn.


55
Đối với bất kỳ ai vấp phải câu hỏi này, ban đầu nó đã được hỏi vào năm 2009 trước khi các thư viện này được giới thiệu, vì vậy xin vui lòng, sử dụng câu trả lời này làm hướng dẫn của bạn!
Seth Hoenig

1
Theo golang.org/pkg/os/#File.Write , khi Write không ghi tất cả các byte, nó sẽ trả về một lỗi. Vì vậy, kiểm tra thêm trong ví dụ đầu tiên ( panic("error in writing")) là không cần thiết.
ayke

15
Lưu ý rằng những ví dụ này không kiểm tra trả về lỗi từ fo.Close (). Từ trang Linux man close (2): Không kiểm tra giá trị trả về của close () là một lỗi lập trình phổ biến nhưng vẫn nghiêm trọng. Hoàn toàn có khả năng các lỗi trong thao tác ghi (2) trước đó được báo cáo đầu tiên ở lần đóng cuối cùng (). Không kiểm tra giá trị trả về khi đóng tệp có thể dẫn đến mất dữ liệu trong im lặng. Điều này đặc biệt có thể được quan sát với NFS và với dung lượng đĩa.
Nick Craig-Wood

12
Vì vậy, một tập tin "lớn" là gì? 1KB? 1 MB? 1GB? Hay "lớn" phụ thuộc vào phần cứng của máy?
425nesp

3
@ 425nesp Nó đọc toàn bộ tập tin vào bộ nhớ, vì vậy nó phụ thuộc vào dung lượng bộ nhớ khả dụng trong máy đang chạy.
Mostafa

49

Đây là phiên bản tốt:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}

8
Điều này lưu trữ toàn bộ tập tin trong bộ nhớ. Vì tệp có thể lớn, nên đó có thể không phải là điều bạn muốn làm.
dùng7610

9
Ngoài ra, 0x777là không có thật. Trong mọi trường hợp, nó nên giống 0644hoặc 0755(bát phân, không phải hex).
cnst

@cnst đổi nó thành 0644 từ 0x777
Trenton

31

Sử dụng io.Copy

package main

import (
    "io"
    "log"
    "os"
)

func main () {
    // open files r and w
    r, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    defer r.Close()

    w, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    defer w.Close()

    // do the actual work
    n, err := io.Copy(w, r)
    if err != nil {
        panic(err)
    }
    log.Printf("Copied %v bytes\n", n)
}

Nếu bạn không muốn phát minh lại bánh xe, io.Copyio.CopyNcó thể phục vụ tốt cho bạn. Nếu bạn kiểm tra nguồn của hàm io.Copy, thì đó không phải là một trong những giải pháp của Mostafa (thực sự là 'cơ bản') được đóng gói trong thư viện Go. Họ đang sử dụng một bộ đệm lớn hơn đáng kể so với anh ta, mặc dù.


5
một điều đáng nói - để chắc chắn rằng nội dung của tệp đã được ghi vào đĩa, bạn cần sử dụng w.Sync()sauio.Copy(w, r)
Shay Tsadok

Ngoài ra, nếu bạn ghi vào tệp đã có, io.Copy()sẽ chỉ ghi dữ liệu bạn cung cấp cho nó, vì vậy nếu tệp hiện có nhiều nội dung hơn, nó sẽ không bị xóa, điều này có thể dẫn đến tệp bị hỏng.
Invidian

1
@Invidian Tất cả phụ thuộc vào cách bạn mở tệp đích. Nếu bạn làm như vậy w, err := os.Create("output.txt"), những gì bạn mô tả sẽ không xảy ra, bởi vì "Tạo tạo hoặc cắt bớt tệp đã đặt tên. Nếu tệp đã tồn tại, nó sẽ bị cắt ngắn." golang.org/pkg/os/#Create .
user7610

Đây phải là câu trả lời chính xác vì nó không phát minh lại bánh xe trong khi không phải đọc toàn bộ tệp cùng một lúc trước khi đọc nó.
Eli Davis

11

Với các phiên bản Go mới hơn, việc đọc / ghi vào / từ tệp rất dễ dàng. Để đọc từ một tập tin:

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    data, err := ioutil.ReadFile("text.txt")
    if err != nil {
        return
    }
    fmt.Println(string(data))
}

Để ghi vào một tập tin:

package main

import "os"

func main() {
    file, err := os.Create("text.txt")
    if err != nil {
        return
    }
    defer file.Close()

    file.WriteString("test\nhello")
}

Điều này sẽ ghi đè lên nội dung của một tệp (tạo một tệp mới nếu nó không có ở đó).


10

[]bytelà một lát (tương tự như một chuỗi con) của tất cả hoặc một phần của mảng byte. Hãy nghĩ về lát cắt như một cấu trúc giá trị với trường con trỏ ẩn để hệ thống định vị và truy cập tất cả hoặc một phần của mảng (lát cắt), cộng với các trường về độ dài và dung lượng của lát cắt mà bạn có thể truy cập bằng cách sử dụng len()cap()các hàm .

Đây là một bộ khởi động làm việc cho bạn, nó đọc và in một tệp nhị phân; bạn sẽ cần thay đổi inNamegiá trị bằng chữ để chỉ một tệp nhỏ trên hệ thống của bạn.

package main
import (
    "fmt";
    "os";
)
func main()
{
    inName := "file-rw.bin";
    inPerm :=  0666;
    inFile, inErr := os.Open(inName, os.O_RDONLY, inPerm);
    if inErr == nil {
        inBufLen := 16;
        inBuf := make([]byte, inBufLen);
        n, inErr := inFile.Read(inBuf);
        for inErr == nil {
            fmt.Println(n, inBuf[0:n]);
            n, inErr = inFile.Read(inBuf);
        }
    }
    inErr = inFile.Close();
}

9
Quy ước Go là để kiểm tra lỗi đầu tiên, và để cho bình thường đang cư trú bên ngoài ifkhối
Hasen

@Jurily: Nếu tệp bị mở khi xảy ra lỗi, làm thế nào để bạn đóng nó?
peterSO

10
@peterSO: sử dụng defer
James Antill

Nhưng tại sao một byte [256] không được chấp nhận và rõ ràng là ngớ ngẩn và dài dòng (nhưng dường như không sai) inBuf: = make ([] byte, 256) được chấp nhận?
người đàn ông không gian Cardiff

7

Thử cái này:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}

1
Điều này sẽ hoạt động nếu bạn muốn đọc toàn bộ tập tin cùng một lúc. Nếu tệp thực sự lớn hoặc bạn chỉ muốn đọc một phần của nó, nó có thể không phải là thứ bạn đang tìm kiếm.
Evan Shaw

3
Bạn thực sự nên kiểm tra mã lỗi, và không bỏ qua nó như thế !!
hasen

7
Điều này đã được chuyển vào gói ioutil ngay bây giờ. Vì vậy, nó sẽ là ioutil.ReadFile ()
Christopher

Tôi đã sửa nên nó báo 0644
Joakim

1

Chỉ cần nhìn vào tài liệu, có vẻ như bạn chỉ cần khai báo bộ đệm loại [] byte và chuyển nó để đọc, sau đó sẽ đọc đến nhiều ký tự đó và trả về số lượng ký tự thực sự đã đọc (và một lỗi).

Các tài liệu nói

Đọc đọc tối đa len (b) byte từ Tệp. Nó trả về số byte đã đọc và một Lỗi, nếu có. EOF được báo hiệu bằng số không có lỗi được đặt thành EOF.

Điều đó không làm việc?

EDIT: Ngoài ra, tôi nghĩ rằng có lẽ bạn nên sử dụng giao diện Reader / Writer được khai báo trong gói bufio thay vì sử dụng gói os .


Bạn có phiếu bầu của tôi bởi vì bạn thực sự thừa nhận những gì người thực nhìn thấy khi họ đọc tài liệu, thay vì ghi lại những gì những người quen đi là NHẮC LẠI (không đọc NHỚ) khi họ đọc tài liệu về chức năng mà họ đã quen thuộc.
người đàn ông không gian Cardiff

1

Phương thức Read lấy tham số byte vì đó là bộ đệm mà nó sẽ đọc vào. Đó là một Thành ngữ phổ biến trong một số vòng tròn và có ý nghĩa khi bạn nghĩ về nó.

Bằng cách này, bạn có thể xác định có bao nhiêu byte sẽ được người đọc đọc và kiểm tra sự trở lại để xem có bao nhiêu byte thực sự được đọc và xử lý bất kỳ lỗi nào một cách thích hợp.

Như những người khác đã chỉ ra trong câu trả lời của họ, bufio có lẽ là những gì bạn muốn đọc từ hầu hết các tập tin.

Tôi sẽ thêm một gợi ý khác vì nó thực sự hữu ích. Đọc một dòng từ một tệp được hoàn thành tốt nhất không phải bằng phương pháp ReadLine mà là phương thức ReadBytes hoặc ReadString.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.