Hey, Scripting Guy!정규식 유효성 검사

Scripting Guys

이 기사의 코드 다운로드: HeyScriptingGuy2008_01.exe (150KB)

사람은 누구나 실수를 하게 마련입니다. 아무도 실수를 안 한다면 애초에 Scripting Guys 팀이 생기지도 않았겠죠? 실수에도 긍정적인 측면이 있다면 어떤 점이라 생각하십니까? 아마 대부분 실수를 통해 무언가를 배울 수 있다는 점을 꼽겠죠? 그런데 그게 아닙니다. 실수를 통해 배울 수 있는 게 있다면 처음부터 실수를 하지 않는 편이 훨씬 낫다는 것뿐입니다.

참고로 말씀 드리자면, 오늘만은 Microsoft Bob에 대한 농담을 자제해 주십시오. 예전에 Microsoft Bob을 놀려대는 실수를 저지른 적이 있는데, 그 당시 실수에 대해 뼈저리게 느끼게 되었습니다. 그 후로 다시는 Microsoft Bob에 대해 입 밖에 내지 않게 되었죠.

솔직히 우리는 아무리 생각해 봐도 실수의 긍정적인 면을 찾지 못하겠습니다. 우리가 실수를 줄이기 위해 무던히 애쓰는 이유가 바로 이 때문이겠지요. 물론 우리가 하는 작업량을 줄이는 것이 실수를 가장 확실하게 줄이는 방법이겠지만, 그럴 수는 없겠죠.

우리 스스로 실수를 줄이기 위해 노력하는 것도 좋지만 함께 일하는 다른 사람들이 계속 실수를 한다면 소용이 없습니다. 다시 한 번 말하지만 Microsoft Bob에 대한 농담은 자제해 주세요. 아마 데이터베이스 프로그램이나 Active Directory®의 프런트 엔드를 작성해 본 경험이 한 번쯤은 다들 있으실 겁니다. 그렇다면 여러분도 잘 알겠지만 프런트 엔드 데이터 입력 프로그램은 직접 데이터를 입력하는 사람과 다를 바가 없습니다.

Ken과 같이 첫 번째 문자는 대문자이고 나머지 문자는 소문자인 형식으로 이름을 입력하도록 했는데 사용자가 KEN과 같은 형식으로 이름을 입력하면 어떻게 될까요? 또한 2007/01/23과 같은 형식으로 날짜를 입력해야 하지만 사용자가 2007년 1월 23일로 입력할 수도 있습니다. 더 많은 예를 들지 않아도 이쯤에서 이해했으리라 믿습니다. 앞서 설명했듯이 프런트 엔드 데이터 입력 프로그램은 직접 데이터를 입력하는 사람과 다를 바가 없다는 것입니다. 그리고 사람이 데이터를 입력하면 실수가 발생하게 마련입니다. 이러한 실수를 방지할 수 있는 특별한 조치를 취하지 않는다면 말이죠.

데이터 유효성 확인

Scripting Games

Scripting Guys가 일년 내내 기다리는 행사가 하나 있습니다. 작년과 재작년 2월에 열렸던 스크립트 센터에 참가했던 독자는 "The Winter Scripting Games" 행사가 아닐까 생각하겠지만, 아쉽게도 아닙니다. 오히려 Scripting Guys는 Scripting Games 대회를 성공적으로 마무리한 후에 밀린 잠을 자며 푹 쉴 수 있는 날이 오기를 학수고대합니다. 2주 동안 쉬지 않고 다양한 행사를 진행해야 하는 강행군이니까요.

우리에게 연중 최고의 행사가 된 Scripting Games 대회의 폐막을 맞이하기 위해 2008 Winter Scripting Games를 시작할 때가 되었습니다. 2008년 2월 15일에서 3월 3일까지 2주가 넘는 기간 동안 스크립트 센터에서 흥미로운 스크립팅 관련 행사와 최고 권위의 스크립팅 대회가 펼쳐집니다.

Scripting Games는 스크립팅 실력을 가늠하고 향상시킬 좋은 기회가 됩니다. 그리고 이번에는 대회를 마친 후 며칠 더 푹 쉴 만한 빌미를 만들기 위해 예년보다 훨씬 대규모로 한층 흥미롭게 준비했습니다. 이번에도 초보 사용자와 고급 사용자의 두 부문으로 나뉘어 행사가 진행됩니다. 따라서 스크립팅 초보자이든, 나이가 많든(또는 젊든), 아니면 어중간한 위치에 있든, 누구나 즐길 수 있는 대회가 될 것입니다.

올해 대회는 예년 대회와 어떻게 다를까요? 또 다른 스크립트 언어가 추가되었습니다. 기존의 VBScript와 Windows PowerShell뿐만 아니라 Perl로도 대회가 펼쳐집니다. Perl이 새로운 언어는 아니지만 이 대회의 대상 언어로는 처음 추가되었습니다. 이 외에도 여러 가지 흥미로운 행사가 있겠지만 행사 몇 달 전에 이 기사를 작성하는 관계로 아직은 정확한 내용을 알 수 없습니다. 자세한 내용은 스크립트 센터에서 확인하시기 바랍니다.

궁금해 하실 분들을 위해 미리 말씀 드리자면, 상품도 준비되어 있습니다. 구체적으로 상품이 뭐냐고요? 여기서 다 알려드리면 재미가 없지 않을까요? 상품 내역도 스크립트 센터(microsoft.com/technet/scriptcenter/funzone/games/default.mspx)에서 확인하시기 바랍니다.

기초적인 데이터 입력 유효성 검사 정도는 이미 다들 수행하고 있으리라 생각합니다. 그리고 간단한 데이터 입력 유효성 검사만으로도 충분한 경우가 많습니다. strName이라는 문자열 값의 문자 수가 20개 이하인지를 확인하려는 경우 다음과 같은 간단한 코드만 있으면 됩니다.

If Len(strName) > 20 Then
    Wscript.Echo "Invalid name; a " & _
    "name must have 20 or fewer characters."
End If

하지만 strName이 4자리 숫자와 대문자 2개의 형식이어야 하는 새로운 부품 번호(예: 1234AB)라고 생각해 보십시오. 간단한 VBScript만 사용해 strName이 부품 번호의 필수 패턴에 맞는지 확인할 수 있을까요? 그렇습니다. 가능합니다. 하지만 바람직한 방법은 아닙니다. 대신 정규식을 사용하는 것이 좋습니다.

값에 숫자만 있는지 확인

정규식의 역사는 1950년대로 거슬러 올라갑니다. 당시에는 정규식이 수학적 표기법의 한 형태로 소개되었습니다. 그리고 1960년대에 이 수학적 모델이 QED 텍스트 편집기와 통합되면서 텍스트 파일에서 문자 패턴을 검색할 수 있게 되었습니다. 그 후에는... 그런데 독자분들 반응이? 아! 정규식의 역사에 별로 관심이 없으신가 보군요. 좋습니다. 그렇다면 바로 본론으로 들어가지요.

변수(예제의 경우 strSearchString 변수)에 숫자만 들어 있는지 확인하는 방법은 그림 1에서 볼 수 있습니다.

Figure 1 숫자만 필요

Set objRegEx = _
  CreateObject("VBScript.RegExp")

objRegEx.Global = True   
objRegEx.Pattern = "\D"

strSearchString = "353627"

Set colMatches = _
  objRegEx.Execute(strSearchString)  

If colMatches.Count > 0 Then
    Wscript.Echo "Invalid: a " & _
      "non-numeric character was found."
Else
    Wscript.Echo "This is a valid entry."
End If

스크립트에서는 먼저 VBScript.RegExp 개체의 인스턴스를 생성합니다. 그렇습니다. 이 개체를 통해 스크립트에 정규식을 사용할 수 있습니다. 이것을 언급하고 싶었습니다. 다음에는 정규식 개체의 두 가지 핵심 속성인 Global과 Pattern에 값을 할당합니다.

Global 속성은 스크립트에서 특정 패턴의 모든 항목을 검색해야 할지, 처음으로 나타나는 해당 패턴만 찾고 검색을 중지해야 할지를 결정합니다. 일반적으로 이 값은 True로 설정하는 것이 좋습니다. 즉, 패턴의 모든 항목을 검색합니다. 참고로 기본값은 False이지만, True로 설정하는 것이 좋은 이유는 패턴의 모든 항목을 찾아야 하는 경우가 많기 때문입니다. 그렇지 않은 경우에도 패턴이 처음으로 나타나는 항목만 찾는 데 따른 이점은 문자열의 중간에서 검색이 중단되면 끝까지 검색할 때보다 스크립트 실행 속도가 약간 빨라지는 것 외에는 없습니다. 이론적으로는 False로 설정하는 것이 효과적인 방법처럼 보이지만 실제로 전체 문자열을 검색하더라도 실행 속도가 워낙 빨라서 차이를 느끼지 못합니다.

더 중요하게 살펴보아야 할 것은 Pattern인데, 여기에서 검색할 문자 및 문자 청사진을 지정합니다. 샘플 스크립트에서는 0에서 9 사이의 숫자 이외의 문자를 찾습니다. 이러한 문자(글자, 문장 부호 등)가 검색되면 strSearchString에 유효하지 않은 값이 할당된 것이겠죠. 정규식에서는 숫자가 아닌 문자를 찾는 데 \D라는 구문이 사용됩니다. 따라서 Pattern 속성에 \D 값을 할당합니다.

참고로, \D를 사용하면 숫자 이외의 문자가 검색되는지 어떻게 알게 되었을까요? 이와 관련해서는 이야기가 깁니다. 몇 년 전 MSDN® 기사(msdn2.microsoft.com/1400241x)의 VBScript 언어 참조에서 이에 대해 다루었습니다.

Global 속성과 Pattern 속성에 값을 할당한 후에는 다음과 같이 strSearchString 변수에 값을 할당합니다.

strSearchString = "353627"

문자열에 숫자 이외의 문자가 있는지 확인하려면 어떻게 해야 할까요? 간단합니다. 변수에서 숫자 이외의 문자를 검색하는 Execute 메서드를 호출하기만 하면 됩니다.

Set colMatches = _
objRegEx.Execute(strSearchString)

Execute 메서드를 호출하면 Pattern에 일치하는 검색된 항목이 자동으로 Matches 컬렉션에 저장됩니다. 샘플 스크립트에서는 이 컬렉션의 이름을 colMatches로 지정했습니다. 변수에 숫자 이외의 문자가 있는지 여부(샘플에는 있음)를 확인하려면 컬렉션에 있는 항목 수를 나타내는 컬렉션의 Count 속성 값을 확인하면 됩니다.

If colMatches.Count > 0 Then
    Wscript.Echo "Invalid: a " & _
      "non-numeric character was found."
Else
    Wscript.Echo "This is a valid entry."
End If

Count의 값이 0보다 크면 변수 값에서 숫자 이외의 문자가 발견된 것입니다. 값의 문자가 모두 숫자이면 이 컬렉션이 비어 있으므로 Count가 0이 됩니다. 다음으로 샘플 스크립트에서 숫자 이외의 문자가 발견되었다는 사실을 출력합니다. 실제 스크립트나 데이터베이스 프런트 엔드에서도 비슷한 메시지를 출력하고 사용자가 다시 시도할 수 있도록 루프를 실행합니다. 자세한 내용은 여러분의 몫으로 남겨 두겠습니다. 직접 살펴보시기 바랍니다. Scripting Guys는 이외에도 신경을 쓸 일이 너무 많습니다.

그게 뭐냐고요? 이를테면 TechNet Magazine에서 일하는 사람들이 매달 우리가 보내는 기사를 실제로 꼼꼼히 읽어보면 어쩌나 하는 걱정 등이 있죠. 우리 기사를 읽지 않고 자신들이 실수하고 있다는 사실을 계속 모른 채로 있어 주기만을 바라고 있거든요.

IsNumeric 함수를 사용하여 strSearchString에 숫자가 포함되어 있는지 여부를 확인할 수는 없냐고요? 좋은 질문입니다. strSearchString이 아무 숫자나 될 수 있다면 물론 가능합니다. 하지만 strSearchString이 양의 정수여야 한다면 문제가 발생할 수 있습니다. IsNumeric은 다음 두 숫자를 모두 유효한 숫자로 식별합니다.

-45
672.69

왜 이 두 숫자가 모두 유효한 숫자로 식별될까요? 양의 정수가 아닐 뿐이지, 유효한 숫자인 것은 사실이니까요. 샘플 스크립트의 정규식에서는 이러한 숫자를 잘못된 항목으로 표시하겠지만, 그것은 숫자 이외의 문자인 빼기 기호(–)와 마침표(.)를 인식하기 때문이지 잘못된 숫자이기 때문은 아닙니다. 여러분이 이 칼럼을 읽을 만한 이유가 되는 가장 중요한 내용이 바로 이 부분입니다.

뭐라고요? 이 정도로는 부족하다고요? 이번 달 독자들은 상당히 까다롭군요. 좋습니다. 그러면 정규식을 사용해 구현할 수 있는 다른 데이터 입력 유효성 검사 유형을 좀 더 살펴보도록 하겠습니다.

값에 숫자가 없는지 확인

앞에서는 값에 숫자만 있는지를 확인하는 방법을 살펴보았습니다. 그렇다면 반대로 값에 숫자가 없는지를 확인하려는 경우에는 어떻게 해야 할까요? 이 경우 다음 검색 패턴을 사용할 수 있습니다.

objRegEx.Pattern = "[0-9]"

이 코드는 어떻게 작동하는 것일까요? 험난하고 알 수 없는 정규식의 세계에서 대괄호 문자([ 및 ])는 문자 집합을 지정하거나, 이 예제 코드에서처럼 문자의 범위를 지정하는 데 사용할 수 있습니다. 그렇다면 Pattern [0-9]는 무엇을 의미할까요? 바로 0 ~ 9 범위에 있는 모든 문자, 즉 모든 숫자 값을 검색함을 의미합니다. 값에서 짝수만 허용하려면(즉, 값에 1, 3, 5, 7, 9가 없는지 확인하려는 경우) 다음과 같은 패턴을 사용합니다.

objRegEx.Pattern = "[13579]"

여기서는 하이픈을 사용하지 않았기 때문에 문자의 범위를 검색하는 것이 아닙니다. 구체적으로 말하면 1, 3, 5, 7, 9라는 실제 문자를 검색하는 것입니다.

물론 [0-9]라는 패턴을 사용하면 숫자만 검색되고 문장 부호 또는 글자도 아니고 숫자도 아닌 다른 문자는 검색되지 않습니다. 글자 이외의 문자를 찾는 패턴을 만들 수도 있을까요? 당연히 가능합니다. 정규식의 용도에는 거의 한계가 없으니까요.

objRegEx.Pattern = "[^A-Z][^a-z]"

이 예에서는 [^A-Z]라는 패턴과 [^a-z]라는 패턴을 함께 사용하여 해당 문자를 검색합니다. 쉽게 알 수 있듯이 A-Z는 대문자 A에서 Z까지의 문자 범위를 나타냅니다. 그런데 ^ 문자는 무엇을 의미할까요? 대괄호 안에 사용된 경우에 ^는 "해당 문자 집합에 포함되지 않은 모든 문자 검색"을 뜻합니다. 따라서 [^A-Z]는 "대문자가 아닌 문자 검색"을 의미합니다. 마찬가지로 [^a-z]는 "소문자가 아닌 문자 검색"을 의미합니다. 결과적으로 이 패턴은 대문자나 소문자가 아닌 모든 문자를 찾는 것임을 나타냅니다. 이러한 문자로는 숫자, 문장 부호 및 키보드에 있는 기타 특수 문자가 있습니다.

이러한 패턴을 사용하는 대신 IgnoreCase 속성을 True로 설정할 수도 있습니다.

objRegEx.IgnoreCase = True

이렇게 하면 검색 시 대/소문자를 구분하지 않습니다. 따라서 다음 Pattern을 IgnoreCase 속성과 함께 사용하면 대문자나 소문자가 아닌 모든 문자가 검색됩니다.

objRegEx.Pattern = "[^A-Z]"

부품 번호의 유효성 확인

이제 좀 더 복잡한 용도로 응용해 보겠습니다. regexlib.com 웹 사이트에서도 몇 가지 정규식 응용 예제를 살펴볼 수 있습니다. 기사의 앞 부분에서 strName이 4자리 숫자와 대문자 2개의 형식이어야 하는 새로운 부품 번호(예: 1234AB)인 경우를 예로 들었습니다. 변수가 이 패턴에 맞는지 확인하려면 어떻게 해야 할까요? 바로 다음과 같이 확인하면 됩니다.

objRegEx.Pattern = "^\d{4}[A-Z]{2}"

이 패턴은 무엇을 의미할까요? 이런 우연이 있을까요? 바로 우리가 설명하려고 했던 내용이군요. 이 경우 세 가지 검색 조건을 사용합니다.

^\d{4} 

\d는 숫자(0에서 9 사이의 문자)를 검색함을 의미합니다. {4}는 무엇을 의미할까요? {4}는 4자리 숫자를 정확히 검색한다는 의미입니다. 결과적으로 연속된 4개의 숫자를 검색하는 것입니다.

다음으로 ^ 문자는 왜 사용된 걸까요? 대괄호 밖에 있는 ^는 값이 뒤에 나오는 패턴으로 시작해야 함을 의미합니다. 즉, AA1234AB는 일치하는 결과로 표시되지 않습니다. 그 이유는 값이 연속된 4개의 숫자가 아니라 AA로 시작하기 때문입니다. 필요에 따라서는 $ 문자를 사용하여 문자열이 특정 패턴으로 끝나야 함을 지정할 수도 있습니다. 하지만 이 예제에서 그러한 조건은 필요 없죠.

[A-Z]{2} 

[A-Z] 구성 요소가 모든 대문자를 나타낸다는 사실은 이제 잘 아실 겁니다. 그리고 {2}는? 맞습니다. 4자리 숫자 뒤에 2개의 대문자가 와야 한다는 것을 의미합니다.

쉽지 않습니까? 이 예제에서 Count가 0이면 값이 잘못된 것입니다. 이 예제의 경우에는 문자열을 무효화하는 단일 문자를 검색하는 것이 아니라 정확한 기준에 해당하는 패턴을 검색하는 것이기 때문에 일치하는 결과가 없으면 값이 잘못되었음을 의미합니다. 이 경우 컬렉션의 Count 속성이 0이 되는 것입니다.

참고로, 유용한 다른 구성 요소로서 {3,}과 {3,7}이 있습니다. {3,}은 연속된 문자(또는 식)가 3개 이상 있어야 함을 나타냅니다. 단, 최소 요구 사항은 3개이지만 최대 개수에는 제한이 없습니다. \d{3,}은 123, 1234 및 123456789 모두를 결과로 검색합니다. {3,7}은 3개 이상 7개 이하의 연속된 문자(또는 식)가 있어야 함을 의미합니다. 따라서 123456은 일치하는 결과로 검색되지만 123456789는 연속된 숫자가 7개를 초과하기 때문에 검색되지 않습니다.

유용한 패턴을 하나 더 소개하겠습니다. 부품 번호가 4자리 숫자로 시작하고 그 뒤에 US라는 문자와 글자, 숫자 등 어떠한 문자도 될 수 있는 다른 두 개의 문자로 이루어진다고 가정해 보겠습니다. 이 부품 번호는 다음과 같이 확인합니다.

objRegEx.Pattern = "^\d{4}US.."

보다시피 이 패턴은 4자리 숫자(\d)로 시작하여 US라는 문자(US 이외의 문자는 검색되지 않음)가 뒤에 옵니다. US라는 문자 뒤에는 어떤 종류든 문자가 2개 더 필요합니다. 정규식에서 문자는 어떻게 나타낼까요? 흠, 잠시 생각 좀 해봐야겠군요. 아! 생각났습니다. 모든 단일 문자는 마침표(.)로 나타냅니다. 논리적으로 생각해보면 2개의 문자는 다음과 같이 2개의 마침표로 나타낸다는 사실을 쉽게 알 수 있습니다.

..

너무 쉽다고요? 그렇다면 좀 더 어려운 문제를 살펴보도록 하지요. 부품 번호 중간에 있는 2개 문자가 부품 생산지 국가를 나타낸다고 가정합니다. 미국, 영국, 스페인에 제조 공장이 있으면 US, UK, ES라는 2개 문자로 이루어진 코드가 유효합니다. 정규식에서 이러한 다중 선택은 대체 어떻게 구현해야 할까요?

다음 코드를 살펴보십시오.

objRegEx.Pattern = "^\d{4}(US|UK|ES).."

여기서 핵심은 (US|UK|ES)라는 구성 요소입니다. 허용되는 세 가지 값(US, UK, ES)을 괄호로 묶고 각각을 파이프(|) 문자로 구분했습니다. 정규식에서 다중 선택은 이러한 형태로 구현합니다.

그런데 대괄호 안에 여러 가지 옵션을 사용할 수 있다고 한 것을 기억하십니까? [13579]라는 예제가 바로 이에 대한 것이 아니었습니까? 그렇습니다. 하지만 이러한 옵션은 단일 문자의 경우에만 사용할 수 있습니다. 즉, [13579]는 항상 1, 3, 5, 7, 9로 해석됩니다. 135와 79를 선택 항목으로 사용하려면 (135|79)라는 구문을 사용해야 합니다. 괄호를 사용하여 검색할 단일 단어를 나타낼 수도 있습니다.

objRegEx.Pattern = "(scripting)"

또는 다음과 같이 괄호를 없앨 수도 있습니다.

objRegEx.Pattern = "scripting"

괄호의 여러 가지 사용 방법은 알았지만 괄호가 검색어의 일부로 포함되는 경우에는 어떻게 해야 할까요? 다시 말해 (scripting)이라는 문자열을 검색하는 경우에 말입니다. 정규식에서 여는 괄호와 닫는 괄호는 모두 예약된 문자이므로 각각의 괄호 문자 앞에 \를 붙여야 합니다. 즉, 다음과 같습니다.

objRegEx.Pattern = "\(scripting\)"

식의 해방

이번 달에 준비한 내용은 여기까지입니다. 정규식에 대해 더 자세히 알아보려면 String Theory for System Administrators: An Introduction to Regular Expressions(microsoft.com/technet/scriptcenter/webcasts/archive.mspx) 웹캐스트를 살펴보시기 바랍니다. 그리고 정규식에 대해 배우고 사용하기 전에 Sophia Loren의 다음과 같은 명언을 가슴에 새기시기 바랍니다. "실수란 충만한 삶을 얻기 위해 치러야 할 비용이지요."

우와! 이 말대로라면 Scripting Guys는 생각보다 충만한 삶을 살아왔었나 보군요!

Dr. Scripto의 Scripting Perplexer

매달 퍼즐 풀이 실력뿐만 아니라 스크립팅 기술까지 시험해 볼 수 있는 문제입니다.

2008년 1월: 낱말 맞추기

Dr. Scripto는 이번 달에 Windows PowerShell과 관련한 문제를 내기로 결정했습니다. 물론, Dr. Scripto가 항상 모든 것을 제대로 처리하기는 하지만 여러분은 바로 사용을 시작하는 경우가 아니라면 cmdlet 없이는 Windows PowerShell 작업이 불가능합니다. 이 퍼즐에서는 Dr. Scripto가 다음 표에 숨겨 놓은 몇 가지 cmdlet을 찾아야 합니다.

동사-명사의 형태로 구성되는 cmdlet의 특성상 Get-, Set- 등으로 시작되는 cmdlet이 수십 개씩 존재하므로 전체 cmdlet을 사용한다면 훨씬 어려운 퍼즐이 되겠지만 cmdlet의 동사 부분과 하이픈은 빼고 명사 부분만 생각하도록 합니다. 한 가지 힌트를 드리자면 사용된 cmdlet의 동사 부분은 모두 "Get-"입니다.

이제 여러분은 cmdlet 명사를 찾아내야 합니다. 친절하게도 퍼즐에 숨겨진 명사의 목록을 아래에 제시해 두었습니다. 각각의 단어는 가로 또는 세로로, 정상 순서 또는 역순으로 배치될 수 있으며, 대각선으로는 배치될 수 없습니다. 첫 번째 단어(Location)를 예시해 두었습니다. 자, 이제 시작해 보십시오.

단어 목록

Alias
ChildItem
Credential
Date
EventLog
ExecutionPolicy
Location
Member
PSSnapin
TraceSource
Unique
Variable
WMIObject

ANSWER:

Dr. Scripto의 Scripting Perplexer

답: 낱말 맞추기, 2008년 1월

Scripting Guys는 Microsoft에서 고용되어 일하고 있는 Microsoft의 직원들입니다. 이들은 좋아하는 야구 경기와 기타 여러 활동을 하는 시간을 제외하고는 항상 TechNet 스크립트 센터를 운영합니다. 자세한 내용은 www.scriptingguys.com에서 확인하십시오.

© 2008 Microsoft Corporation 및 CMP Media, LLC. All rights reserved. 이 문서의 전부 또는 일부를 무단으로 복제하는 행위는 금지됩니다..