본문 바로가기
빌드노트 검색

빌드노트

-n 파일에 따옴표를 붙여도 cat은 옵션으로 읽는다

따옴표로 감싼 -n도 GNU cat에는 옵션이다. 실제 파일과 표준 입력의 출력을 비교해 --로 옵션을 끝내는 경우와, 단독 -를 ./- 경로로 지정해야 하는 경우를 구분한다.

이름이 -n인 파일을 읽으려고 cat "-n"을 실행했는데 파일 내용이 나오지 않을 수 있다. stat에서는 같은 인자가 옵션 오류를 일으킨다. 따옴표는 셸이 인자를 만드는 방법을 제어하지만, 명령이 그 인자를 옵션으로 읽는 것까지 막지는 않는다. GNU cat·stat에서는 --로 옵션을 끝내거나 ./가 붙은 경로를 전달한다. 이름이 정확히 -인 파일은 -- 뒤에서도 표준 입력을 뜻할 수 있으므로 ./-로 지정한다.

현재 폴더에 그 파일들이 있다면 다음처럼 읽을 수 있다. 첫 명령은 이름이 -n인 파일, 두 번째는 이름이 -인 파일을 연다.

cat -- "-n"
cat "./-"

./는 현재 폴더를 가리키는 경로의 일부다. 파일 이름을 바꾸는 조치가 아니다. 다른 폴더의 파일이라면 그 파일을 가리키는 경로를 쓴다.

따옴표를 붙여도 명령에는 -n이 전달된다

Bash에서 "-n"을 인자로 쓰면 문법으로 사용한 따옴표는 제거되고 -n이 전달된다. 이 짧은 이름은 따옴표가 없어도 이미 인자 하나다. cat은 전달받은 -n을 줄 번호 옵션으로 해석한다. 인용했다고 해서 별도로 “이 인자는 파일 이름”이라는 표시가 붙지는 않는다. Bash 매뉴얼의 QUOTING과 Quote Removal 설명 (새 탭에서 열림)에서 이 셸 처리 규칙을 확인할 수 있다.

--는 그다음 단계에 관여한다. GNU 명령에 옵션 목록이 끝났다고 알려, 뒤의 -n을 파일 인자로 받게 한다. 실제로 사용할 옵션은 -- 앞에 둔다. GNU의 공통 옵션 설명 (새 탭에서 열림)은 이 규칙과 단독 -의 특별한 의미를 따로 설명한다.

공백이 든 이름이나 변수 값을 전달할 때 인용은 여전히 필요하다. 인자 하나를 보존하는 일과, 그 인자가 옵션인지 파일인지 결정하는 일은 서로 다른 단계다. 여기서는 xargs가 이름을 나누는 과정 없이, 명령에 인자 하나를 직접 넘긴다.

파일과 표준 입력에 다른 내용을 넣었다

다음은 운영 장애 기록이 아니라 새 파일로 확인한 비교다. 실행 환경은 Ubuntu 24.04의 WSL2, GNU Coreutils 9.4, Bash 5.2.21이었다. 두 파일의 내용과 표준 입력을 다르게 만들어 어느 쪽을 읽었는지 구별했다.

직접 비교하려면 GNU cat·stat이 있는 Linux의 Bash에서 다음 준비 명령을 실행한다. dash-demo가 이미 있으면 mkdir가 실패하고 뒤 명령은 실행되지 않는다. 그 경우 다른 새 폴더 이름으로 시작한다. set -C는 괄호 안에서 기존 파일의 덮어쓰기를 막는다.

mkdir dash-demo && cd dash-demo && (
    set -C
    printf 'FILE_N\n' > ./-n &&
    printf 'FILE_DASH\n' > ./-
)

준비가 성공했다면 같은 폴더에서 아래 세 명령을 실행한다. 파이프 앞의 printf는 STDIN 한 줄만 보내고 끝나므로 입력을 계속 기다리지 않는다.

printf 'STDIN\n' | cat "-n"
printf 'STDIN\n' | cat -- "-n"
printf 'STDIN\n' | cat "./-n"

순서대로 나온 실제 출력이다.

     1	STDIN
FILE_N
FILE_N

첫 명령은 -n을 옵션으로 소비했다. 파일 인자가 남지 않아 표준 입력을 읽고 줄 번호를 붙였다. 나머지 두 명령은 파일의 FILE_N을 출력했다. 따옴표 없는 cat -n도 첫 명령과 같았고, 세 경우 모두 종료 코드는 0이었다. 성공 종료만으로 원하는 파일을 읽었다고 판단할 수 없는 이유다. cat의 파일 생략 시 동작과 -n의 의미는 GNU cat 매뉴얼 (새 탭에서 열림)에 명시돼 있다.

stat에서는 -n이 유효한 옵션이 아니었다. 같은 파일 인자를 따옴표로 감싼 비교에서도 stat: invalid option -- 'n'과 종료 코드 1을 받았다. 따옴표만 추가하는 것으로는 이 오류가 해결되지 않았다.

-- 뒤의 단독 -도 표준 입력이었다

이름이 정확히 -일 때는 옵션 종료만으로 충분하지 않다. 같은 폴더에서 다음 비교로 확인할 수 있다.

printf 'STDIN\n' | cat "-"
printf 'STDIN\n' | cat -- "-"
printf 'STDIN\n' | cat "./-"

실제 출력은 다음과 같았고 모두 종료 코드 0이었다.

STDIN
STDIN
FILE_DASH

--는 옵션 해석을 끝냈지만 cat이 파일 인자 -를 표준 입력으로 취급하는 규칙까지 없애지는 않았다. ./-는 단독 -와 다른 문자열이므로 현재 폴더의 실제 파일을 열었다. --와 경로 표기를 함께 쓴 cat -- "./-"도 같은 파일 내용을 출력했다.

파일 크기를 조회할 때도 경로를 명확히 한다. 아래 명령에서 %n은 파일 이름, %s는 바이트 단위 크기이며, -c는 -- 앞에 놓인 실제 옵션이다. 이 형식 지정은 GNU stat 매뉴얼 (새 탭에서 열림)에 설명돼 있다.

stat -c '%n|%s' -- "-n"
stat -c '%n|%s' -- "./-"

실제 결과는 각각 -n|7, ./-|10이었다. 앞서 만든 개행 포함 파일 크기와 일치한다. 별도 비교에서는 stat에 단독 -를 넘기면 -- 유무와 관계없이 표준 입력 파이프의 정보가 나왔다. 파일 종류·크기는 fifo|0이었고, ./-에서는 regular file|10이었다. 이때의 0은 이름이 -인 파일이 비어 있다는 뜻이 아니다.

자기 명령에서 옵션 오류가 사라진 뒤 파일을 찾지 못한다는 오류가 나온다면 현재 폴더와 전달한 경로를 확인한다. ./는 파일이 있는 폴더로 이동했을 때의 표기다. 위 결과는 GNU 두 명령과 새 일반 파일에 한정되며 BSD·BusyBox 구현은 실행하지 않았다. 열린 터미널 입력을 기다리는 상황도 직접 재현하지 않았다. 이번에는 고정 입력을 끝까지 보내고 닫아, 어느 입력을 선택했는지만 비교했다.

← 전체 글 보기