다양한 cli가 존재하고 요즘은 Sub command 형태로 cli를 확장하는 것이 대세인 듯하다. Devocean에도 ‘Cobra를 이용한 CLI 유틸리티 만들기**’(**https://devocean.sk.com/search/techBoardDetail.do?ID=163440)라는 주제로 개요에대해 다루고 있다. 실제 cli를 구현하다보면 맞닥드리게되는 다양한 옵션에 대한 소개가 부족한 듯하여 정리해 보려고 한다.

본 페이지에서는 중심이되는 cobra의 command 객체에 대해 다뤄본다.

cobra 기본 사용법

기본프로젝트 생성

기본적으로 ‘cobra init’ 명령을 사용하여 기본 cobra기반 프로젝트를 생성한다. 이때 ‘-l’ 옵션으로 라이센스관련 정보를 ‘-a’ 옵션으로 저자 관련 정보를 지정하여 시작할 수 있다. 명령이 수행되면 $GOPATH/src 아래에 생성된 자원들을 확인할 수 있다.

$ cobra init tks -a "SK Telecom" -l MIT
Your Cobra application is ready at
/home/hereisgopath/src/tks.

Give it a try by going there and running `go run main.go`.
Add commands to it by running `cobra add [cmdname]`.

$ tree $GOPATH/src/tks
/home/hereisgopath/src/tks
├── cmd
│   └── root.go
├── LICENSE
└── main.go

1 directory, 3 files

sub command 추가

앞에서 만들어진 프로젝트로 이동하여 ‘cobra add’ 명령을 사용하면 sub command를 위한 구조와 기본 내용이 포함된 코드들이 반영된다.

$ cd $GOPATH/src/tks
$ cobra add test

구체적으로 디렉토리 ‘cmd’에 지정한 명령어 이름으로 파일이 생성되고 (위 예제에서는 cmd/test.go) 기본 코드를 포함하고 있다.

자동 생성파일 살펴보기

앞에서 init시 만들어진 cmd/root.go 파일을 살펴보면 다음과 같다. (수많은 주석과 함께 만들어지지만 편의를 위해 주석부분은 삭제했음)

package cmd

import (
	"fmt"
	"os"

	homedir "github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var cfgFile string

var RootCmd = &cobra.Command{
	Use:   "tks",
	Short: "A brief description of your application",
	Long: `A ..... application.`,
}

func Execute() {
	if err := RootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func init() { 
	cobra.OnInitialize(initConfig)
	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tks.yaml)")
	RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func initConfig() {
	if cfgFile != "" {
		// Use config file from the flag.
		viper.SetConfigFile(cfgFile)
	} else {
		// Find home directory.
		home, err := homedir.Dir()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		viper.AddConfigPath(home)
		viper.SetConfigName(".tks")
	}

	viper.AutomaticEnv() // read in environment variables that match

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}

각 부분은 다음과 같은 의미를 갖는다.

변수

함수