Go Flag 라이브러리: CLI 인수 완전 가이드
James Reed
Infrastructure Engineer · Leapcell

소개
flag
는 명령줄 옵션을 파싱하는 데 사용됩니다. Unix와 유사한 시스템 사용 경험이 있는 사람들은 명령줄 옵션에 익숙할 것입니다. 예를 들어 ls -al
명령어는 현재 디렉토리의 모든 파일 및 디렉토리에 대한 자세한 정보를 나열하며, 여기서 -al
은 명령줄 옵션입니다.
명령줄 옵션은 실제 개발, 특히 도구를 작성할 때 일반적으로 사용됩니다.
- 구성 파일의 경로를 지정합니다. 예를 들어
postgres -D /usr/local/pgsql/data
는 지정된 데이터 디렉토리로 PostgreSQL 서버를 시작합니다. - 특정 매개 변수를 사용자 정의합니다. 예를 들어
python -m SimpleHTTPServer 8080
은 8080 포트에서 수신 대기하는 HTTP 서버를 시작합니다. 지정하지 않으면 기본적으로 8000 포트에서 수신 대기합니다.
빠른 시작
라이브러리를 배우는 첫 번째 단계는 물론 사용하는 것입니다. 먼저 flag
라이브러리의 기본 사용법을 살펴보겠습니다.
package main import ( "fmt" "flag" ) var ( intflag int boolflag bool stringflag string ) func init() { flag.IntVar(&intflag, "intflag", 0, "int flag 값") flag.BoolVar(&boolflag, "boolflag", false, "bool flag 값") flag.StringVar(&stringflag, "stringflag", "default", "string flag 값") } func main() { flag.Parse() fmt.Println("int flag:", intflag) fmt.Println("bool flag:", boolflag) fmt.Println("string flag:", stringflag) }
먼저 프로그램을 컴파일한 다음 실행할 수 있습니다(macOS 사용 중).
$ go build -o main main.go $ ./main -intflag 12 -boolflag 1 -stringflag test
출력:
int flag: 12
bool flag: true
string flag: test
특정 옵션을 설정하지 않으면 해당 변수는 기본값을 취합니다.
$ ./main -intflag 12 -boolflag 1
출력:
int flag: 12
bool flag: true
string flag: default
설정되지 않은 stringflag
옵션은 기본값 default
를 가짐을 알 수 있습니다.
go run
을 직접 사용할 수도 있습니다. 이 명령어는 먼저 프로그램을 컴파일하여 실행 파일을 생성한 다음 명령줄의 다른 옵션을 이 프로그램에 전달하여 파일을 실행합니다.
$ go run main.go -intflag 12 -boolflag 1
-h
를 사용하여 옵션 도움말 정보를 표시할 수 있습니다.
$ ./main -h Usage of /path/to/main: -boolflag bool flag 값 -intflag int int flag 값 -stringflag string string flag 값 (default "default")
요약하자면, flag
라이브러리를 사용하는 일반적인 단계는 다음과 같습니다.
- 여기서
intflag
,boolflag
,stringflag
와 같이 옵션 값을 저장할 전역 변수를 정의합니다. init
메서드에서flag.TypeVar
메서드를 사용하여 옵션을 정의합니다. 여기서Type
은Int
,Uint
,Float64
,Bool
과 같은 기본 유형이거나,time.Duration
시간 간격일 수도 있습니다. 정의할 때 변수의 주소, 옵션 이름, 기본값 및 도움말 정보를 전달합니다.main
메서드에서flag.Parse
를 호출하여os.Args[1:]
에서 옵션을 파싱합니다.os.Args[0]
은 실행 프로그램의 경로이므로 제외됩니다.
주의 사항
flag.Parse
메서드는 모든 옵션이 정의된 후에 호출되어야 하며, flag.Parse
가 호출된 후에는 새 옵션을 정의할 수 없습니다. 이전 단계를 따르면 기본적으로 문제가 없을 것입니다.
init
는 모든 코드보다 먼저 실행되므로 모든 옵션 정의를 init
에 넣으면 main
함수에서 flag.Parse
가 실행될 때 모든 옵션이 이미 정의됩니다.
옵션 형식
flag
라이브러리는 세 가지 명령줄 옵션 형식을 지원합니다.
-flag
-flag=x
-flag x
-
와 --
를 모두 사용할 수 있으며, 기능은 동일합니다. 일부 라이브러리는 -
를 사용하여 짧은 옵션을 나타내고 --
를 사용하여 긴 옵션을 나타냅니다. 비교적 말하면 flag
는 사용하기 더 쉽습니다.
첫 번째 형식은 부울 옵션만 지원합니다. 나타나면 true
이고, 나타나지 않으면 기본값을 취합니다.
세 번째 형식은 부울 옵션을 지원하지 않습니다. 이 형식의 부울 옵션은 Unix와 유사한 시스템에서 예기치 않은 동작을 나타낼 수 있기 때문입니다. 다음 명령을 고려하십시오.
cmd -x *
여기서 *
는 쉘 와일드카드입니다. 0
또는 false
라는 파일이 있는 경우 부울 옵션 -x
는 값 false
를 갖습니다. 그렇지 않으면 부울 옵션 -x
는 값 true
를 갖습니다. 그리고 이 옵션은 하나의 인수를 소비합니다.
부울 옵션을 명시적으로 false
로 설정하려면 -flag=false
형식만 사용할 수 있습니다.
파싱은 첫 번째 비 옵션 인수(즉, -
또는 --
로 시작하지 않는 인수) 또는 종결자 --
를 만나면 중지됩니다. 다음 프로그램을 실행하십시오.
$ ./main noflag -intflag 12
출력은 다음과 같습니다.
int flag: 0
bool flag: false
string flag: default
noflag
를 만나면 파싱이 중지되고 후속 옵션 -intflag
가 파싱되지 않기 때문입니다. 따라서 모든 옵션은 기본값을 취합니다.
다음 프로그램을 실행하십시오.
$ ./main -intflag 12 -- -boolflag=true
출력은 다음과 같습니다.
int flag: 12
bool flag: false
string flag: default
먼저 intflag
옵션이 파싱되고 값이 12로 설정됩니다. --
를 만난 후 파싱이 중지되고 후속 --boolflag=true
는 파싱되지 않으므로 boolflag
옵션은 기본값 false
를 취합니다.
파싱이 중지된 후에도 명령줄 인수가 남아 있으면 flag
라이브러리는 해당 인수를 저장하며 flag.Args
메서드를 통해 해당 인수 슬라이스를 가져올 수 있습니다.
flag.NArg
메서드를 통해 파싱되지 않은 인수 수를 가져올 수 있으며, flag.Arg(i)
를 통해 위치 i
(0부터 시작)에서 인수에 액세스할 수 있습니다.
flag.NFlag
메서드를 호출하여 옵션 수를 가져올 수도 있습니다.
위의 프로그램을 약간 수정합니다.
func main() { flag.Parse() fmt.Println(flag.Args()) fmt.Println("Non-Flag Argument Count:", flag.NArg()) for i := 0; i < flag.NArg(); i++ { fmt.Printf("Argument %d: %s\n", i, flag.Arg(i)) } fmt.Println("Flag Count:", flag.NFlag()) }
이 프로그램을 컴파일하고 실행합니다.
$ go build -o main main.go $ ./main -intflag 12 -- -stringflag test
출력:
[-stringflag test]
Non-Flag Argument Count: 2
Argument 0: -stringflag
Argument 1: test
--
를 만나 파싱이 중지된 후 나머지 인수 -stringflag test
는 flag
에 저장되며 Args
, NArg
, Arg
와 같은 메서드를 통해 액세스할 수 있습니다.
정수 옵션 값은 1234
(10진수), 0664
(8진수) 및 0x1234
(16진수)와 같은 형식을 허용하고 음수일 수도 있습니다. 실제로 flag
는 내부적으로 strconv.ParseInt
메서드를 사용하여 문자열을 int
로 파싱합니다.
따라서 이론적으로 ParseInt
에서 허용하는 모든 형식이 괜찮습니다.
부울 옵션 값은 다음과 같습니다.
true
값:1
,t
,T
,true
,TRUE
,True
false
값:0
,f
,F
,false
,FALSE
,False
옵션을 정의하는 또 다른 방법
위에서는 flag.TypeVar
를 사용하여 옵션을 정의하는 방법을 소개했습니다. 이 메서드를 사용하려면 먼저 변수를 정의한 다음 변수의 주소를 전달해야 합니다.
또 다른 방법이 있습니다. flag.Type
(Type
은 Int
, Uint
, Bool
, Float64
, String
, Duration
등이 될 수 있음)을 호출하면 변수가 자동으로 할당되고 해당 변수의 주소가 반환됩니다. 사용법은 이전 방법과 유사합니다.
package main import ( "fmt" "flag" ) var ( intflag *int boolflag *bool stringflag *string ) func init() { intflag = flag.Int("intflag", 0, "int flag 값") boolflag = flag.Bool("boolflag", false, "bool flag 값") stringflag = flag.String("stringflag", "default", "string flag 값") } func main() { flag.Parse() fmt.Println("int flag:", *intflag) fmt.Println("bool flag:", *boolflag) fmt.Println("string flag:", *stringflag) }
프로그램을 컴파일하고 실행합니다.
$ go build -o main main.go $ ./main -intflag 12
출력은 다음과 같습니다.
int flag: 12
bool flag: false
string flag: default
사용할 때 역참조가 필요한 것을 제외하고는 이전 방법과 기본적으로 동일합니다.
고급 사용법
짧은 옵션 정의
flag
라이브러리는 짧은 옵션을 명시적으로 지원하지 않지만 동일한 변수에 대해 다른 옵션을 설정하여 이를 달성할 수 있습니다. 즉, 두 옵션이 동일한 변수를 공유합니다.
초기화 순서가 불확실하므로 동일한 기본값을 갖도록 해야 합니다. 그렇지 않으면 이 옵션이 전달되지 않으면 동작이 불확실합니다.
package main import ( "fmt" "flag" ) var logLevel string func init() { const ( defaultLogLevel = "DEBUG" usage = "로그 수준 값 설정" ) flag.StringVar(&logLevel, "log_type", defaultLogLevel, usage) flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(약칭)") } func main() { flag.Parse() fmt.Println("로그 수준:", logLevel) }
프로그램을 컴파일하고 실행합니다.
$ go build -o main main.go $ ./main -log_type WARNING $ ./main -l WARNING
긴 옵션과 짧은 옵션을 모두 사용하면 다음이 출력됩니다.
로그 수준: WARNING
이 옵션을 전달하지 않으면 기본값이 출력됩니다.
$ ./main 로그 수준: DEBUG
시간 간격 파싱
기본 유형을 옵션으로 사용하는 것 외에도 flag
라이브러리는 time.Duration
유형, 즉 시간 간격을 지원합니다. 시간 간격에 지원되는 형식은 "300ms"
, "-1.5h"
, "2h45m"
등 매우 다양합니다.
시간 단위는 ns
, us
, ms
, s
, m
, h
, day
등이 될 수 있습니다. 실제로 flag
는 내부적으로 time.ParseDuration
을 호출합니다. 구체적으로 지원되는 형식은 time
라이브러리 문서에서 확인할 수 있습니다.
package main import ( "flag" "fmt" "time" ) var ( period time.Duration ) func init() { flag.DurationVar(&period, "period", 1*time.Second, "절전 기간") } func main() { flag.Parse() fmt.Printf("%v 동안 절전 모드...", period) time.Sleep(period) fmt.Println() }
전달된 명령줄 옵션 period
에 따라 프로그램은 해당 시간 동안 절전 모드에 들어가며 기본값은 1초입니다. 프로그램을 컴파일하고 실행합니다.
$ go build -o main main.go $ ./main 1초 동안 절전 모드... $ ./main -period 1m30s 1분 30초 동안 절전 모드...
옵션 사용자 정의
flag
라이브러리에서 제공하는 옵션 유형을 사용하는 것 외에도 옵션 유형을 사용자 정의할 수도 있습니다. 표준 라이브러리에서 제공하는 예제를 분석해 보겠습니다.
package main import ( "errors" "flag" "fmt" "strings" "time" ) type interval []time.Duration func (i *interval) String() string { return fmt.Sprint(*i) } func (i *interval) Set(value string) error { if len(*i) > 0 { return errors.New("interval 플래그가 이미 설정되었습니다.") } for _, dt := range strings.Split(value, ",") { duration, err := time.ParseDuration(dt) if err != nil { return err } *i = append(*i, duration) } return nil } var ( intervalFlag interval ) func init() { flag.Var(&intervalFlag, "deltaT", "이벤트 간에 사용할 쉼표로 구분된 간격 목록") } func main() { flag.Parse() fmt.Println(intervalFlag) }
먼저 새 유형을 정의합니다. 여기서 interval
유형이 정의됩니다.
새 유형은 flag.Value
인터페이스를 구현해야 합니다.
// src/flag/flag.go type Value interface { String() string Set(string) error }
String
메서드는 이 유형의 값을 형식화하고 flag.Parse
메서드가 실행되어 사용자 정의 유형의 옵션을 만나면 이 유형의 변수에 대한 Set
메서드를 옵션 값을 매개 변수로 사용하여 호출합니다.
여기서 ,
로 구분된 시간 간격이 파싱되어 슬라이스에 저장됩니다.
사용자 정의 유형 옵션의 정의는 flag.Var
메서드를 사용해야 합니다.
프로그램을 컴파일하고 실행합니다.
$ go build -o main main.go $ ./main -deltaT 30s [30s] $ ./main -deltaT 30s,1m,1m30s [30s 1m0s 1m30s]
지정된 옵션 값이 불법인 경우 Set
메서드는 error
유형의 값을 반환하고 Parse
실행이 중지되고 오류 및 사용법 도움말이 인쇄됩니다.
$ ./main -deltaT 30x invalid value "30x" for flag -deltaT: time: unknown unit x in duration 30x Usage of /path/to/main: -deltaT value 이벤트 간에 사용할 쉼표로 구분된 간격 목록
프로그램에서 문자열 파싱
때로는 옵션이 명령줄을 통해 전달되지 않습니다. 예를 들어 구성 테이블에서 읽거나 프로그램에서 생성됩니다. 이 경우 flag.FlagSet
구조의 관련 메서드를 사용하여 해당 옵션을 파싱할 수 있습니다.
실제로 우리가 이전에 호출한 flag
라이브러리의 메서드는 모두 FlagSet
구조의 메서드를 간접적으로 호출합니다. flag
라이브러리는 명령줄 옵션을 파싱하기 위해 특히 FlagSet
유형의 전역 변수 CommandLine
을 정의합니다.
이전에 호출한 flag
라이브러리의 메서드는 편의를 위한 것이며 내부적으로는 모두 CommandLine
의 해당 메서드를 호출합니다.
// src/flag/flag.go var CommandLine = NewFlagSet(os.Args[0], ExitOnError) func Parse() { CommandLine.Parse(os.Args[1:]) } func IntVar(p *int, name string, value int, usage string) { CommandLine.Var(newIntValue(value, p), name, usage) } func Int(name string, value int, usage string) *int { return CommandLine.Int(name, value, usage) } func NFlag() int { return len(CommandLine.actual) } func Arg(i int) string { return CommandLine.Arg(i) } func NArg() int { return len(CommandLine.args) }
마찬가지로 옵션을 파싱하기 위해 FlagSet
유형의 자체 변수를 만들 수도 있습니다.
package main import ( "flag" "fmt" ) func main() { args := []string{"-intflag", "12", "-stringflag", "test"} var intflag int var boolflag bool var stringflag string fs := flag.NewFlagSet("MyFlagSet", flag.ContinueOnError) fs.IntVar(&intflag, "intflag", 0, "int flag 값") fs.BoolVar(&boolflag, "boolflag", false, "bool flag 값") fs.StringVar(&stringflag, "stringflag", "default", "string flag 값") fs.Parse(args) fmt.Println("int flag:", intflag) fmt.Println("bool flag:", boolflag) fmt.Println("string flag:", stringflag) }
NewFlagSet
메서드에는 두 개의 매개 변수가 있습니다. 첫 번째 매개 변수는 프로그램 이름이며 도움말을 출력하거나 오류가 발생할 때 표시됩니다. 두 번째 매개 변수는 파싱 중 오류를 처리하는 방법이며 다음과 같은 몇 가지 옵션이 있습니다.
ContinueOnError
: 오류가 발생한 후에도 파싱을 계속합니다.CommandLine
은 이 옵션을 사용합니다.ExitOnError
: 오류가 발생하면os.Exit(2)
를 호출하여 프로그램을 종료합니다.PanicOnError
: 오류가 발생하면panic
을 생성합니다.
flag
라이브러리의 관련 코드를 간단히 살펴보겠습니다.
// src/flag/flag.go func (f *FlagSet) Parse(arguments []string) error { f.parsed = true f.args = arguments for { seen, err := f.parseOne() if seen { continue } if err == nil { break } switch f.errorHandling { case ContinueOnError: return err case ExitOnError: os.Exit(2) case PanicOnError: panic(err) } } return nil }
flag
라이브러리의 메서드를 직접 사용하는 것과는 약간 다릅니다. FlagSet
가 Parse
메서드를 호출할 때 문자열 슬라이스를 매개 변수로 명시적으로 전달해야 합니다. flag.Parse
가 내부적으로 CommandLine.Parse(os.Args[1:])
를 호출하기 때문입니다.
Leapcell: 웹 호스팅을 위한 차세대 서버리스 플랫폼
마지막으로 Go 서비스를 배포하는 데 가장 적합한 플랫폼인 **Leapcell**을 추천합니다.
1. 다국어 지원
- JavaScript, Python, Go 또는 Rust로 개발합니다.
2. 무제한 프로젝트 무료 배포
- 사용량에 대해서만 지불하세요. 요청이 없으면 요금이 부과되지 않습니다.
3. 최고의 비용 효율성
- 유휴 요금 없이 사용한 만큼 지불하세요.
- 예: $25로 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
4. 간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합
- 실행 가능한 통찰력을 위한 실시간 지표 및 로깅
5. 간편한 확장성 및 고성능
- 높은 동시성을 쉽게 처리하기 위한 자동 확장
- 운영 오버헤드가 없으므로 구축에만 집중할 수 있습니다.
Leapcell 트위터: https://x.com/LeapcellHQ