본래 Go는 시스템 프로그래밍 언어의 아쉬운 점을 해결하기 위해 개발되었다.
유닉스 프로세스
프로세스란 일종의 실행 환경ㅇ로서, 명령어, 사용자 데이터, 시스템 데이터, 그리고 실행 시간 동안 얻게 되는 다양한 종류의 리소스로 구성된다. 프로그램은 프로세스에 초기화할 명령와 사용자 데이터로 구성된 바이너리 파일이다.
프로세스는 크게 세 종류로 나눌수 있다. 유저 프로세스는 user space에서 실행되며 특수한 접근 구너한을 가지지 않은 경우가 대부분이다. 커널 프로세스는 kernel space에서만 실행되며, 모든 커널 데이터 구조체에 대한 완전한 접근 권한을 가지고 있다. 데몬 프로세스는 사용자 영역에서 실행되는 프로그램으로, 터미널과 상화 작용하지 않고 백그라운드에서 구동된다.
flag 패키지
플래그란 프로그램의 동작을 제어하기 위해 전달하는 옵션으로, 특정한 포맷의 스트링으로 표현한다. flag 패키지는 커맨드라인 인수나 옵션의 순서를 따로 구분하지 않고, 커맨드라인 옵션을 처리하는 과정에서 에러가 발생하면 이에 대한 정보를 화면에 출력해주느 기능을 제공한다.
package main
import (
"flag"
"fmt"
)
func main() {
minsuK := flag.Bool("k", true, "k")
minsuO := flag.Int("O", 1, "O")
flag.Parse()
valueK := *minsuK
valueO := *minsuO
valueO++
fmt.Println("-k:", valueK)
fmt.Println("-O:", valueO)
}
$ go run foo.go -O 100
-k: true
-O: 101
flag.Bool("k", true, "k")라는 문장은 k라는 이름의 불리언 타입의 커맨드 라인 옵션을 정의한다. 디폴트는 true로 지정한다. 커맨드 라인 옵션을 저의한 뒤에는 반드시 flag.Parse()를 호출해야 한다.
package main
import (
"flag"
"fmt"
"strings"
)
type NamesFlag struct {
Names []string
}
func (s *NamesFlag) GetNames() []string {
return s.Names
}
func (s *NamesFlag) String() string {
return fmt.Sprint(s.Names)
}
func (s *NamesFlag) Set(v string) error {
if len(s.Names) > 0 {
return fmt.Errorf("Cannot use names flag more than once!")
}
names := strings.Split(v, ",")
for _, item := range names {
s.Names = append(s.Names, item)
}
return nil
}
func main() {
var manyNames NamesFlag
minusK := flag.Int("k", 0, "An int")
minusO := flag.String("o", "Mihalis", "The nae")
flag.Var(&manyNames, "names", "comma-separated list")
flag.Parse()
fmt.Println("-k", *minusK)
fmt.Println("-o", *minusO)
for i, item := range manyNames.GetNames() {
fmt.Println(i, item)
}
fmt.Println()
for index, val := range flag.Args() {
fmt.Println(index, ":", val)
}
}
$ go run foo.go -names=Mihalis,Jim,Athina 1 two Three
-k 0
-o Mihalis
0 Mihalis
1 Jim
2 Athina
0 : 1
1 : two
2 : Three
버퍼를 이용하는 파일 입출력과 버퍼를 이용하지 않는 파일 입출력
버퍼를 이용하는 파일 입력과 출력은 데이터를 읽거나 쓰기 전에 잠시 버퍼에 저장하는 방식이다. 따라서 파일을 한 바이트 단위로 읽지 않고, 한 번에 여러 바이트를 읽을 수 있다. 다시 말해 데이터를 버퍼에 저장후, 각자 원하는 방식으로 읽는 것이다.
그렇다면 파일을 읽거나 쓸 때, 언제 버퍼를 사용해야 하고, 또 어떤 경우에 버퍼를 쓰지 말아야 할까? 중요한 데이터를 다룰 때는 일반적으로는 버퍼를 사용하지 않는 파일 입출력을 사용하느 ㄴ편이 좋다.
텍스트 파일 일기
텍스트 파일 한 줄씩 읽기
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
)
func lineByLine(file string) error {
var err error
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
r := bufio.NewReader(f)
for {
line, err := r.ReadString('\n')
if err == io.EOF {
break
} else if err != nil {
fmt.Printf("error reading file %s", err)
break
}
fmt.Print(line)
}
return nil
}
func main() {
flag.Parse()
for _, file := range flag.Args() {
lineByLine(file)
}
}
읽을 파일을 열었다면 bufio.NewReader()로 리더를 새로 생성한다. 이 리더를 이용해 bufio.ReadString()을 호출해서 입력 파일을 한 줄씩 읽는다. 여기서 매개변수가 \n인데 줄바꿈이 일어날때까지 파일을 읽는것이다.
텍스트 파일을 한 단어씩 읽기
func wordByWord(file string) error {
var err error
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
r := bufio.NewReader(f)
for {
line, err := r.ReadString('\n')
if err == io.EOF {
break
} else if err != nil {
fmt.Printf("error reading file %s", err)
break
}
r := regexp.MustCompile("[^\\s]+")
words := r.FindAllString(line, -1)
for i := 0; i < len(words); i++ {
fmt.Println(words[i])
}
}
return nil
}
정규 표현식을 이용해 입력된 각 줄을 단어 단위로 구분한다.
텍스트 파일을 한 문자씩 읽기
func charByChar(file string) error {
var err error
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
r := bufio.NewReader(f)
for {
line, err := r.ReadString('\n')
if err == io.EOF {
break
} else if err != nil {
fmt.Printf("error reading file %s", err)
break
}
for _, x := range line {
fmt.Println(string((x)))
}
}
return nil
}
/dev/random 읽기
/dev/random이란 시스템 디바이스로부터 데이터를 읽는 방법을 소개한다. 이 시스템 디바이스의 목적은 랜덤 데이터를 생성하는 것이다.
package main
import (
"encoding/binary"
"fmt"
"os"
)
func main() {
f, err := os.Open("/dev/random")
defer f.Close()
if err != nil {
fmt.Println(err)
return
}
var seed int64
binary.Read(f, binary.LittleEndian, &seed)
fmt.Println("Seed:", seed)
}
$ go run foo.go
Seed: -4292657771210943746
파일에서 읽고 싶은 만큼 읽기
이 기법은 특별한 방식으로 인코딩한 바이너리 파일을 읽을 때 유용하다.
원하는 양만큼 바이트 슬라이스를 생성한 후 읽으면 된다.
package main
import (
"fmt"
"io"
"os"
"strconv"
)
func readSize(f *os.File, size int) []byte {
buffer := make([]byte, size)
n, err := f.Read(buffer)
if err == io.EOF {
return nil
}
if err != nil {
fmt.Println(err)
return nil
}
return buffer[0:n]
}
func main() {
bufferSize, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println(err)
return
}
file := os.Args[2]
f, err := os.Open(file)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
for {
readData := readSize(f, bufferSize)
if readData != nil {
fmt.Print(string(readData))
} else {
break
}
}
}
io.Reader.Read() 메소드는 두 개의 변수를 리턴한다. 하나는 바이트 수고, 다른 하나는 error변수다. readSize()함수는 io.Read의 첫번째 리턴 값을 잉용해 그 크기만큼 가진 바이트 슬라이스를 리턴한다.
바이너리 포맷을 사용하는 이유
가장 큰 이유는 공간 문제 때문이다. 예를 들어 숫자 20을 스트링 포맷으로 파일에 저장한다면 20이라는 숫자를 아스키 문자로 표현할 때 2를 위해 한 바이트 0을 위해 또 한 바이트를 차지해 총 두 바이트가 차지한다. 반면 20이라는 숫자를 바이너리 포맷으로 저장하면 단 한 바이트면 충분하다.
파일 쓰기
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
)
func main() {
s := []byte("Data to write\n")
f1, err := os.Create("f1.txt")
if err != nil {
fmt.Println("Can not make file")
return
}
defer f1.Close()
fmt.Fprintf(f1, string(s))
f2, err := os.Create("f2.txt")
if err != nil {
fmt.Println("Can not make file")
return
}
defer f2.Close()
n, err := fmt.Fprintf(f2, string(s))
fmt.Printf("Wrote %d bytes\n", n)
f3, err := os.Create("f3.txt")
if err != nil {
fmt.Println(err)
return
}
w := bufio.NewWriter(f3)
n, err = w.WriteString(string(s))
w.Flush()
f4 := "f4.txt"
err = ioutil.WriteFile(f4, s, 0644)
f5, err := os.Create("f5.txt")
n, err = io.WriteString(f5, string(s))
}
- fmt.Fprintf() 함수는 지정한 데이터를 f1으로 표현한 파일에 쓴다
- f2.WriteString()으로 데이터를 파일에 쓴다
- bufio.NewWriter()로 파일을 열고, bufio.WriteString()으로 데이터를 쓴다
- ioutil.Write() 파일 하나로 데이터를 쓴다
- io.WriteString()을 이용해 원하는 데이터를 파일에 쓴다
bytes 패키지
package main
import (
"bytes"
"fmt"
"io"
"os"
)
func main() {
var buffer bytes.Buffer
buffer.Write([]byte("This is"))
fmt.Fprintf(&buffer, " a string!\n")
buffer.WriteTo(os.Stdout)
buffer.WriteTo(os.Stdout)
buffer.Reset()
buffer.Write([]byte("Mastering Go!"))
r := bytes.NewReader([]byte(buffer.String()))
fmt.Println(buffer.String())
for {
b := make([]byte, 3)
n, err := r.Read(b)
if err == io.EOF {
break
}
if err != nil {
fmt.Println(err)
continue
}
fmt.Printf("Read %s Bytes : %d\n", b, n)
}
}
$ go run foo.go
This is a string!
Mastering Go!
Read Mas Bytes : 3
Read ter Bytes : 3
Read ing Bytes : 3
Read Go Bytes : 3
Read ! Bytes : 1
먼저 bytes.buffer 변수를 하나 생성 한후, 그리고 buff.Writer()와 fmt.Fprintf()를 이용해 해당 변수에 데이터를 집어 넣는다. 그런 다음 buffer.WriteTo() 함수를 두 번 호출한다.
'Go 언어 공부' 카테고리의 다른 글
[GO 마스터하기] 09-동시성2 (0) | 2020.09.22 |
---|---|
[GO 마스터하기] 09-동시성 1 (0) | 2020.09.21 |
[GO 마스터하기] 07-리플렉션과 인터페이스 (0) | 2020.09.21 |
[GO 마스터하기] 06-Go Package (0) | 2020.09.21 |
[GO 마스터하기] 05-Go 자료구조 (0) | 2020.09.16 |