콘텐츠로 이동

Rust 명령줄 인자

연습 삼아 만들어 볼 만한 작은 프로젝트, 예를 들어 할 일 목록이나 단어 세기 프로그램은 대부분 값을 소스 코드에 박아 두는 대신 명령줄에서 입력을 받아야 해요. 이 페이지에서는 그 기초를 다뤄요.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}

이 코드를 greet라는 프로그램으로 컴파일해서 이렇게 실행하면

Terminal window
./greet Alice

다음과 같이 나와요.

["greet", "Alice"]

첫 번째 요소는 항상 프로그램 자기 이름이에요. 내가 실제로 입력한 인자는 인덱스 1부터 시작해요.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("사용법: greet <이름>");
return;
}
let name = &args[1];
println!("안녕하세요, {name}님!");
}

cargo run -- Alice로 실행하면 이렇게 나와요.

안녕하세요, Alice님!

인자 없이 실행하면 프로그램이 죽는 대신 사용법 메시지가 나와요. 인덱스로 접근하기 전에 args.len()을 확인해 두면, 누가 인자를 깜빡하고 안 넘겨도 패닉이 나지 않아요.

인자는 전부 String으로 들어오기 때문에, 숫자로 쓰려면 (타입 변환 페이지에서 본) .parse()가 필요할 때가 많아요.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("사용법: square <숫자>");
return;
}
match args[1].parse::<i32>() {
Ok(n) => println!("{}", n * n),
Err(_) => println!("올바른 숫자가 아니에요"),
}
}

플래그(flag)는 --shout처럼 어떤 기능을 켜는 인자예요. 크레이트를 쓰기 전에, 직접 처리하면 어떻게 되는지 먼저 보면 도움이 돼요. 테스트하기 쉽게 만드는 요령은, 인자를 슬라이스 &[String]으로 받는 함수에 로직을 넣는 거예요. 그러면 main은 진짜 인자를 넘기고, 실험할 때는 직접 만든 목록을 넘길 수 있어요.

한글에는 대문자가 없어서, 이 예제의 인사말은 to_uppercase()의 효과가 잘 보이도록 영어로 두었어요.

fn run(args: &[String]) {
let mut shout = false;
let mut names = Vec::new();
// 인덱스 0(프로그램 이름)은 건너뛰어요
for arg in &args[1..] {
if arg == "--shout" {
shout = true;
} else {
names.push(arg);
}
}
if names.is_empty() {
println!("사용법: greet [--shout] <이름>...");
return;
}
for name in names {
let line = format!("Hello, {name}!");
if shout {
println!("{}", line.to_uppercase());
} else {
println!("{line}");
}
}
}
fn main() {
// 실제 프로그램에서는 이렇게 써요.
// let args: Vec<String> = std::env::args().collect();
// 여기서는 Playground에서도 돌아가도록 목록을 직접 만들어요.
let args = vec![
String::from("greet"),
String::from("--shout"),
String::from("Ana"),
String::from("Ben"),
];
run(&args);
}

실행 결과:

HELLO, ANA!
HELLO, BEN!
▶ 직접 실행해 보기

몇 가지 짚어 볼게요.

  • &args[1..]는 프로그램 이름을 뺀 나머지 인자 전부예요. 플래그는 어디에 와도 괜찮아요. greet Ana --shout Ben도 잘 돌아가요.
  • 목록에서 --shout를 지우고 다시 실행해 보세요. 평범한 인사가 나와요. 이름까지 지우면 사용법 메시지가 나와요.
  • run은 목록이 어디서 왔는지 신경 쓰지 않으니, std::env::args().collect()로 바꾸는 건 main에서 한 줄만 고치면 돼요.

플래그가 한두 개라면 이걸로 충분해요. 하지만 짧은 버전(-s), 플래그 뒤에 오는 값(--count 3), --help 메시지까지 원하게 되면 직접 짠 반복문은 금방 커져요. 그때가 clap으로 넘어갈 때예요.

인자가 몇 개를 넘어가면 clap을 쓰세요

섹션 제목: “인자가 몇 개를 넘어가면 clap을 쓰세요”

--verbose 같은 플래그, 있어도 되고 없어도 되는 값, 친절한 --help 출력이 필요해지면 args에 인덱스로 직접 접근하는 방식은 금방 감당하기 어려워져요. 이럴 때 표준처럼 쓰는 해결책이 clap (영어) 크레이트예요. 이렇게 추가해요.

Terminal window
cargo add clap --features derive
use clap::Parser;
#[derive(Parser)]
struct Args {
name: String,
#[arg(short, long, default_value_t = 1)]
count: u32,
}
fn main() {
let args = Args::parse();
for _ in 0..args.count {
println!("안녕하세요, {}님!", args.name);
}
}

구조체 정의만으로 인자 파싱, 값 검증, 제대로 동작하는 --help 메시지까지 모두 만들어져요. 간단한 스크립트에는 clap이 필요 없지만, 한 번 이상 실제로 쓸 도구라면 손으로 할 일을 많이 덜어 줘요.

Playground에서는 진짜 인자를 넘길 수 없어서, 두 문제 모두 env::args().collect()가 돌려주는 것과 똑같이 생긴 Vec<String>을 직접 만들어서 시작해요. 내 컴퓨터에서는 이걸 진짜 인자로 바꾸고 cargo run -- ...로 실행해 보세요.

vec!["notes", "add", "우유 사기"](각각 String)가 주어졌을 때, 사용자가 입력한 인자가 몇 개인지(프로그램 이름은 빼고) 출력하세요. 그다음 각 인자를 1부터 시작하는 번호와 함께 출력하세요.

힌트: 개수는 args.len() - 1이에요. for i in 1..args.len() 반복문을 쓰면 프로그램 이름 다음부터 인덱스를 하나씩 얻을 수 있어요.

정답 보기
fn main() {
// std::env::args().collect()에서 왔다고 생각해 주세요
let args = vec![
String::from("notes"),
String::from("add"),
String::from("우유 사기"),
];
println!("인자를 {}개 넘겼어요", args.len() - 1);
for i in 1..args.len() {
println!("{i}: {}", args[i]);
}
}

실행 결과:

인자를 2개 넘겼어요
1: add
2: 우유 사기
▶ 직접 실행해 보기

"우유 사기"는 인자 하나로 쳐요. 실제 명령줄에서는 따옴표로 감싸서 입력하고(cargo run -- add "우유 사기"), 셸이 그걸 문자열 하나로 넘겨 주거든요. 인덱스 0은 여전히 프로그램 이름이라서 반복문이 1부터 시작해요.

vec!["sum", "4", "10", "x", "6"]이 주어졌을 때, 올바른 숫자인 인자는 모두 더하고, 숫자가 아닌 인자에는 경고를 출력하세요. 마지막으로 합계를 출력하세요.

힌트: &args[1..]를 반복하면서 인자를 숫자로 바꾸기에서처럼 match arg.parse::<i32>()를 쓰세요. 반복문 바깥에 let mut total = 0;을 두면 돼요.

정답 보기
fn main() {
let args = vec![
String::from("sum"),
String::from("4"),
String::from("10"),
String::from("x"),
String::from("6"),
];
let mut total = 0;
for arg in &args[1..] {
match arg.parse::<i32>() {
Ok(n) => total += n,
Err(_) => println!("'{arg}'은(는) 숫자가 아니라서 건너뛰어요"),
}
}
println!("합계: {total}");
}

실행 결과:

'x'은(는) 숫자가 아니라서 건너뛰어요
합계: 20
▶ 직접 실행해 보기

인자는 "4"조차도 모두 텍스트로 들어오기 때문에 하나하나 파싱해야 해요. 잘못된 입력을 메시지와 함께 건너뛰는 편이 프로그램이 죽는 것보다 친절해요. 프로그램에 따라서는 첫 번째 잘못된 값에서 return으로 멈추는 게 더 나을 수도 있어요.

  • std::env::args().collect()는 명령줄 인자를 Vec<String>으로 줘요. 인덱스 0은 항상 프로그램 이름이에요.
  • 인덱스로 접근하기 전에 args.len()을 확인하세요. 그래야 인자가 빠져도 패닉이 나지 않아요.
  • .parse()로 String 인자를 숫자로 바꿔요. 다른 곳에서 쓰던 것과 똑같아요.
  • 인자 처리는 &[String]을 받는 함수에 넣으세요. 그러면 직접 만든 목록으로 시험해 볼 수 있어요.
  • 단순한 인자 몇 개를 넘어서면, clap 크레이트가 많이 쓰이고 검증된 표준적인 선택이에요.

이해도 점검

1. env::args().collect()를 호출했을 때, 결과로 나온 Vec의 첫 번째 요소는 무엇일까요?

2. args에 인덱스로 접근하기 전에 args.len()을 확인하는 이유는 무엇일까요?

3. args에 직접 인덱스로 접근하는 방식에는 없고 clap 크레이트에는 있는 것은 무엇일까요?

점수: 0 / 3