명령줄 인자 받기

RustBeginner
지금 연습하기

소개

명령줄 프로그램은 실행 파일 이름 뒤에 입력된 값을 받고, 일반적인 결과를 표준 출력에 기록하며, 표준 오류와 0이 아닌 종료 상태를 통해 실패를 알립니다. 이러한 프로세스 경계를 사용하면 사람과 스크립트가 성공과 실패를 구분할 수 있습니다.

이 실습에서는 간단한 텍스트 검색 명령을 완성합니다. 검색 함수는 패키지의 라이브러리 소스에 이미 준비되어 있으므로, 각 단계에서는 인자 구문 분석, 파일 읽기, 검색 결과가 없을 때의 처리, 최종 프로세스 통신에 집중합니다.

명령줄 인자 구문 분석

이 단계에서는 프로세스 인자에서 검색어와 파일 경로를 확인하고 추출합니다.

프로젝트 경로는 /home/labex/project/mini-search입니다. src/main.rs는 명령줄 바이너리이고, src/lib.rs에는 다음 단계에서 사용할 재사용 가능한 검색 로직이 준비되어 있습니다. 프로젝트로 이동한 다음 바이너리 소스 파일을 엽니다.

cd /home/labex/project/mini-search
nano src/main.rs

env::args()는 실행 파일 경로를 인덱스 0에 포함하여 모든 인자를 String으로 생성합니다. 준비된 main 함수는 이 인자들을 벡터에 모읍니다. 그런 다음 &argsargs: &[String] 매개변수에 전달합니다.

&[String] 타입은 String 값의 슬라이스입니다. 벡터 요소를 읽기 전용으로 빌려 보는 창이라고 할 수 있습니다. &str과 같은 대여 개념을 사용하지만, 텍스트 바이트가 아니라 String 요소의 시퀀스를 봅니다. run이 요소를 읽는 동안 벡터의 소유권은 main이 유지합니다.

데이터 흐름은 다음과 같습니다.

shell words → env::args() → Vec<String> in main → borrowed &[String] in run

run 안의 자리 표시자인 Err(...)를 다음 코드로 바꿉니다.

    if args.len() != 3 {
        return Err(String::from("usage: mini-search <query> <file>"));
    }
    let query = args[1].clone();
    let path = args[2].clone();
    Ok(vec![format!("Query: {query}"), format!("File: {path}")])

항목이 정확히 3개라는 것은 실행 파일 이름과 사용자가 입력한 인자 2개를 의미합니다. 인자 개수가 올바르지 않으면 명시적인 return Err(...)가 즉시 run을 종료합니다. 이는 함수의 마지막 표현식으로 반환하는 방식과 달리 **조기 반환(early return)**입니다. 따라서 아래 코드는 인자 개수가 올바를 때만 실행됩니다.

대여한 슬라이스를 인덱스로 접근하면 대여된 문자열을 얻습니다. 두 번의 clone 호출은 검색어와 경로만 소유된 복사본으로 만들어 run의 나머지 부분에서 로컬 소유 값으로 다루도록 합니다. 이는 의도적인 소유권 선택이며, 이동 오류를 해결하기 위한 일반적인 방법은 아닙니다. 그런 다음 vec![first, second] 매크로가 두 요소로 이루어진 벡터를 만들고, Ok가 이 두 개의 확인용 행을 임시로 반환합니다.

Ctrl+O를 눌러 저장하고 Enter를 누른 다음, Ctrl+X로 종료합니다. 인자 2개를 사용해 명령을 실행합니다.

cargo run --quiet -- rust data/notes.txt

첫 번째 --quiet는 Cargo 자체의 메시지를 줄입니다. 별도의 --는 Cargo가 옵션 해석을 중지하도록 합니다. -- 뒤의 모든 내용은 프로그램에 전달됩니다.

Query: rust
File: data/notes.txt

이 출력으로 인자가 예상한 위치에 전달되었음을 확인할 수 있습니다.

파일 읽기 및 라이브러리 로직 호출

이 단계에서는 임시 확인 결과를 실제 파일 읽기와 검색 결과로 바꿉니다.

바이너리 소스 파일을 엽니다.

nano src/main.rs

use std::env; 아래에 다음 import를 추가합니다.

use std::fs;
use mini_search::find_lines;

패키지 이름 mini-search는 Rust 크레이트 이름 mini_search가 됩니다. find_lines에 준비된 pub가 있으므로 라이브러리 크레이트 외부에서도 이 함수를 사용할 수 있습니다. 이 함수를 import하면 바이너리에서 src/lib.rs의 공개 함수를 호출할 수 있습니다. 검색 로직을 라이브러리에 두면 재사용 가능한 데이터 처리와 프로세스별 인자 처리를 분리할 수 있습니다. 이는 이후 모듈 실습에서 자세히 배우는 라이브러리/바이너리 및 공개 범위 모델을 미리 살펴보는 간단한 예입니다.

임시 Ok(vec![...]) 행을 다음 코드로 바꿉니다.

    let contents = fs::read_to_string(&path)
        .map_err(|error| format!("could not read {path}: {error}"))?;
    Ok(find_lines(&query, &contents))

읽기 오류에는 요청한 경로가 포함되며, ?를 사용해 오류를 전달합니다. 읽기에 성공하면 바이너리는 검색어와 파일 내용을 빌리고, 라이브러리는 일치하는 행을 소유된 값으로 반환합니다.

저장하고 종료한 다음, 확인하고 실행합니다.

cargo check
cargo run --quiet -- rust data/notes.txt
Rust makes ownership explicit.
Cargo builds Rust packages.
Rust tools help beginners.

준비된 mainOk 분기가 일반 검색 결과를 표준 출력에 출력합니다.

검색 결과가 없을 때 유용한 오류로 처리

이 단계에서는 결과가 있는 성공적인 검색과, 유효한 검색이지만 일치하는 항목을 찾지 못한 경우를 구분합니다.

소스 파일을 엽니다.

nano src/main.rs

Ok(find_lines(&query, &contents))를 다음 코드로 바꿉니다.

    let matches = find_lines(&query, &contents);
    if matches.is_empty() {
        return Err(format!("no lines matched '{query}'"));
    }
    Ok(matches)

빈 벡터도 확인할 수 있지만, 검색 결과가 없었다는 사실을 설명하면 명령을 더 유용하게 사용할 수 있습니다. 저장하고 종료한 다음, 존재하지 않는 검색어를 사용해 봅니다.

cargo run --quiet -- python data/notes.txt
no lines matched 'python'

현재 중간 단계에서는 준비된 Err 분기가 여전히 표준 출력으로 메시지를 출력하고 정상적으로 종료합니다. 다음 단계에서 오류가 올바른 프로세스 동작을 하도록 수정합니다.

오류를 stderr로 보내고 0이 아닌 상태로 종료

이 단계에서는 일반 출력과 실패를 분리하여 명령줄 경계를 완성합니다.

표준 출력(stdout)은 요청한 결과를 전달합니다. 표준 오류(stderr)는 진단 메시지를 독립적으로 전달합니다. 종료 상태가 0이면 성공이고, 0이 아니면 실패입니다. 바이너리 소스 파일을 엽니다.

nano src/main.rs

기존 표준 라이브러리 import 아래에 다음 import를 추가합니다.

use std::process;

한 줄로 작성된 Err(error) 분기를 다음 코드로 바꿉니다.

        Err(error) => {
            eprintln!("{error}");
            process::exit(1);
        }

eprintln!은 한 줄을 stderr에 기록합니다. process::exit(1)은 상태 1로 프로세스를 즉시 종료합니다. 저장하고 종료한 다음, Cargo가 자체 실패 메시지를 추가하지 않고 바이너리를 직접 실행할 수 있도록 한 번 빌드합니다.

cargo build --quiet
./target/debug/mini-search python data/notes.txt

진단 메시지는 다음과 같이 표시됩니다.

no lines matched 'python'

직전에 실행한 명령의 상태를 즉시 출력합니다.

echo $?

echo는 인자를 출력하고, 셸은 $?를 가장 최근 명령의 종료 상태로 확장합니다.

1

이제 같은 경계가 잘못된 사용법과 존재하지 않는 파일에도 적용됩니다. ./target/debug/mini-search를 실행하면 사용법 메시지가 표시되고, data/missing.txt와 같은 경로를 지정하면 파일 읽기 오류가 표시됩니다. 두 경우 모두 stderr에만 출력하고 상태 1로 종료합니다. 반면 일치하는 검색은 결과를 stdout에 출력하고 상태 0으로 종료합니다.

요약

명령줄 인자를 수집하고 확인했으며, Cargo의 -- 구분자를 사용했습니다. 또한 검색 로직을 준비된 라이브러리 경계에 두고 요청한 파일을 읽은 다음, 성공 사례와 세 가지 실패 사례에 대해 일반적인 stdout, stderr 및 종료 상태 동작을 완성했습니다.