공부 기록/리눅스 기초

[리눅스 기초] 11 소프트웨어 관리

도도히히 2026. 1. 17. 13:23

패키지 설치 Overview

패키지

dpkg

  • 우분투에서 패키지(프로그램)를 설치할 때 많이 사용되던 명령어
  • apt-get이 나오기 전에 주로 씀

apt-get

  • dpkg의 확장 개념
  • dpkg 기능이 포함되어 있음

의존성이 있는 deb 파일 다운로드 및 설치하기

  • 의존성 문제를 해결하기 위해 다른 패키지를 설치하고 싶지만 패키지를 설치하기 전 어떤 deb 파일을 설치해야 하는지 정확히 알 수 X
  • 설치해야 할 deb 파일을 알아내더라도 그 파일 또한 의존성 문제가 있을 수 있음
  • 우분투에서는 이러한 문제를 한번에 해결하기 위해 apt-get 명령어 제공

apt-get 명령어

  • apt-get 명령어는 *.deb 패키지를 설치하는 편리한 도구
  • 우분투가 제공하는 deb 파일 저장소에서 자동으로 deb 파일을 다운로드하여 설치
  • 의존성 문제를 걱정하지 않아도 됨
  • dpkg 명령어의 경우, *.deb 파일을 다운로드한 후 설치해야 하는 번거로움이 있음

main, universe, restricted, multiverse의 의미

  • main: 우분투에서 공식적으로 지원하는 무료 소프트웨어
  • universe: 우분투에서 지원하지 않는 무료 소프트웨어
  • restricted: 우분투에서 공식적으로 지원하는 유료 소프트웨어
  • multiverse: 우분투에서 지원하지 않는 유료 소프트웨어

미러(mirror) 사이트

  • 우분투 패키지 저장소는 우분투 사이트에서 제공
  • 전 세계적으로 동일한 저장소가 수백 개 존재
  • 대학, 연구소, 기업 등이 자발적으로 구축한 것
  • apt-get -y install 패키지명 : sources.list에 기록된 사이트에 자동으로 접속해서 다운로드 가능

 


apt 패키지 관련 명령어

apt-cache [옵션] [서브 명령] : 패키지 검색 및 정보보기 명령 (패키지 데이터베이스(APT 캐시)에서 패키지와 관련된 정보를 출력)

옵션

-f : 패키지에 대한 전체 정보 출력

-h : 관련 도움말 출력

서브

stats : 캐시 통계 정보(전체 패키지 이름과 단일 가상 패키지 등) 출력

dump : 현재 설치되어 있는 패키지 업그레이드

pkgnames : 사용 가능한 모든 패키지의 이름 출력

search [키워드] : 키워드에 해당하는 패키지 검색

show [패키지 이름] : 해당 패키지의 정보 출력

showpkg [패키지 이름] : 패키지의 의존성과 역의존성에 대한 정보 출력

APT 캐시 통계정보 출력 : apt-cache stats

  • 시스템에 설치되어 있는 전체 패키지의 이름과 일반 패키지
  • 순수 가상과 단일 가상 및 혼합 가상 패키지
  • 등의 APT 캐시의 전반적인 통계정보를 출력하는 명령

패키지 업데이트 : apt-cache dump

  • 현재 시스템에 설치되어 있는 모든 패키지에 대한 업데이트 수행

모든 패키지 이름 출력 : apt-cache pkgnames

  • 현재 시스템에 설치되어 있는 모든 패키지의 이름 출력

패키지 정보 출력 : apt-cache show [패키지명]

  • 현재 시스템에 설치되어 있는 특정 패키지의 정보 출력

apt-get [옵션] [서브 명령] : 패키지 설치 및 업데이트 명령

옵션

-d : 패키지 다운로드 수행

-f : 의존성이 깨진 패키지 수정

-h : 관련 도움말 출력

서브 명령

update : 패키지 저장소에서 새로운 패키지 정보를 가져옴

upgrade : 모든 패키지를 최신 버전으로 업그레이드

install [패키지 이름] : 해당 패키지 설치

remove [패키지 이름] : 설치된 해당 패키지 제거

purge [패키지 이름] : 설치된 해당 패키지와 설정 파일을 모두 삭제

autoremove : 시스템에 설치된 패키지를 자동으로 정리 및 삭제

download [패키지 이름] : 해당 패키지를 현재 디렉터리에 다운로드

autoclean : 오래된 패키지 또는 불완전한 다운로드 패키지 제거

check : 의존성이 깨진 패키지 확인

clean : /var/cache/apt/archives 디렉터리에 캐시되어 있는 모든 패키지를 제거하여 디스크 공간 확보

 

패키지 정보 업데이트 : sudo apt-get update

  • update 서브 명령을 수행하면 /etc/apt/sources.list에 새로운 패키지 정보(목록)를 가져와서
  • 만약 /etc/apt/sources.list 파일을 수정하였다면 반드시 이 명령 수행해야 함

패키지 업그레이드 : sudo apt-get upgrade

  • 현재 시스템에 설치되어 있는 모든 패키지 중에서 업그레이드가 필요한 패키지를 찾은 다음 새로운 버전이 있는 패키지에 대해서는 모두 업그레이드를 수행
  • 업그레이드 필요여부 검색 : sudo apt-get upgrade | grep [패키지 이름]
  • 검색과정 멈춤 : Ctrl+C 누르기

특정 패키지 설치 또는 업그레이드 : sudo apt-get install

  • 1개 또는 1개 이상의 패키지를 설치하거나 업그레이드 수행
  • 1개의 패키지만 설치할 경우 : sudo apt-get install [패키지 이름]
  • 패키지를 설치할 때 진행여부에 대한 y를 입력하지 않아도 자동으로 입력되도록 수행하려면 -y 옵션 사용 sudo apt-get -y install [패키지 이름]
  • 여러 패키지 설치할 경우 : sudo apt-get install [패키지 이름1] [패키지 이름2]
  • 새로운 패키지를 설치하지 않고 업그레이드만 수행 : sudo apt-get install [패키지 이름] --only-upgrade

특정 패키지 삭제 : purge 또는 remove

remove: 해당 패키지의 설정 파일은 남아있게 됨

purge: 남은 설정 파일까지 모두 삭제

설정 파일을 남겨놓는 이유: 나중에 동일한 패키지를 다시 설치할 때 재사용

sudo apt-get purge [패키지 이름]

sudo apt-get remove [패키지 이름] --purge (두 옵션을 함께 쓸 수 있음)

 

패키지 자동 정리 및 삭제 : autoremove

더 이상 존재할 필요가 없는 패키지가 남아있을 수 있으므로 잔존해 있는 패키지에 대해서 자동 정리 및 삭제

sudo apt-get autoremove

 

디스크 공간 정리 : clean

패키지를 검색하였거나 다운로드한 파일들을 삭제한 후 디스크 공간 정리

sudo apt-get clean

 

특정 패키지 다운로드 : download

특정 패키지를 직접 설치하지 않고 다운로드만 수행

sudo apt-get download [패키지 이름]

 

특정 패키지의 소스 관련 서브 명령 : source

특정 패키지의 소스 코드가 필요할 경우 다운로드할 때 사용

sudo apt-get --download-only source [패키지 이름]

다운로드를 수행한 특정 패키지의 소스코드에 대한 압축 해제 : sudo apt-get source [패키지 이름]

특정 패키지의 소스 코드 다운로드를 수행하여 압축을 해제하고 컴파일 : sudo apt-get --compile source [패키지 이름]

 

문제 11-01

  1. pkgnames 서브 명령으로 모든 패키지 이름 출력하기
  2. upgrade 서브 명령으로 업그레이드를 수행할 패키지 확인하기
  3. install 서브 명령과 옵션 y를 설정하여 netcat 패키지 설치하기
  4. 설치된 netcat 패키지의 정보 출력하기
  5. netcat 패키지의 설정 파일까지 모두 삭제하기
$ sudo apt-cache pkgnames
$ sudo apt-get upgrade
$ sudo apt-get -y install netcat
$ sudo apt-cache show netcat
$ sudo apt-get purge netcat

 

 


DPKG 패키지 관련 명령어

DPKG 명령

  • 시스템의 특정 파일이 어느 패키지에 포함되어 있는지 등에 대한 구체적인 기능을 확인할 때 옵션과 함께 사용
  • APT 명령은 소프트웨어 관리를 위해 내부적으로 DPKG 명령을 이용

dpkg [옵션] [패키지 이름 또는 파일 이름] : 패키지 관리 명령

옵션

-l : 설치된 패키지 목록 전체 출력

-l [패키지 이름] : 해당 패키지의 설치상태 정보 출력

-r [패키지 이름] : sudo 명령을 사용하여 해당 패키지를 삭제

-P [패키지 이름] : sudo 명령을 사용하여 해당 패키지와 설정 정보 모두 삭제

-s [패키지 이름] : 해당 패키지의 자세한 정보 출력

-L [패키지 이름] : 해당 패키지가 설치된 파일목록 출력

-S [경로명] : 경로명이 포함된 패키지 검색

-c [.파일 확장자] : 해당 파일 확장자를 가진 파일의 내용 출력

-i [.파일 확장자] : sudo 명령으로 해당 파일 확장자를 가진 파일 설치

-x [.파일 확장자] [디렉터리명] : 해당 파일 확장자를 가진 파일을 지정한 디렉터리에 풀어놓음

 

dpkg -l : 시스템에 설치되어 있는 패키지의 목록 출력

dpkg -l zip : 설치된 패키지 중에서 zip 패키지에 대한 정보만 출력

dpkg -l gnome-chess : gnome-chess 패키지가 설치되어 있는지 확인

sudo apt-get download gnome-chess

ls gnome* : 다운로드한 패키지 확인

sudo dpkg -i gnome-chess_1%3a3.36.0-1_amd64.deb : 의존성 패키지 hoichess가 설치되어 있지 않으므로 설치가 정상적으로 안됨

sudo apt-get install hoichess : gnome-chess 패키지의 의존성을 가진 hoichess 패키지 설치

특정 패키지를 못 찾는다는 오류 메시지가 출력되면 해당 패키지는 제외하고 설치 진행

 


APTITUDE 패키지 관련 명령어

APTITUDE 명령 : sudo 명령으로 root 계정 권한 위임

  • apt 명령과 같이 패키지 관리를 쉽게 작업할 수 있도록 자동화 기능 제공

aptitude [서브 명령] : 패키지 설치 및 업데이트 명령

서브

단독 실행 : 비주얼 모드로 실행하기 위한 curses 프로그램 활성화

update : 패키지 저장소에서 새로운 패키지 정보를 가져옴

upgrade : 모든 패키지를 최신 버전으로 업그레이드

install [패키지 이름] : 해당 패키지 설치

remove [패키지 이름] : 설치된 해당 패키지 제거

purge [패키지 이름] : 설치된 해당 패키지와 설정 파일을 모두 삭제

autoremove : 시스템에 설치된 패키지를 자동으로 정리 및 삭제

download [패키지 이름] : 해당 패키지를 현재 디렉터리에 다운로드

autoclean : 오래된 패키지 또는 불완전한 다운로드 패키지 제거

check : 의존성이 깨진 패키지 확인

clean : /var/cache/apt/archives 디렉터리에 캐시되어 있는 모든 패키지를 제거하여 디스크 공간 확보

 

sudo apt-get install aptitude : aptitude 패키지 설치

sudo aptitude update : 기존에 설치된 패키지의 새로운 정보를 가져와서 apt 캐시를 수정

aptitude search gnome : gnome으로 시작하는 패키지 검색

aptitude show gnome-clock : gnome-clocks 패키지의 상세한 정보 확인

sudo aptitude install gnome-clocks : gnome-clocks 패키지 설치

 

문제 11-02

  1. aptitude 명령으로 gnome-dictionary 패키지에 대한 상세한 정보 확인하기
  2. aptitude 명령으로 gnome-dictionary 패키지 설치하기
  3. apt 명령으로 gnome-dictionary 패키지가 설치한 파일목록 확인하기
  4. gnome-dictionary 패키지를 실행하기
  5. 우분투 소프트웨어 센터를 통해 OpenTTD 게임 설치하기
$ aptitude show gnome-dictionary
$ sudo aptitude install gnome-dictionary
$ sudo apt-cache show gnome-dictionary
> 윈도 화면에 설치된 패키지 아이콘을 클릭하여 실행
> 우분투 소프트웨어 센터에서 OpenTTD 게임 설치 

 


파일 아카이브와 압축

파일 아카이브

  • 아카이브(Archive)란 특정 시간에 함께 묶인 파일이나 디렉터리의 집합을 의미 (저장소 또는 보관소)
  • 다른 시스템과 파일이나 디렉터리를 주고 받거나 파일을 백업하는 용도로 사용
  • 파일 아카이브를 위해 사용되는 명령은 tar, cpio, ar 등이 있음

tar 기능[옵션] [아카이브 생성 파일명] 대상 파일명 : 파일과 디렉터리를 묶어 하나의 아카이브로 생성하는 명령

기능

c : 새로운 tar 아카이브 생성

r : 새로운 파일 추가

t : tar 파일에 존재하는 파일 리스트 출력

u : 수정된 파일에 대해 업데이트 수행

x : tar 파일에 있는 원본 파일을 추출

옵션

C [디렉터리명] : 파일들을 특정 디렉터리에 풀어 놓음

f [디바이스명 또는 파일명] : 아카이브 파일 또는 테이프 장치를 지정

(디바이스명을 -으로 지정하면 tar 파일 대신 표준입력에서 읽어 들임)

h : 심볼릭 링크의 원본 파일 포함

j : bzip2로 압축 또는 해제

p : 파일 복구 시 원래의 접근 권한을 유지

v : 처리하고 있는 파일의 정보 출력

z : gzip으로 압축 또는 해제

Z : compress를 통해 아카이브를 필터링

 

아카이브 생성 : cvf

mkdir arch_test

cd arch_test

mkdir ex_ac.d

touch test_01.txt test_02.txt

gedit test_01.txt

gedit test_02.txt

cd ~

ls arch_test

tar cvf arch_test.tar arch_test

→ 기존에 존재하던 arch_test 디렉터리는 삭제되지 않고 그대로 남아있음

 

아카이브 내용 출력 : tvf

tar tvf arch_test.tar : 앞서 생성한 arch_test.tar 파일에 대한 상세한 정보 출력

 

아카이브 풀기 : xvf

이미 존재하는 아카이브 파일을 풀기 위함

cp arch_test.tar arch_test/ex_ac.d : arch_test.tar 아카이브를 arch_test/ex_ac.d 디렉터리에 복사

cd arch_test/ex_ac.d

tar xvf arch_test.tar

 

아카이브 업데이트 : uvf

이미 존재하는 아카이브 파일에 수정된 파일을 추가할 수 있음

u 기능은 아카이브에 존재하는 파일의 내용이 수정될 경우 아카이브를 업데이트하기 위해 사용하는 기능 (기존 파일의 내용에 변경 사항이 없다면 아카이브 파일에 아무런 변화 X)

cd ~

tar uvf arch_test.tar arch_test

gedit arch_test/test_01.txt

tar uvf arch_test.tar arch_test

 

아카이브에 파일 추가 : rvf

이미 존재하는 아카이브에 지정한 파일을 무조건 추가

tar rvf arch_test.tar hosts : 복사한 hosts 파일을 아카이브에 추가

 

아카이브와 파일 압축

아카이브를 생성하면서 파일 압축을 수행 (디스크 공간이 부족할 때 유용)

tar cvzf arch_test.tar.gz arch_test : arch_test 디렉터리를 아카이브 생성과 동시에 압축 파일로 생성

아카이브 생성과 동시에 bzip2 형식으로 파일 압축

tar cvjf arch_test.tar.bz2 arch_test

cf. 확장자 이름을 붙이는 것은 쉽게 알아보기 위한 것임 (필수로 붙여야 하는 것 X)

 


파일 압축과 해제

1) gzip 명령으로 파일 압축

gzip [옵션] 파일 이름 : gzip 형식으로 파일 압축 명령

옵션

-d : 파일 압축 해제

-l : 압축된 파일의 정보 출력

-r : 하위 디렉터리로 이동하여 파일 압축

-t : 압축 파일 검사

-v : 압축 정보를 화면에 출력

-9 : 최대한 압축

gzip arch_test.tar arch_test : arch_test 디렉터리를 arch_test.tar 파일명으로 압축 수행

→ arch_test.tar 파일은 사라지고 arch_test.tar.gz 압축 파일이 새로 생성됨 arch_test는 디렉터리이므로 무시됨

 

압축 파일 내용 보기 : zcat

zcat arch_test.tar.gz | more

cf. more은 페이지 단위, less는 줄 단위

 

압축 파일 풀기 : gunzip

gzip 명령으로 압축된 파일은 gunzip 명령으로 복원할 수 있으며 옵션 -d를 사용하여 압축된 파일을 복원할 수도 있음

gunzip arch_test.tar.gz

 

2) bzip2 명령으로 압축

bzip2는 gzip 명령에 비해 압축률은 좋지만 속도가 약간 느림

bzip2 [옵션] 파일 이름 : bzip2 형식으로 파일 압축 명령

옵션

-d : 파일 압축 해제

-l : 압축된 파일의 정보 출력

-t : 압축 파일 검사

-v : 압축 정보를 화면에 출력

-best : 최대한 압축

bzip2 arch_test.tar arch_test

 

압축 파일 내용 보기 : bzcat

bzcat arch_test.tar.bz2 | more

 

압축 파일 풀기 : bunzip2

bunzip2 arch_test.tar.bz2

tar cvf my.tar /etc/fonts/  -- 묶기
tar cvJf my.tar.xz /etc/fonts/  -- 묶기+xz로 압축
tar cvzf my.tar.gz /etc/fonts/  -- 묶기+gzip로 압축
tar cvjf my.tar.bz2 /etc/fonts/  -- 묶기+bzip2로 압축
tar tvf my.tar  -- 파일 확인
tar xvf my.tar  -- tar 풀기

tar Cxvf newdir my.tar  -- newdir에 tar 풀기
tar xJf my.tar.xz  -- xz 압축 풀기+tar 풀기
tar xzf my.tar.gz  -- gzip 압축 풀기+tar 풀기
tar xjf my.tar.bz2  -- bzip2 압축 풀기+tar 풀기

 


파일 아카이브와 압축 해보기

tar 아카이브를 현재 디렉터리에 풀기

  • tar xvf T.tar

tar 아카이브를 지정된 디렉터리에 풀기

  • tar xvf T.tar -C ./test

tar 아카이브의 내용 확인하기

  • tar tvf T.tar

현재 디렉터리를 tar로 묶고 gzip으로 압축하기

  • tar cvzf T.tar.gz *

gzip으로 압축된 tar 아카이브를 현재 디렉터리에 풀기

  • tar xvzf T.tar.gz

현재 디렉터리를 tar로 묶고 bzip2로 압축하기

  • tar cvjf T.tar.bz2 *

bzip2로 압축된 tar 아카이브를 현재 디렉터리에 풀기

  • tar xvjf T.tar.bz2

문제 11-03

  1. 새로운 zip_test.d 디렉터리 생성하기
  2. zip_test.d 디렉터리로 이동하기
  3. touch 명령으로 ex_01.txt와 ex_02.txt 파일 생성하기
  4. gedit 창에서 ex_01.txt와 ex_02.txt 파일에 간단한 내용 각각 편집하기
  5. cd ~ 명령으로 사용자 계정의 홈 디렉터리로 이동하기
  6. tar 명령으로 zip_test.d 디렉터리를 대상으로 아카이브 ac_zip.tar 생성하기
  7. 아카이브 ac_zip.tar의 내용 확인하기
  8. ac_zip.tar 아카이브 풀기
$ mkdir zip_test.d
$ cd zip_test.d
$ touch ex_01.txt ex_02.txt
$ gedit ex_01.txt
$ gedit ex_02.txt
$ cd ~
$ tar cvf ac_zip.tar zip_test.d
$ tar tvf ac_zip.tar
$ tar xvf ac_zip.tar

 


소프트웨어 컴파일

컴파일러의 개념 (광의의 개념)

  • 자연어로 작성된 고급언어를 컴퓨터가 이해할 수 있는 기계어로 번역해 주는 SW
  • 자연어를 기계어로 번역해 주는 과정을 컴파일(Compile)이라고 함

gcc 컴파일러

  • 우분투에서 사용하는 C언어 기본 컴파일러는 gcc
  • 관련 패키지가 설치되어 있지 않으면 패키지를 설치하기

aptitude show gcc : aptitude 명령으로 gcc 패키지가 설치되어 있는지 확인

sudo apt-get install gcc : gcc 패키지가 설치 안되어있을 때 패키지 설치

 

gcc 컴파일러 사용

실행 파일의 이름을 사용자가 지정하지 않으면 기본적으로 a.out 파일명으로 생성됨

현재 디렉터리의 경로를 지정하기 위해 ./를 사용하여 ./a.out 명령으로 실행

gcc -o [변경할 실행파일명] [C 프로그램 소스 파일명]

 

geany 에디터 설치

sudo apt-get install geany

sudo apt-get install geany-plugins

 

geany 컴파일러 사용

-c : 링킹 전단계까지 오브젝트 파일을 만들어놓은 상태 (? 확실 X)

컴파일 → 빌드 → 실행

**void qsort(void *base, size_t nmemb, size_t size, int (*compare)(const void , const void ));

  • #include <stdlib.h>에 정의된 quick sort 알고리즘 라이브러리 함수
  • base: 정렬할 배열의 시작 주소
  • nmemb: 배열 요소의 개수
  • size: 배열의 한 요소당 크기 (bytes 단위)
  • compare: 비교 함수의 포인터

**int compare(const void a, const void b)

  • 반환값에 따라 요소들의 순서를 결정
  • 비교 함수는 두 void * 포인터를 받아 정수로 캐스팅 후 값을 비교
반환값 의미 정렬 결과
< 0 a가 b보다 작다 a가 b보다 앞에 온다
== 0 a와 b가 같다 순서 유지 (stable 아님)
> 0 a가 b보다 크다 a가 b보다 뒤에 온다

stable: 순서를 보장하는 것

 

코딩 실습

예제) qsort_example.c

#include <stdio.h>
#include <stdlib.h>

// 오름차순 비교 함수
int compare(const void *a, const void *b) {
	return (*(int*)a - *(int*)b);
}

int main() {
	int arr[] = {7, 2, 9, 4, 3, 8, 1};
	int n = sizeof(arr)/sizeof(arr[0]);
	
	printf("정렬 전 배열: ");
	for (int i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
	printf("\\n");
	
	qsort(arr, n, sizeof(int), compare);
	
	printf("정렬 후 배열: ");
	for (int i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
	printf("\\n");
	
	return 0;
}

cf. (int)a : dereferencing

 

문제 설명

문자열 배열이 주어졌을 때, 다음 기준에 따라 정렬하여 출력하시오.

  1. 문자열 길이가 짧은 순서대로 정렬한다.
  2. 길이가 같다면 사전 순으로 앞선 문자열이 먼저 오도록 정렬한다.

입력

  • 첫 줄에 정렬할 문자열의 개수 N이 주어진다. (1≤N≤100)
  • 이후 N개의 줄에 문자열이 한 줄에 하나씩 주어진다.
  • 문자열은 알파벳 소문자로만 구성되며, 길이는 최대 100자이다.

출력

  • 정렬된 결과를 한 줄에 하나씩 출력한다.

입력 예시:

5

apple

kiwi

banana

pear

grape

 

출력 예시:

kiwi

pear

apple

grape

banana

 

답)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 100
#define MAX_LEN 101

// 문자열 비교 함수
int compare(const void *a, const void *b) {
	const char *str1 = *(const char **)a;
	const char *str2 = *(const char **)b;
	
	int len1 = strlen(str1);
	int len2 = strlen(str2);
	
	if (len1 != len2) {
		return len1 - len2;
	} else {
		return strcmp(str1, str2);
	}
}

int main() {
	int N;
	char *words[MAX];
	
	scanf("%d", &N);
	getchar(); // 개행 문자 제거
	
	for (int i = 0; i < N; i++) {
		words[i] = malloc(MAX_LEN * sizeof(char));  // 동적 할당
		fgets(words[i], MAX_LEN, stdin);
		words[i][strcspn(words[i], "\\n")] = '\\0'; // 개행 제거
	}
	
	qsort(words, N, sizeof(char *), compare);
	
	for (int i = 0; i < N; i++) {
		printf("%s\\n", words[i]);
		free(words[i]);
	}
	
	return 0;
}	

 

words[i][strcspn(words[i], "\n")] = '\0';

  • 널 문자를 넣어서 문자열을 끝내야 하는데 “\0” 를 쓰면 널 문자를 가리키는 포인터로 해석될 수 있기 때문에 작은 따옴표로 써야 함