[중요] Shell 확장

[중요] Shell 확장

2018, Nov 18    

0. 들어가며


나는 블로그에서 나름 쉘의 중요하지만 사람들은 잘 모를 수도 있는 기능들을 개성 있게 다루고 있다는 자부심이 있다. 핵심 커맨드부터 처음 보는 커맨드에 대한 대처법, 기능성 좋은 다른 커맨드 등. 이 블로그가 아직 많은 포스트를 담고 있지 않지만, 이런 나만의 컨텐츠를 다루고 있다는 자부심으로 지금도 이 글을 쓴다.

이번 포스트는 쉘의 확장에 대한 포스트이다. 밑에서부터 설명하겠지만 미리 말하면 난 이 기능이 정말정말 매력적이고 중요하다고 생각한다. 인생은 언제나 비용과 효용의 싸움. CLI는 윈도우와 같은 GUI에 비해 어렵지만, 사용자가 쉘을 잘 이해하고 있다면 그 효율을 비약적으로 상승시킬 수 있다. 다시 말해, 난이도로 효율을 사는 것이다. 트레이드 오프. 확장은 쉘 활용의 효율을 높여준다. 내 생각에 여기까지 이해하면 쉘스크립트 프로그래밍 이전까지의 쉘을 ‘어느 정도 알고 있다’고 말해도 된다고 생각한다.

지금까지의 포스트들에서 다루는 기능들의 중요도가 높아서 나름 신경써서, 다시 말해 중압감을 느끼며 작성하기도 했는데 이 포스트 이후부터는 쉘 스크립트를 다루기 전까지는 보다 가벼운 주제들을 다룰 생각이다. 이 포스트까지 잘 마무리하고 한숨 돌릴 수 있도록 해보자. :)

1. 확장이란?


쉘 확장(Expansion)은 쉘에서 프로그램을 실행하기 직전에 사용자가 입력한 특수한 의미의 인자를 정해진 의미에 맞게 해석해 프로그램의 입력 또는 출력으로 전환하는 것을 의미한다.

이 말의 의미를 고민할 필요가 있다. 먼저 프로그램이란 무엇인가? 여기서 프로그램이란 꼭 엑셀 같은 거창한 프로그램만을 의미하는 것이 아니라 쉘에서 실행하는 모든 기능들, 명령어들을 의미한다. 가령 우리가 쉘에서 cp 명령어를 사용하는 것은 cp 라는 유닉스의 전통적인 CRUD 프로그램을 실행시키는 것이다. 유닉스는 기본적으로 cp 라는 프로그램을 내장하고 있는데 which 를 통해 확인해보면 cp 프로그램은 ‘/bin/cp’ 라는 경로에 저장되어 있다. 운영체제적으로 쉘에서 cp 프로그램을 실행시키는 것은 쉘 프로세스에서 cp 자식 프로세스를 실행하는 것을 의미한다.

그럼 ‘인자를 정해진 의미에 맞게 해석해 입력 또는 출력으로 전환한다’? 사실 이게 문제일 것이다. 이건 간단한 예를 통해 살펴보도록 하자. 우리는 echo 라는 프로그램을 알고 있다. 인자들을 화면에 출력하는 프로그램으로 알고 있는데 echo 로 ‘*‘를 화면에 출력하는 코드를 입력해보자.

$ echo *

__pycache__ anagram.py baekjoon caesar_cipher.py fibonacci.py gcd.py hanoi_tower.py multiply_integers.py str_rotation.py xdecimal_number.py

???? 분명히 ‘*‘를 입력했는데 수상한 출력이 나왔다. 이 출력은 사실 내가 위치한 경로(pwd 의 결과)의 파일 목록이다. 왜 이런 일이 발생했을까?

이는 쉘에서 지원하는 확장 기능 때문이다. 쉘은 프로그램 실행 시에 미리 정해진 의미가 있는 입력에 대해서는 다른 입력으로 대체하는데 이것이 곧 확장이다. 위의 예제에서는 쉘에서 ‘*‘는 ‘모든 파일’, ‘모든 글자’라는 의미를 가지고 있기 때문에 ‘*‘가 현재 경로의 모든 파일의 이름 목록으로 확장되었으며 이 목록이 echo 의 인자로 전달되었다.

기억해야 할 것은 확장은 echo, cp 같은 쉘의 자식 프로세스들이 실행하는 것이 아니고, 프로그램에 인자가 입력되기 직전 쉘이 직접 수행한다는 것이다. 이것은 향후 우리가 쉘스크립트 프로그램을 작성할 때 입력으로 확장가능한 글자가 들어올 때마다 우리가 직접 변환하지 않아도 된다는 것을 의미한다. 그랬다면 입력 인자를 검증하는 데만 엄청난 노력을 들여야 했을 것이다.

쉘에는 생각지도 못했을 재밌는 확장이 존재하며 이들을 이해하면 쉘 활용의 효율을 좀더 끌어올릴 수 있다. 다음 장에서 6개의 확장을 살펴보도록 하자.

2. 각 확장 살펴보기


확장은 크게 6개로 나눌 수 있다. 각각을 살펴보도록 하자.

2.1. 경로명 확장

경로명 확장은 와일드카드라고 하는 의미 있는 글자가 경로나 경로 상의 파일들로 확장되는 것을 의미한다. 와일드카드는 쉘에서 파일 이름의 그룹을 묶는 역할을 하는데, 가령 아까 예제에서 ‘*‘는 ‘경로의 모든 파일’, ‘모든 글자’를 의미한다.

이런 예제가 있을 수 있다. 경로의 파일 중에서 영어 대문자로 시작하는 파일들을 출력하고 싶다면 다음과 같이 입력하면 된다.

$ echo [[:upper:]]*

Downloads

경로에서 대문자로 시작하는 파일이 출력되었다.

[[:upper:]]는 ‘영어 대문자 한 글자’를 뜻하는 와일드카드로 대문자 한 글자만으로 이름 지어진 파일은 (아마) 없을 수 있기 때문에 ‘*‘를 붙여서 ‘파일 이름이 대문자로 시작하는 모든 파일’로 인식되도록 했다.

’[[:upper:]]’는 다음과 같은 부분으로 쪼개진다. 먼저 ’[:upper:]’를 먼저 살펴보면 와일드카드에서 ‘[: :]’는 문자 클래스를 의미하는데 여기서는 ‘대문자 클래스’를 지칭한다. 중요한 것은 ‘[:upper:]’ 자체는 한 글자를 의미하는 것이 아니라, 단순히 ‘대문자 클래스(분류)’를 나타낸다는 것이다. 따라서 ‘echo [:upper:]*’ 처럼 입력하면 작동하지 않는다.

‘클래스에 포함되는 한 글자’로 의미를 주고 싶다면 클래스를 ‘[ ]’로 감싸야 한다. 그래서 ‘echo [[:upper:]]*‘가 ‘영어 대문자 한 글자’로 시작하는 모든 글자로 확장될 수 있었다. 귀찮게 왜 이렇게 해놨냐고 할 수 있는데, 이를 이렇게 활용해볼 수도 있다.

만약 ‘영어 소문자나(OR) 숫자로 시작하는’ 파일 목록을 출력하고 싶다면 어떻게 할까? 두 클래스를 나열하고 ‘[ ]’로 감싸면 된다.

$ echo [[:lower:][:digit:]]*

ok.txt  404.html
...

위에서처럼 ‘[ ]’ 안에 클래스를 두 개 이상 넣어서 여러 클래스의 문자 한 글자를 지칭할 수 있다. 자주 사용되는 문자 클래스들은 다음과 같다.

문자 클래스 매칭 문자
[:alnum:] 모든 알파벳과 숫자
[:alpha:] 모든 알파벳
[:digit:] 모든 숫자
[:lower:] 소문자 알파벳
[:upper:] 대문자 알파벳

만약 반대로 ‘소문자가 아닌’과 같이 부정(NOT)을 표현하고 싶다면 ‘[ ]’안의 첫 글자로 우분투에서는 ‘^’를 입력하면 된다.

$ echo [^[:lower:]]*

Downloads ~ 바탕화면

’^’를 추가해줌으로써 ‘[ ]’ 안에 속한 클래스들의 글자가 아닌 글자를 지칭한다. 여집합이라고 생각하면 된다. 배포판에 따라서는 ‘^’이 아닌 ‘!’일 수도 있다.


마지막으로 ‘[ ]’ 안에 ‘[: :]’ 꼴의 문자 클래스뿐만 아니라 단순히 글자 몇 개를 지정할 수도 있다.

$ echo [abc]*	# 1.
$ echo [1-5]*	# 2.

archive-directory bin
404.html

1번에서는 ‘[ ]’ 안에 ‘abc’를 입력함으로써 이 중 하나로 시작하는 모든 파일로 확장되었다. 2번에서는 ‘-‘를 통해 범위를 지정해줌으로써 1부터 5사이의 숫자로 시작하는 파일로 확장되었다.

여기까지가 경로명 확장의 기본이고, 이 확장은 주로 시스템을 살펴보거나 필요한 파일을 검색하는 등의 일에 간간히 쓰인다. 정규표현식과 비슷하지만 정규표현식 자체는 아니라는 것을 기억하기 바란다.


2.2. 틸드(~) 확장

’~’, 이 물결무늬를 영어로는 틸드(tilde)라고 한다고 한다.(이거 공부하면서 처음 알았다;) 틸드 기호는 쉘에서는 홈 디렉토리라는 특수한 의미를 가지고 있고 쉘에서 ‘~’가 유저의 홈 디렉토리로 해석되는 것을 틸드 확장이라고 한다.

$ echo ~

/home/sunghwanpark

알다시피 리눅스 시스템의 모든 유저의 홈 디렉토리는 ‘/home’ 경로 밑에 존재한다. 현재 시스템에 로그인한 유저는 나 박성환이기 때문에 ‘~’는 위와 같이 확장되었다.

틸드 확장은 간단하고 매우 자주 쓰이기 때문에 확장이라고 하는지도 몰랐는데 기본적으로 확장이란 쉘에서 글자를 해석해서 자체 변환시키는 모든 것을 가리키므로 기억해두면 된다.


2.3. 산술 확장

쉘을 간단한 계산기처럼 쓰는 것이 가능하다. 산술 확장은 수식(expression)을 실제 값으로 평가한다. 수식이 무엇인지에 대해서는 expression과 statement를 다룬 이 포스트에서 확인하기 바란다.

산술 확장은 다음과 같은 문법을 지켜야 한다.

$((expression))

’$’ 표시와 함께 ‘(( ))’ 안에 수식을 넣으면 단일 값으로 평가된다. 간단한 예를 살펴보자.

$ echo $((3 + 3))

6

‘3 + 3’이라는 수식을 평가해 6이라는 값을 출력했다.

산술 확장에서 사용할 수 있는 연산자들은 다음과 같다.

연산자 설명
+ 덧셈
- 뺄셈
* 곱셈
/ 나눗셈(소수가 아닌 몫만 구해진다)
% 나머지(modulo)
** 거듭제곱

참고로 산술 확장은 정수만을 허용하기 때문에 소수점 연산을 할 수 없다.

$ echo 7을 2로 나눈 몫은 $((7 / 2))이고 나머지는 $((7 % 2))입니다!

7을 2로 나눈 몫은 3이고 나머지는 1입니다!

산술 확장은 소수를 사용할 수 없는 등 기능에 제한이 있지만 간단한 계산을 할 때는 편하다. 향후 쉘 프로그래밍에서도 중요한 역할을 맡으리라 기대된다.


2.4. 중괄호 확장

개인적으로 제일 관심 있는 확장이 중괄호 확장이다. ‘{ }’, 중괄호 안에 표현된 패턴과 일치하는 다양한 텍스트 문자열을 만들 수 있다. 바로 예제를 살펴보자.

$ echo {김,이,박}성환

김성환 이성환 박성환

중괄호 안에 주어진 ‘김’, ‘이’, ‘박’을 하나씩 사용해서 ‘성환’과 조인해 다양한 성환이를 만들었다. 이때 중괄호 안에는 스페이스가 들어가서는 안 된다.


,를 구분자로 하면 양옆을 낱개로 사용하는데 ..을 사용하면 범위를 표현할 수 있다.

$ echo {A..Z}

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

‘A’와 ‘Z’ 사이에 ‘..’을 입력함으로써 둘을 포함한 모든 영어 대문자를 표현할 수 있었다.
내가 요즘 관심 있어 하는 코드인데 다음과 같이 입력해보라.

$ echo {가..힣}

가 각 갂 갃 간 ... (넘나 많음)
...

한글 모음과 자음을 합해 만들 수 있는 모든 한글 글자의 첫 글자는 ‘가’이고, 끝 글자는 ‘힣’이다. 한 번 시행해보기 바란다. 모든 한글 글자를 얻을 수 있나니… wc 를 통해서 한글 글자의 개수를 세보는 것도 재미 있는 경험일 것이다.

이런 범위를 표현하는 것은 글자의 유니코드와 관련이 있다. 음, 영감이 떠올랐다. 언제 한 번 UTF-8, 유니코드, ascii 등의 인코딩 관련 포스트를 작성해야겠다. 이 부분은 포스트와 직접적인 관련은 없기 때문에 일단 넘어가자.


중괄호 확장도 중첩이 가능하다. 다음과 같이.

$ echo \\{\\{a..z\\},\\{가,나,다,라\\}\\}  
# ^ 지킬의 문제로 '\\'를 붙였다. 이해바란다. 실행 시에 지우자.

a b c d e f g h i j k l m n o p q r s t u v w x y z 가 나 다 라

중첩됐을 때는 중괄호를 안에서부터 풀어나가면 우리같은 범인도 해석 가능하다.


이 기능이 굉장히 유용할 때가 있다. 가령 이런 예가 있을 수 있다. 인터넷 신문 어플리케이션의 컴퓨터에서는 기사를 저장할 때 연도별, 월별 또는 날짜별로 디렉토리를 만들어 파일을 정리할 수 있다. 이를 위해서는 먼저 연별 디렉토리를 만들어 그 안에 월별 디렉토리, 월별 디렉토리마다에 일별 디렉토리를 만드는 과정이 필요할 수도 있다. 중괄호 확장을 통해서면 이 작업을 한 큐에 끝낼 수 있다.

$ mkdir {2018..2020}-0{1..9} {2018..2020}-{10..12}

2018-01  2018-05  2018-09  2019-01  2019-05  2019-09  2020-01  2020-05  2020-09
2018-02  2018-06  2018-10  2019-02  2019-06  2019-10  2020-02  2020-06  2020-10
2018-03  2018-07  2018-11  2019-03  2019-07  2019-11  2020-03  2020-07  2020-11
2018-04  2018-08  2018-12  2019-04  2019-08  2019-12  2020-04  2020-08  2020-12

이런 게 어썸한 게 아니면 대체 무엇이 어썸할 것인가? 난 확장에서 이 부분에서만큼은 준 소름이 돋았다.


2.5. 매개변수 확장

매개변수 확장은 쉽게 말해 ‘변수’를 사용하는 것이다. 변수는 프로그래밍에서 항상 사용하는 개념이기 때문에 쉘 자체보다는 쉘 스크립트 프로그래밍에서 빛을 발한다. 일단 쉘에서만 변수를 사용해보자.

가령 유닉스 시스템에는 기본적으로 정의되어 있는 환경변수들이 있는데 그중 현재 사용자를 가리키는 변수로 ‘USER’가 있다. 이 변수를 확인하고 싶다면 다음과 같이 입력한다.

$ echo $USER

sunghwanpark

쉘에서 변수를 사용하려면 변수명 앞에 ‘$’을 꼭 붙여줘야 한다. PHP의 변수 사용법과 비슷하다. 차이점은 PHP는 변수를 정의할 때도 ‘$’를 사용하는데 쉘에서는 변수 정의에서는 ‘$’를 쓰지 않아도 된다는 것이다.

$ answer=3	# 1.
$ echo 정답은 $answer 입니다!	# 2.

정답은 3 입니다.

1번에서 보듯이 변수를 정의할 때는 ‘$’을 사용하지 않는다. 다만 ‘=’ 대입연산자와 피연산자(‘answer’, ‘3’) 사이에 빈 공간이 있으면 안 된다.

그리고 변수를 호출할 때는 변수명 앞에 ‘$’를 붙인다. 이는 위에서도 확인했다.


다만 여기서 미묘한 이슈가 있다. 2번에서 ‘$answer’와 ‘입니다!’ 사이에 빈 공간이 있는 것을 발견했는가? 저 공간은 일부러 둔 것이다. 만약 둘을 붙이면 출력은 ‘정답은 !’가 된다. 왜 그럴까?

이유는 생각보다 간단하다. 둘을 붙이면 ‘$answer입니다!’가 되는데 쉘은 이를 ‘answer입니다’라는 변수를 호출하는 것으로 생각한다.(쉘에서 변수명에 ‘!’을 넣을 수 없기 때문에 ‘!’는 고려되지 않는다.) 우리는 그런 변수를 선언한 적이 없기 때문에 당연히 값이 나올리 없다.

그러면 이럴 때 어떻게 해야 할까? 이때는 산술 확장을 쓰도록 하자.

$ answer=3	# 1.
$ echo 정답은 $((answer))입니다!	# 2.

정답은 3입니다!

위에서 소개한 포스트에서 수식(expression)은 하나의 값으로 치환될 수 있는 구문이라고 했다. 그러니 ‘answer’라는 구문도 수식이라고 할 수 있다. 따라서 당당하게 3으로 값이 치환되어 출력되었다. 촉이 좋다면 여기서 얻을 수 있는 또다른 인사이트는 산술 확장 안에서 변수를 호출할 때는 ‘$’를 붙이지 않아도 된다는 것이다. 산술 확장에서 ‘$’가 붙으니 또 쓰지 않는다고 쉽게 생각하도록 하자.


2.6. 명령어 치환

명령어 치환은 명령어의 출력결과를 확장으로 사용하는 것이다.

$ ls -l $(which cp)

-rwxr-xr-x 1 root root 151024  3월  3  2017 /bin/cp

명령어를 ‘$( )’로 감싸면 그 명령어가 자신의 실행결과로 치환된다. 위의 예에서는 ‘which cp’는 cp 프로그램의 실제 경로를 출력할텐데 그 실제 경로가 명렁어 치환으로 인해 원 인자를 치환해서 결과적으로 ‘ls -l /bin/cp’가 실행되었다.

다음과 같은 좀더 고급스럽게 써볼 수도 있겠다.

$ file $(ls /usr/bin/* | grep zip)


/usr/bin/funzip:     ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=06412c648a6927c4a14c751fe2412db3425ecd0f, stripped
/usr/bin/gpg-zip:    POSIX shell script, ASCII text executable
...

‘/usr/bin’ 안에는 유저들이 자주 사용하는 프로그램들이 저장되어 있다. ’$( )’ 안의 식은 ‘/usr/bin’ 디렉토리의 모든 프로그램 중 이름에 ‘zip’을 포함하고 있는 프로그램을 간추릴 것이다. 그 결과가 원식을 치환해서 file 의 인자로 그 프로그램들이 입력되었다. file 프로그램은 그 이름들을 입력으로 받아 각각의 파일 타입을 출력한다.

내 생각에 명령어 치환은 지금까지 다룬 확장들 중 가장 고급스러운 기능인 것 같다.(그냥 느낌적인 느낌이다.) 다른 확장은 이제 눈감고도 쓰는데 이 확장은 자주 써볼 기회를 만들지 못했다. 써봐야 익숙해지기 때문에 필요한 순간에 잘 쓸 수 있도록 하자.


3. 확장 숨기기


이전 장에서 주요 6개의 확장을 살펴보았다. 확장은 분명히 유용해보인다. 하지만 쉘을 쓰다보면 확장이 필요 없는 경우도 분명 있을 수 있다. 다음 예를 보자.

$ echo I'm $USER!!!

I'm sunghwanpark!!!

매우 간단한 매개변수 확장이다. 나의 정체를 만천하에 선언하는 코드일 수도 있다. 그런데 만약 내 의도는 내 이름을 출력하는 것이 아니라 말 그대로 ‘$USER’를 출력하는 것이었다면 어땠을까? 나는 어쩌면 기축통화로서의 미 달러화의 위상에 깊은 감명을 받아 ‘$’를 수시로 갖다 쓸지도 모르는 일이다.

만약 그렇다면 쉘 확장이 언제나 좋은 것은 아닐 것이다. 쉘에 내장된 확장 기능으로 인해 내 다른 의도가 꺾여버릴지도 모른다. 그렇다면 경우에 따라서는 확장을 억제하는, 다시 말해 이스케이프하는 방법이 필요할 것이다. 쉘은 미 달러화에 대한 내 사랑을 잘 알고 있기 때문에 따라서 이 기능을 제공한다.

한 줄로 말하면 확장을 억제하려면 코드를 따옴표로 감싸면 된다. 그런데 큰따옴표를 쓸 때와 작은따옴표를 쓸 때의 확장의 범위가 다르다. 그래서 각각 독립된 절로 살펴보도록 하자.

3.1. 큰따옴표

코드를 큰따옴표로 감싸면 특정 확장을 무시한다. 그말은 몇몇 확장은 무시하지 않는다는 것을 의미한다. 먼저 무시하는 예제를 보자.

$ echo "*"

*

큰따옴표로 “*“를 감싸니 “*“가 ‘모든 파일’, ‘모든 글자’로 확장되지 않고 단순히 “*” 글자로 해석되었다. 그렇다. “ “ 안의 글자가 escape된 것이다.


앞서 말했듯이 큰따옴표는 모든 확장을 숨기지 않는다. 큰따옴표는 매개변수 확장, 산술 확장, 명령어 치환, 이렇게 세 확장은 억제하지 않는다. 대신 나머지는 억제한다.

# 1. 억제되지 않는 확장: 매개변수 확장, 산술 확장, 명령어 치환
$ echo "$USER $((3*4)) \n $(date)"

sunghwanpark 12 
 2018. 11. 18. () 02:01:26 KST


# 2. 억제되는 확장: 나머지
$ echo "* {1..3} ~"

* {1..3} ~

3.2. 작은따옴표

작은따옴표로 코드를 감싸면 모든 확장을 무시한다.

$ echo '$USER $((3*4)) $(date) * {1..3} ~'

$USER $((3*4)) $(date) * {1..3} ~

정말 칼같이 다 억제했다.


3.3. 정리

확장과 확장을 숨기는 두 가지 방법을 처음 배웠을 때 작은따옴표와 큰따옴표의 차이가 계속 헷갈렸다. 그래서 두 가지를 비교하면 다음과 같다.

확장 여부(O: 확장 허용, X: 확장 억제)

확장명\ 따옴표 따옴표 없이 큰따옴표 작은따옴표
경로명 확장 O X X
틸드 확장 O X X
산술 확장 O O X
중괄호 확장 O X X
매개변수 확장 O O X
명령어 치환 O O X

아마 가장 헷갈리는 것은 큰따옴표가 허용하는 확장과 허용하지 않는 확장을 구분하는 것일텐데, 내가 이것을 기억하는 기가 막힌 방법을 찾아냈다.

큰따옴표는 산술 확장, 매개변수 확장, 명령어 치환은 확장을 허용하는데 이들은 모두 ‘$’을 사용한다. 그러니까 큰따옴표는 ‘$’를 사용하는 확장식은 확장을 허용하고, ‘$’를 사용하지 않는 확장식은 억제한다고 기억하면 되겠다.


4. 마치며


지금까지 쉘 확장에 대해 살펴보았다. 개인적으로 셀 확장은 이전에 두 번 정도 공부했지만 최근까지도 헷갈렸는데 이번에 포스트를 준비하면서 다시 공부했고 이제는 확실히 감을 잡은 것 같다. 그래서 공부할 수 있었음에 감사한다. 블로그가 이게 좋다.

쉘 확장은 정말정말 중요하다. 모든 쉘 포스트마다 ‘이게 정말 중요해~’라고 말해서 ‘중요하다’라는 단어의 위상을 떨어뜨리는 것 같지만, 지금까지의 쉘 포스트들은 정말로 중요한 주제들을 다뤘다. 그래서 뿌듯하다. 쉘 확장까지는 달달 외우라고 하고 싶다. 그리고 많이 사용해보자. 맛들리면 이제 진짜 GUI로 돌아갈 수 없게 된다. GUI의 답답함에 온몸이 덜덜 떨릴 것이다.

이상한 소리만 했는데 어쨌든, 확장 포스트를 무사히 마쳐서 기쁘다. 이 부분은 혼자 공부하면 반드시 헷갈리는 게 나오기 쉽다. 그러니 질문 주시면 보다 상세하게 답변드리도록 하겠다.

이상 쉘 확장 포스트를 마칩니다.