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,Truefalse값: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