flag
Go 의 flag
패키지는 CLI(Command Line Interface) 어플리케이션을 작성하기 위해 사용하는 내장 패키지이다. 주로 서브 커맨드가 없는 싱글 커맨드에 대해 옵션을 지정하여 사용할 때 사용한다. 가장 간단한 사용법은 아래와 같다. 예를 들어 사용자에게 --port, --p
옵션을 통해 포트를 지정할 수 있는 옵션을 지정하여 값을 받는다고 생각해보자.
package main
import (
"flag"
)
func main() {
var port int
flag.IntVar(&port, "port", 3000, "")
flag.IntVar(&port, "p", 3000, "")
flag.Parse()
}
어플리케이션의 코드가 main.go 라고 가정해보면 다음과 같이 사용할 수 있다.
$ go run main.go --port 3000
또한 help 메시지를 자동으로 만들어주고 있어서 다음과 같이 확인할 수 있다.
$ go run main.go --help
Usage of main.exe:
-p int
(default 3000)
-port int
(default 3000)
flag
패키지를 이렇게 사용해도 큰 문제는 없긴하지만 조금 더 내부의 코드를 들여다보고 이를 사용하면 조금 더 유연하게 사용해볼 수 있는데, 한 번 알아보도록 하자.
IntVar()
우리가 int
타입의 옵션을 지정할 때 사용한 함수인 flag.IntVar()
의 구현을 보자.
// IntVar defines an int flag with specified name, default value, and usage string.
// The argument p points to an int variable in which to store the value of the flag.
func IntVar(p *int, name string, value int, usage string) {
CommandLine.Var(newIntValue(value, p), name, usage)
}
구현을 보면, CommandLine.Var()
메서드를 호출하고 있는 모습을 볼 수 있는데, CommandLine
은 사실 FlagSet
타입을 할당한 변수이며 오류가 나면 에러를 발생시키고 프로그램을 종료하게된다. NewFlagSet()
은 외부에 노출되어 있는 함수이기 때문에 외부에서도 호출할 수 있다는 것을 기억하자.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
FlagSet.Var()
CommandLine.Var()
메서드는 FlagSet.Var()
와 같다는 결론을 낼 수 있으며 구현은 아래와 같은데, Flag
를 생성하고 등록하는 모습을 볼 수 있다. 물론 우리가 직접 Flag
타입을 사용하지는 않을 것이고, FlagSet.Var()
메서드를 호출하기만 할 것이다. 중요한 것은 첫 번째 파라매터에 있는 Value
타입이다.
// Var defines a flag with the specified name and usage string. The type and
// value of the flag are represented by the first argument, of type Value, which
// typically holds a user-defined implementation of Value. For instance, the
// caller could create a flag that turns a comma-separated string into a slice
// of strings by giving the slice the methods of Value; in particular, Set would
// decompose the comma-separated string into the slice.
func (f *FlagSet) Var(value Value, name string, usage string) {
// Remember the default value as a string; it won't change.
flag := &Flag{name, usage, value, value.String()}
// ...
if f.formal == nil {
f.formal = make(map[string]*Flag)
}
f.formal[name] = flag
}
Value
제목에 서술했듯이 Value
타입은 사실 인터페이스다. 따라서 인터페이스만 충족하기만 하면 FlagSet.Var()
를 통해 사용할 수 있음을 의미한다.
type Value interface {
String() string
Set(string) error
}
나만의 플래그 타입 만들기
이제 커스텀 플래그 타입을 만들어보고 이를 적용시켜보자. 먼저 MyFlag
타입을 만든 뒤, Value
인터페이스에 부합하도록 String(), Set()
메서드를 만들어주어야 한다.
type MyFlag string
func (f *MyFlag) String() string {
return string(*f)
}
func (f *MyFlag) Set(s string) error {
*f = MyFlag(s)
return nil
}
func NewMyFlagValue(val string, p *string) *MyFlag {
*p = val
return (*MyFlag)(p)
}
IntVar()
에서 보았듯이, NewIntValue()
를 사용한 것처럼 NewMyFlagValue()
함수를 사용해보았다. 이는 필수는 아니지만, 비슷하게 따라해본 것이다. 중요한 것은 인터페이스를 충족하기 위한 두 메서드이며 아래와 같이 사용할 수 있다.
func main() {
flagSet := flag.NewFlagSet("main", flag.ExitOnError)
var message string
flagSet.Var(NewMyFlagValue("Hello, world!", &message), "myFlag", "")
if err := flagSet.Parse(os.Args[1:]); err != nil {
log.Fatal(err)
}
}
Hello, world
에 해당하는 값은 플래그의 기본 값이며, 만약 아래와 같이 입력했다면 message
변수에는 Hello, Go!
가 입력될 것이다.
go run main.go --myFlag Hello, Go!
써드파티 라이브러리
flag
패키지는 주로 싱글 커맨드를 작성할 때 사용하고, 만약 go run
처럼 서브 커맨드가 있는 경우에는 써드파티 라이브러리를 사용할 수 있다.
urfave/cli
A simple, fast, and fun package for building command line apps in Go - urfave/cli
github.com