Windows Paths

Windows Paths

일반적으로 Windows 경로명은 선택적인 드라이브 지정자(drive specifier)와 드라이브별 경로로 구성돼요. Windows 경로는 절대적이지만 여전히 현재 드라이브에 상대적일 수 있으며, 그런 경로는 / 또는 \ 구분자로 시작하고 UNC 경로나 \\?\로 시작하는 경로는 아니에요.

드라이브 지정으로 시작하는 경로는 완전해요. 대략적으로 드라이브 지정은 라틴 문자 뒤에 콜론이 오는 것, \\‹machine›\‹volume› 형태의 UNC 경로, 또는 REL\‹element›RED\‹element›가 아닌 다른 것으로 뒤따르는 \\?\ 형태 중 하나예요. (\\?\ 경로의 변형들은 아래에서 더 자세히 설명돼요.)

출처: Racket Reference

본문

Racket은 한 가지 방식으로 일반적인 Windows 경로 구문을 구현하지 않아요. Racket 밖에서 "C:rant.txt" 같은 경로명은 드라이브별 상대 경로일 수 있어요. 즉 그것은 "C:" 드라이브의 "rant.txt" 파일을 가리키지만, 파일의 완전한 경로는 "C:" 드라이브의 현재 작업 디렉토리에 의해 결정돼요. Racket은 드라이브별 작업 디렉토리를 지원하지 않아요 (current-directory 파라미터에 반영되듯 모든 드라이브에 걸친 작업 디렉토리 하나만 있어요). 결과적으로 Racket은 "C:rant.txt" 같은 경로를 암묵적으로 "C:\rant.txt"로 변환해요.

Racket 특유의: 경로가 /\가 뒤따르지 않는 드라이브 지정자 ‹letter›:로 시작할 때마다, 경로가 정화(cleanse)되면서 \가 삽입돼요.

그 외에는 Racket이 표준 Windows 경로 규칙을 따르지만, 표준 규칙으로 표현할 수 없는 경로를 다루기 위해 \\?\REL\\?\RED 규칙을 추가하고, \\?\ 경로에서 과도한 \를 다루는 규칙도 추가해요.

아래에서 ‹letter›는 라틴 문자(대소문자 무관)를, ‹machine›\/를 포함하지 않고 ?도 아닌 임의의 문자 시퀀스를, ‹volume›\/를 포함하지 않는 임의의 문자 시퀀스를, ‹element›\를 포함하지 않는 임의의 문자 시퀀스를 나타내요.

경로 요소가 경로에서 마지막인 경우, 그 요소의 끝에 붙는 공백과 .은 무시돼요. 단, 경로가 \\?\로 시작하거나 요소가 공백과 .만으로 구성된 경우는 예외예요.

장치에 접근하는 다음 특수 "파일"들은 \\?\로 시작하는 경로명을 제외하고 모든 디렉토리에 존재하며, 대소문자를 구분하지 않고, 마침표나 콜론 뒤의 모든 가능한 끝맺음을 가져요: "NUL", "CON", "PRN", "AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9".

\\?\ 경로를 제외하면 /\와 동등해요. \\?\ 경로와 UNC 경로의 시작을 제외하면, 인접한 여러 개의 /\는 단일 \로 간주돼요. \\?\ 경로에서 요소들은 단일 또는 이중 \로 구분될 수 있어요.

디렉토리는 뒤따르는 구분자가 있든 없든 접근할 수 있어요. \\?\가 아닌 경로의 경우 뒤따르는 구분자는 임의 개수의 /\일 수 있고, \\?\ 경로의 경우 뒤따르는 구분자는 단일 \여야 하며, \\?\‹letter›: 뒤에는 두 개의 \가 올 수 있어요.

\\?\ 경로를 제외하면, 경로 요소로서의 단일 .은 "현재 디렉토리"를 의미하고, 경로 요소로서의 ..은 "부모 디렉토리"를 의미해요. 드라이브 바로 뒤의 상위 디렉토리 경로 요소(즉 ..)는 무시돼요.

\\‹machine›\‹volume›(/\를 대체할 수 있음)으로 시작하는 경로명은 UNC 경로이며, 시작부 \\‹machine›\‹volume›은 드라이브 지정자로 간주돼요.

일반적으로 경로 요소는 #\x0에서 #\x1F 범위의 문자나 다음 문자 중 어느 것도 포함할 수 없어요:

< > : " / \ | ? *

\를 제외하면, 이런 문자를 포함하는 경로 요소는 \\?\ 경로를 사용해 접근할 수 있어요 (기본 파일시스템이 그 문자를 허용한다고 가정).

\\?\‹letter›:\으로 시작하는 경로명에서, \\?\‹letter›:\ 접두사는 경로가 두 개의 연속된 \로 끝나지 않는 비드라이브 요소를 가지면서 동시에 세 개 이상의 \ 시퀀스를 포함하지 않는 한 경로의 드라이브로 간주돼요. ‹letter› 앞의 \ 대신 두 개의 \가 올 수 있어요. /\ 대신 사용될 수 없어요 (하지만 /는 요소 이름에 사용될 수 있으며, 실제 디렉토리나 파일을 가리키지 않는 결과가 나올 뿐이에요).

\\?\UNC\‹machine›\‹volume›으로 시작하는 경로명에서, \\?\UNC\‹machine›\‹volume› 접두사는 경로가 두 개의 연속된 \로 끝나지 않고 세 개 이상의 \ 시퀀스를 포함하지 않는 한 경로의 드라이브로 간주돼요. UNC 앞의 \, UNC 뒤의 \, 그리고 ‹machine› 뒤의 \ 대신 두 개의 \가 올 수 있어요. UNC 부분의 문자는 대문자나 소문자일 수 있고, /\ 대신 사용될 수 없어요 (하지만 /는 요소 이름에 사용될 수 있어요).

Racket 특유의: \\?\REL\‹element› 또는 \\?\REL\\‹element›으로 시작하는 경로명은, 경로가 두 개의 연속된 \로 끝나지 않고 세 개 이상의 \ 시퀀스를 포함하지 않는 한 상대 경로예요. 이 Racket 특유 경로 형태는 Windows 경로에서 일반적으로 표현할 수 없는 요소(예: 공백으로 끝나는 마지막 요소)를 가진 상대 경로를 지원해요. REL 부분은 정확히 세 글자의 대문자여야 하고, /\ 대신 사용될 수 없어요. 경로가 \\?\REL\..으로 시작하면, 경로가 \..의 반복으로 계속되는 한 각 요소는 상위 디렉토리 요소로 간주되고, 상위 디렉토리 요소들을 구분하는 데 단일 \가 사용되어야 해요. 요소를 구분하는 데 두 번째 \가 사용되는 즉시, 또는 ..이 아닌 요소를 만나는 즉시, 나머지 요소들은 전부 리터럴(절대 상위 디렉토리 요소가 아님)이에요. \\?\REL 경로 값이 문자열로 변환될 때(또는 경로 값이 쓰이거나 표시될 때), 그 문자열은 시작부 \\?\REL이나 바로 뒤따르는 \를 포함하지 않아요. 경로 값을 바이트 문자열로 변환하면 \\?\REL 접두사가 보존돼요.

Racket 특유의: \\?\RED\‹element› 또는 \\?\RED\\‹element›으로 시작하는 경로명은, 경로가 두 개의 연속된 \로 끝나지 않고 세 개 이상의 \ 시퀀스를 포함하지 않는 한 드라이브 상대 경로예요. 이 Racket 특유 경로 형태는 Windows 경로에서 일반적으로 표현할 수 없는 요소를 가진 드라이브 상대 경로(즉 드라이브가 주어지면 절대적)를 지원해요. RED 부분은 정확히 세 글자의 대문자여야 하고, /\ 대신 사용될 수 없어요. \\?\REL 경로와 달리 .. 요소는 항상 리터럴 경로 요소예요. \\?\RED 경로 값이 문자열로 변환될 때(또는 경로 값이 쓰이거나 표시될 때), 그 문자열은 시작부 \\?\RED를 포함하지 않고 단일 시작부 \를 포함해요. 경로 값을 바이트 문자열로 변환하면 \\?\RED 접두사가 보존돼요.

Windows 경로로서는 달리 형태가 잘못된 문자 시퀀스에 의미를 제공하는 세 가지 추가 Racket 특유 규칙이 있어요:

Racket 특유의: \\?\‹any›\\ 형태의 경로명에서 ‹any›‹letter›:\‹letter›: 이외의 임의의 비어 있지 않은 문자 시퀀스이면, 전체 경로가 경로의 (존재하지 않는) 드라이브로 간주돼요.

Racket 특유의: \\?\‹any›\\\‹elements› 형태의 경로명에서 ‹any›가 임의의 비어 있지 않은 문자 시퀀스이고 ‹elements›\로 시작하지 않고 두 개의 \로 끝나지 않으며 세 개의 \ 시퀀스를 포함하지 않는 임의의 시퀀스이면, \\?\‹any›\\은 경로의 (존재하지 않는) 드라이브로 간주돼요.

Racket 특유의: \\?\로 시작하고 앞선 글머리 기호의 패턴 중 어느 것과도 일치하지 않는 경로명에서, \\?\은 경로의 (존재하지 않는) 드라이브로 간주돼요.

Racket 밖에서는 \\?\ 경로를 제외하면 경로명은 파일 경로로 사용될 때 일반적으로 259자, 디렉토리 경로로 사용될 때 247자로 제한돼요. Racket은 내부적으로 247자보다 긴 경로명을 \\?\ 형태로 변환해 그 제한을 피해요. 이 경우 경로는 먼저 구문상 단순화돼요(simplify-path의 의미에서). 운영체제는 32,000자 정도보다 긴 \\?\ 경로를 통해 파일에 접근할 수 없어요.

위 설명이 "문자"라고 말하는 곳에, 바이트 문자열을 경로로 해석할 때는 "바이트"를 대입해요. Windows 경로의 바이트 인코딩은 ASCII 문자를 보존하고, 위에서 언급한 모든 특수 문자는 ASCII이므로 모든 규칙이 같아요.

\ 경로 구분자는 Racket 문자열에서 이스케이프 문자라는 점에 주의하세요. 따라서 경로 \\?\REL\..\\..은 문자열로는 "\\\\?\\REL\\..\\\\.."로 써야 해요.

디렉토리 구분자로 끝나는 경로는 구문상 디렉토리를 가리켜요. 또한 마지막 요소가 (\\?\ 형태로 인용되지 않은) 같은 디렉토리 또는 상위 디렉토리 표시자이거나 루트를 가리키면 경로는 구문상 디렉토리를 가리켜요.

심볼릭 링크를 지원하는 Windows 변형에서도, 경로의 상위 디렉토리 .. 표시자는 링크에 민감하지 않게 구문상으로 해석돼요. 예를 들어 경로가 d\..\f로 끝나고 dd와 다른 부모를 가진 디렉토리를 참조하는 심볼릭 링크를 가리키더라도, 경로는 여전히 d와 같은 디렉토리의 f를 가리켜요. 상대 경로 링크는 \\?\REL 경로가 접두사로 붙은 것처럼 해석되지만, ... 요소가 경로 전체에서 허용되고 임의 개수의 중복 \ 구분자가 허용된다는 점이 달라요.

Windows 경로는 다음과 같이 정화돼요: \\?\로 시작하는 경로에서 중복 \가 제거되고, 상위 디렉토리 표시자와 리터럴 경로 요소를 구분하는 여분의 \가 이미 없으면 \\?\REL에 여분의 \가 추가되며, \\?\RED 뒤에도 여분의 \가 이미 없으면 유사하게 추가돼요. 다른 경로에서는 (공유 폴더 이름의 시작을 제외하면) 여러 개의 /\가 단일 / 또는 \로 변환되고, 드라이브 지정의 콜론 뒤에 \가 없으면 삽입돼요.

(bytes->path-element bstr)의 경우, bstr/, 콜론, 끝에 붙는 점, 끝에 붙는 공백, 특수 장치 이름(예: "aux")이 \\?\REL 접두사를 사용해 경로 요소의 리터럴 부분으로 인코딩돼요. bstr 인자는 \를 포함해서는 안 되며, 그렇지 않으면 exn:fail:contract 예외가 일어나요.

(path-element->bytes path) 또는 (path-element->string path)의 경우, path의 바이트 문자열 형태가 \\?\REL로 시작하면 그 접두사는 결과에 포함되지 않아요.

(build-path base-path sub-path ...)의 경우, base-path의 마지막 요소와 마지막 sub-path를 제외한 모든 요소의 끝에 붙는 공백과 마침표가 제거돼요 (\\?\로 시작하는 요소는 제외). base-path\\?\로 시작하면, 각 비-\\?\REL\ 및 비-\\?\RED\ sub-path가 추가된 후 추가분의 모든 /\로 변환되고, 여러 개의 연속된 \가 단일 \로 변환되며, 추가된 . 요소는 제거되고, 추가된 .. 요소는 앞선 요소와 함께 제거돼요. 이런 변환은 결과의 원래 base-path 부분이나 \\?\REL\, \\?\RED\, sub-path에는 수행되지 않아요. \\?\REL\ 또는 \\?\RED\ sub-path가 비-\\?\ base-path에 추가되면, base-path(\\?\REL\ 또는 \\?\RED\ sub-path까지의 추가분 포함)가 단순화되어 \\?\ 경로로 변환돼요. 다른 경우에는 경로의 루트 의미가 바뀌지 않도록 경로를 결합하기 전에 \가 추가되거나 제거될 수 있어요 (예: //xy를 결합하면 /x/y가 생성되는데, //x/y는 드라이브 상대 경로가 아니라 UNC 경로가 되기 때문이에요).

(simplify-path path use-filesystem?)의 경우 path가 확장되고, path\\?\로 시작하지 않으면 끝에 붙는 공백과 마침표가 제거되고, 드라이브 지정의 콜론 뒤에 /가 없으면 삽입되며, 요소들이 있고 여분의 \가 이미 없으면 루트로서 \\?\ 뒤에 \가 삽입돼요. 그 외에 path에 표시자가 없거나 중복 구분자가 없으면 path가 돌려져요.

(split-path path)base, name, must-be-dir?을 생성할 때, \\?\로 시작하지 않는 경로를 분할하면 \\?\로 시작하는 부분들이 생성될 수 있어요. 예를 들어 C:/x /aux/를 두 번 분할하면 \\?\REL\\x \\?\REL\\aux가 생성돼요. 이런 경우 x 뒤의 끝에 붙는 공백을 보존하고 "aux" 파일 대신 AUX 장치를 가리키는 것을 피하기 위해 \\?\가 필요해요.

Windows Path Representation

Windows의 경로는 본래 UTF-16 코드 유닛의 시퀀스이며, 그 시퀀스는 짝 없는 서로게이트(unpaired surrogate)를 포함할 수 있어요. 이 시퀀스는 UTF-8의 확장을 통해 바이트 문자열로 인코딩되며, UTF-16 코드 유닛 시퀀스의 짝 없는 서로게이트는 서로게이트가 아닌 값인 것처럼 변환돼요. 확장 인코딩은 Windows에서 bytes-open-converter에 대한 "platform-UTF-16""platform-UTF-8" 인코딩으로 구현돼요.

Racket의 Windows 경로 내부 표현은 바이트 문자열이므로 path->bytesbytes->path는 항상 역관계(inverse)예요. 경로를 네이티브 UTF-16 코드 유닛 시퀀스로 변환할 때, platform-UTF-8 디코딩 오류 자리에 #\tab이 사용돼요 (#\uFFFD와 달리 탭이 일반적으로 Windows 경로에서 문자로 허용되지 않는다는 근거에서요).

Windows 경로는 platform-UTF-8 인코딩을 디코딩 오류 자리에 #\uFFFD를 둔 UTF-8 인코딩으로 취급해 문자열로 변환돼요. 마찬가지로 문자열은 UTF-8 인코딩(이 경우 오류가 발생할 수 없음)으로 경로에 변환돼요.

더 알아보기