Menu

Golang 명령줄 인자: os.Args, flag, 환경 변수

Go 프로그램이 명령줄을 읽는 방법: os.Args, 타입이 있는 옵션을 위한 flag 패키지, FlagSet으로 만드는 하위 명령, os.Getenv와 os.LookupEnv로 읽는 환경 변수, os.Exit로 정하는 종료 코드를 다룹니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

os.Args

os.Args는 문자열 슬라이스입니다. os.Args[0]은 프로그램 이름이고, 나머지는 셸이 넘겨준 그대로의 인자입니다.

에디터의 Args 패널에서는 필드 하나가 인자 하나이며, 그대로 전달됩니다. 필드 세 개에 hello, two words, -v를 넣어 보세요. 프로그램은 인자 세 개를 보고, two words는 공백을 포함한 인자 하나로 남습니다. 터미널에서는 셸이 나누기 때문에 같은 것을 go run . hello "two words" -v로 씁니다.

인덱싱하기 전에 항상 len(os.Args)를 확인하세요. 인자가 없을 때 os.Args[1]index out of range [1] with length 1로 패닉을 일으킵니다.

인자는 문자열입니다. 사용자는 무엇이든 입력하므로 숫자는 strconv.Atoistrconv.ParseFloat로 변환하고 오류를 처리하세요.

flag 패키지

-port 8080 -verbose 같은 옵션에는 flag를 쓰세요. 파싱하고, 타입을 변환하고, 오류를 보고하고, 도움말 메시지를 만들어 줍니다.

인자 없이 실행하면 hello, world를 한 번 출력합니다. Args 패널에서 필드 두 개에 -nameGopher를 넣고, 이어서 필드 세 개에 -count=3, -loud, extra를 넣어 보세요. 프로그램은 HELLO, GOPHER!를 세 번 출력하고 remaining args: [extra]를 출력합니다.

플래그가 동작하는 방식:

  • 각 정의 함수(flag.String, flag.Int, flag.Bool, flag.Float64, flag.Duration, flag.Uint64...)는 이름, 기본값, 사용법 문자열을 받아 포인터를 반환합니다. flag.Parse() 후에 *name으로 값을 읽으세요.
  • Var 형태는 이미 있는 변수에 묶습니다: flag.IntVar(&cfg.Port, "port", 8080, "port"). 설정 구조체에는 이쪽이 더 깔끔합니다.
  • 사용자는 -name value, -name=value, --name value, --name=value로 쓸 수 있습니다. Go는 대시 하나와 둘을 구분하지 않습니다.
  • 불리언은 값을 받으려면 =가 필요합니다. -loud는 true를, -loud=false는 false를 설정하지만, -loud false는 true를 설정하고 false를 위치 인자로 남깁니다.
  • 파싱은 플래그가 아닌 첫 번째 인자(또는 --)에서 멈춥니다. prog file.txt -v-v를 위치 인자로 취급합니다. 플래그를 먼저 쓰세요.
  • flag.Args()는 남은 위치 인자를, flag.NArg()는 그 개수를, flag.Arg(i)는 그중 하나를 반환합니다.

알 수 없는 플래그나 잘못된 값은 오류와 사용법을 출력하고 상태 코드 2로 종료합니다. -h-help는 사용법을 출력하고 (Go 1.15부터) 상태 코드 0으로 종료합니다. 사용법 텍스트는 정의로부터 생성됩니다:

Usage of greet:
  -count int
    	how many times (default 1)
  -delay duration
    	pause between greetings, e.g. 10ms
  -loud
    	shout the greeting
  -name string
    	who to greet (default "world")

flag.PrintDefaults()를 호출하기 전에 직접 머리말을 출력하려면 flag.Usage에 함수를 설정하세요.

FlagSet으로 만드는 하위 명령

git commit -m msg 같은 도구에는 자기만의 플래그를 가진 하위 명령이 있습니다. 하위 명령마다 flag.FlagSet을 만들고 첫 번째 인자에 대한 switch로 하나를 고르세요:

flag.ContinueOnErrorParse가 종료하는 대신 오류를 반환하게 하므로 함수를 테스트할 수 있게 유지해 줍니다. runos.Args를 읽는 대신 인자를 매개변수로 받으므로, 테스트에서 run([]string{"list", "-all"})을 직접 호출할 수 있습니다. Args 패널에 list-all을 넣어 보거나, 오류 경로를 보려면 delete를 넣어 보세요.

중첩된 명령, 셸 자동 완성, 문서 생성이 필요한 큰 CLI라면 대부분의 프로젝트가 서드파티 github.com/spf13/cobra를 씁니다. 작은 도구라면 표준 flag 패키지로 충분합니다.

환경 변수

  • os.Getenv는 변수가 없을 때와 빈 문자열로 설정되었을 때 모두 ""를 반환합니다. os.LookupEnv는 둘을 구분합니다.
  • 값은 항상 문자열입니다. 시작할 때 변환하고 검증해서, 요청 처리 도중이 아니라 시작 시점에 분명한 메시지와 함께 실패하게 하세요.
  • os.Setenv는 현재 프로세스와 나중에 시작하는 자식 프로세스에 영향을 줍니다. 여러분을 실행한 셸의 환경은 바꿀 수 없습니다.
  • os.Environ()은 모든 변수를 "KEY=value" 문자열로 반환합니다.

설정의 흔한 구성은 이렇습니다. 사람이 입력하는 것은 플래그로, 배포 설정은 (컨테이너 플랫폼이 설정하는) 환경 변수로 받고, 플래그가 환경 변수를, 환경 변수가 기본값을 덮어씁니다.

종료 코드와 os.Exit

Go 프로그램은 main이 반환되면 상태 코드 0으로 종료합니다. os.Exit(code)는 그 상태 코드로 프로세스를 즉시 끝냅니다. 관례상 0은 성공, 1은 일반 오류, 2는 사용법 오류입니다(flag 패키지가 2를 씁니다).

os.Exit는 지연된 함수를 실행하지 않습니다. 파일이 플러시되지 않고 defer 정리 작업도 건너뜁니다. log.Fatalos.Exit(1)을 호출하므로 효과가 같습니다. os.Exitmain의 끝 한곳에만 두고, 실제 프로그램은 하위 명령 예제처럼 오류를 반환하는 run 함수에 넣으세요:

func main() {
	if err := run(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, "error:", err)
		os.Exit(1)
	}
}

오류 메시지는 os.Stdout이 아니라 os.Stderr에 쓰세요. 그래야 출력을 파일로 리다이렉트하거나 다른 명령으로 파이프해도 보입니다. 복구되지 않은 패닉은 상태 코드 2로 종료합니다.

흔한 실수

  • 길이를 확인하지 않고 os.Args를 인덱싱함. 인자가 빠지면 패닉이 납니다.
  • flag.Parse() 전에 플래그를 읽음. 기본값을 얻게 됩니다.
  • *를 잊음. fmt.Println(port)는 값이 아니라 0xc000012345 같은 주소를 출력합니다.
  • 위치 인자 뒤에 플래그를 둠. prog input.txt -v-v를 파싱하지 않습니다.
  • bool 플래그에 -verbose false를 씀. -verbose=false라고 쓰세요.
  • 프로그램 깊은 곳에서 os.Exitlog.Fatal을 호출함. 지연된 정리 작업이 실행되지 않고 코드를 테스트할 수 없습니다. 오류를 main까지 반환하세요.

자주 묻는 질문

Go에서 명령줄 인자는 어떻게 가져오나요?

os.Args는 인덱스 0에 프로그램 이름을, 그 뒤에 인자들을 담은 []string입니다. os.Args[1:]가 사용자가 입력한 인자입니다. 인덱싱하기 전에 len(os.Args)를 확인하세요. 그렇지 않으면 인자가 빠졌을 때 프로그램이 패닉을 일으킵니다.

Go에서 flag 패키지는 어떻게 쓰나요?

플래그를 선언하고, flag.Parse()를 호출한 뒤 읽습니다: port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). 함수들은 포인터를 반환합니다. 사용자는 -port=9000, -port 9000, --port 9000으로 쓸 수 있고, -h는 자동 생성된 사용법을 출력합니다.

Go에서 환경 변수는 어떻게 읽나요?

os.Getenv("HOME")은 값을 반환하고, 변수가 설정되지 않았으면 빈 문자열을 반환합니다. 설정되지 않은 것과 빈 값으로 설정된 것을 구분하려면 v, ok := os.LookupEnv("HOME")을 쓰세요. os.Setenv는 현재 프로세스와 그 이후에 시작하는 자식 프로세스의 환경을 바꿉니다.

Go의 os.Exit는 지연된 함수를 실행하나요?

아니요. os.Exit는 주어진 상태 코드로 프로세스를 즉시 끝내고 지연된 호출은 실행되지 않으므로, 버퍼에 있던 출력이 사라지거나 파일이 플러시되지 않을 수 있습니다. 흔한 패턴은 func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }이며, 실제 작업과 defer는 모두 run 안에 둡니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기