커맨드 라인 인터페이스(CLI) 프로그램은 터미널에서 실행되므로, 그래픽 인터페이스가 없는 비GUI 프로그램입니다.
실제로 우리는 매일 ls, ps, top 등과 같은 CLI를 사용하고 있습니다.
awesome-cli-apps에는 많은 훌륭한 CLI 프로그램들이 모여 있으니 참고하세요.
저는 러스트로 작성된 ls의 현대적 버전인 exa를 추천합니다.
CLI 프로그램 형태
CLI 프로그램은 다음과 같은 모습일 것입니다:
$ ./program_name [arguments] [flags] [options]
보통 -h 또는 --help를 통해 사용 정보를 확인할 수 있습니다.
예를 들어 cargo 프로그램을 살펴봅시다:
$ cargo -h
Rust's package manager
USAGE:
cargo [OPTIONS] [SUBCOMMAND]
OPTIONS:
-V, --version Print version info and exit
--list List installed commands
--explain <CODE> Run `rustc --explain CODE`
-v, --verbose Use verbose output (-vv very verbose/build.rs output)
-q, --quiet No output printed to stdout
--color <WHEN> Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
-Z <FLAG>... Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Prints help information
Some common cargo commands are (see all commands with --list):
build Compile the current project
check Analyze the current project and report errors, but don't build object files
clean Remove the target directory
doc Build this project's and its dependencies' documentation
new Create a new cargo project
init Create a new cargo project in an existing directory
run Build and execute src/main.rs
test Run the tests
bench Run the benchmarks
update Update dependencies listed in Cargo.lock
search Search registry for crates
publish Package and upload this project to the registry
install Install a Rust binary
uninstall Uninstall a Rust binary
See 'cargo help <command>' for more information on a specific command.
이제 cargo 사용법을 알게 되셨죠?
프로젝트 생성
새 CLI 프로그램 만들기를 시작해봅시다!
여기서는 프로젝트 이름을 meow로 하겠습니다.
$ cargo new meow
$ cd meow
cargo가 새 프로젝트 생성을 도와줄 것입니다.
인수(Arguments)
CLI에 대해 알아본 바와 같이, CLI에는 보통 인수가 있습니다.
간단한 방법은 다음과 같습니다:
// main.rs
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}
$./meow a1 a2 a3
["meow", "a1", "a2", "a3"]
그러면 인수를 얻을 수 있습니다.
하지만 실제 상황에서는 CLI가 더 복잡합니다. 예를 들면:
$ ./foo -g -e a1 a3 a4
$ ./foo a1 -e -l --path=~/test/123
이처럼 단순한 방식으로는 사용하기 불편합니다.
- 인수에 기본값이 있을 수 있음
- 플래그 위치가 바뀔 수 있음
- 옵션 위치가 바뀔 수 있음
- arg1이 arg2에 의존할 수 있음
따라서 이 작업을 쉽게 수행할 수 있는 크레이트가 필요합니다.
Clap
Clap은 Rust용 완전한 기능을 갖춘 빠른 커맨드라인 인수 파서입니다.
사용법은 다음과 같습니다.
먼저 cli.yml 파일을 만들어 인수 설정을 해줍니다.
다음과 같은 모습입니다:
<!-- cli.yml -->
name: myapp
version: "1.0"
author: Kevin K. <kbknapp@gmail.com>
about: Does awesome things
args:
- config:
short: c
long: config
value_name: FILE
help: Sets a custom config file
takes_value: true
- INPUT:
help: Sets the input file to use
required: true
index: 1
- verbose:
short: v
multiple: true
help: Sets the level of verbosity
subcommands:
- test:
about: controls testing features
version: "1.3"
author: Someone E. <someone_else@other.com>
args:
- debug:
short: d
help: print debug information
그리고 main.rs에 다음 코드를 추가합니다:
#[macro_use]
extern crate clap;
use clap::App;
fn main() {
// YAML 파일은 현재 파일과 비슷한 방식으로 모듈을 찾는 것처럼 상대 경로에서 찾습니다
let yaml = load_yaml!("cli.yml");
let m = App::from_yaml(yaml).get_matches();
match m.value_of("argument1") {
// ...
}
// ...
}
clap 크레이트가 yml 파일을 불러와 파싱하면, 프로그램에서 인수를 사용할 수 있습니다.
위 cli.yml로 프로그램을 -h 옵션과 함께 실행하면 다음과 같은 결과가 나옵니다:
$ meow -h
My Super Program 1.0
Kevin K. <kbknapp@gmail.com>
Does awesome things
USAGE:
MyApp [FLAGS] [OPTIONS] <INPUT> [SUBCOMMAND]
FLAGS:
-h, --help Prints help information
-v Sets the level of verbosity
-V, --version Prints version information
OPTIONS:
-c, --config <FILE> Sets a custom config file
ARGS:
INPUT The input file to use
SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
test Controls testing features
매우 편리하지 않나요?
설정(Configuration)
CLI에도 설정이 필요합니다.
실행 전에 결정되어야 하는 일부 매개변수는 .env, .config, .setting과 같은 설정 파일에 기록됩니다.
예를 들어 .env 파일 내용이 다음과 같다면:
PORT = 8000
PATH = "home/foo/bar"
MODE = "happy mode"
ZONE = 8
AREA = "Seoul"
다음과 같이 직접 작성하거나,
- .env 파일 읽기
- \n으로 분리
- =를 기준으로 분리하여 HashMap에 데이터 추가
또는 크레이트 사용 가능
dotenv_codegen
dotenv_codegen은 매크로를 사용하는 간단한 .env 설정 파서입니다.
이 크레이트는 .env 파일을 읽습니다.
사용법이 간단합니다.
fn main() {
println!("{}", dotenv!("PORT"));
}
환경 변수(Environment Variables)
시스템의 환경 변수도 참조하고 싶을 수 있습니다(예: JAVA_HOME).
use std::env;
let key = "HOME";
match env::var_os(key) {
Some(val) => println!("{}: {:?}", key, val),
None => println!("{} 환경 변수가 정의되지 않았습니다.", key)
}
오류 처리(Error handling)
오류 처리 또한 중요합니다.
프로그램에서 오류가 발생하면 panic!하여 프로그램이 중단되는 일은 없어야 합니다.
때로는 오류가 크게 중요하지 않으므로, 프로그램을 다시 실행하거나 다른 규칙을 적용하는 식으로 오류를 처리할 수 있어야 합니다.
panic
panic!("이것은 패닉입니다");
간단하지만 역할이 제한적입니다.
- 프로그램이 중단됨
- 종료 코드 없이 종료
- 스크립트에서 주로 사용
Result
Result는 프로그램이 중단되지 않고 오류를 전달합니다.
함수 실행에 실패하면 오류 유형과 함께 Error를 반환합니다.
그런 다음 오류 유형에 따라 "재시도" 또는 "포기" 등의 적절한 조치를 취할 수 있습니다.
enum MyErr {
Reason1,
Reason2,
}
fn foo() -> Result<(), MyErr> {
match bar {
Some(_)=>{}
None => Err(MyErr::Reason1)
}
}
fn hoo() {
match foo() {
Ok(_) => reply(),
Err(e) => println!(e)
// `e`로는 메시지를 구분할 수 없어
// `fmt`를 사용해 메시지로 변환해야 함
}
}
오류 메시지(Error Message)
오류 유형의 오류 메시지를 출력하거나 사용하고 싶을 수 있습니다.
이를 위해서는 MyErr에 fmt을 구현해 오류 메시지를 정의해야 합니다.
enum MyErr {
Reason1(String),
Reason2(String, u32),
}
impl fmt::Display for MyErrError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MyErr::Reason1(ref s) =>
write!(f, "`{}` 오류입니다", s),
MyErr::Reason2(ref s, ref num) =>
write!(f, "`{}` 그리고 `{}`가 오류입니다", s, num),
}
}
}
Err(e) => println!("{}", e)
// `XXX` 오류입니다
표준 오류(Standard error)
시스템에는 표준 출력과 표준 오류가 있습니다.
println!()은 표준 출력이고, eprintln!()은 표준 오류입니다.
예를 들어:
$ cargo run > output.txt
표준 출력 스트림이 output.txt 파일로만 리디렉션됩니다.
따라서 오류 메시지를 로그 파일에 작성하고 싶지 않다면 eprintln!()을 사용하여 표준 오류로 출력할 수 있습니다.
종료 코드(Exit Code)
0이 아닌 코드는 다른 프로그램에 실패를 알립니다.
use std::process;
fn main() {
process::exit(1);
}
결론
CLI 프로그램은 어떤 작업이든 수행할 수 있으며, 좋은 CLI를 위해서는 꼼꼼한 설계가 필요합니다.
CLI는 인수와 설정을 파싱하고, 환경 변수를 읽으며, 오류를 잘 처리해야 합니다.
또한 표준 출력과 오류로 메시지를 출력하고, 실패 시 종료 코드와 함께 종료해야 합니다.
'Rust' 카테고리의 다른 글
Rust 강좌 2. 프라임 - 소수와 친해지기 (4) | 2024.08.08 |
---|---|
Rust 강좌 1. Cargo와 crates.io (0) | 2024.08.05 |
Serde를 활용한 다양한 JSON 열거형 처리 방법 및 주의점 (0) | 2024.05.19 |
Rust 난수 생성 완벽 가이드 - rand 크레이트 사용법 (0) | 2024.05.17 |
Rust 모듈 사용법 - 크레이트 생성부터 모듈 공개까지 완벽 가이드 (0) | 2024.05.17 |