<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>pride-marimo.log</title>
        <link>https://velog.io/</link>
        <description>개발공부 요약노트</description>
        <lastBuildDate>Sat, 16 May 2026 05:59:14 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>pride-marimo.log</title>
            <url>https://velog.velcdn.com/images/pride-marimo/profile/c5ae2873-f744-43fd-91b9-7579bb884176/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. pride-marimo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pride-marimo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[1. 명령행 인터페이스(7) - vi 편집]]></title>
            <link>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A47-vi-%ED%8E%B8%EC%A7%91</link>
            <guid>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A47-vi-%ED%8E%B8%EC%A7%91</guid>
            <pubDate>Sat, 16 May 2026 05:59:14 GMT</pubDate>
            <description><![CDATA[<p>리눅스의 대표 텍스트 편집기인 <code>vi</code>를 사용해보자.</p>
<p>우선 기본 vi 편집기는 가장 기본적인편집 기능만 있어, 여러 기능이 추가되어 향상된 vim(VI iMproved)를 사용하는 편이 낫다.</p>
<h2 id="vim-패키지-설치">vim 패키지 설치</h2>
<ul>
<li>패키지 정보를 갱신한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/82e19671-d006-4e4d-baab-078d34654784/image.png" alt=""></li>
<li>vim 패키지를 설치한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/7eec0634-7bc8-4b1b-bc71-d0f74e5f036c/image.png" alt=""></li>
<li>터미널에서 vi를 실행시키면 다음과 같은 화면을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/95c6012d-e3ec-4f93-b167-e4e30db9bf3c/image.png" alt=""></li>
<li>편집기 종료 시 <code>:q</code> 를 입력한 후 엔터 키를 누른다.</li>
</ul>
<br>

<h2 id="vi-모드">vi 모드</h2>
<p>vi에는 명령, 입력, ex 세 가지 모드로 텍스트를 편집할 수 있다.</p>
<table>
<thead>
<tr>
<th>모드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>명령모드</td>
<td>텍스트 편집기에서 사용할 수 있는 일반적인 기능을 활용할 수 있는 모드<br>커서 이동, 텍스트 삭제, 복사, 붙여넣기 등을 할 수 있다.</td>
</tr>
<tr>
<td>입력모드</td>
<td>실제 텍스트를 입력할 수 있는 모드<br>명령모드에서 <code>a</code>, <code>i</code>, <code>o</code> 등을 누르면 입력 모드로 전환하며, <code>esc</code> 키를 누르면 입력을 끝내고 다시 명령모드로 돌아온다.</td>
</tr>
<tr>
<td>ex모드</td>
<td>확장기능을 사용할 수  있는 모드<br>파일 저장, 편집기 종료, 텍스트 검색, 치환 등이 가능하다.<br><code>:</code> 을 눌러 ex모드로 전환하며, <code>esc</code> 키를 누르면 다시 명령모드로 돌아온다.</td>
</tr>
</tbody></table>
<blockquote>
<h4 id="vi-외-다른-편집기">vi 외 다른 편집기</h4>
</blockquote>
<ul>
<li><strong>emacs</strong> : 기능이 강력한 텍스트 편집기. 다만 여러 기능을 제공하므로 사용법을 쉽게 익히기 어렵다는 단점이 있다.</li>
<li><strong>nano</strong> : vim이나 emacs보다 사용하기 쉬우며, 우분투에도 기본 설치 되어있다. 방향키를 이용해 커서를 이동하고 <code>ctrl</code> 또는 <code>Alt</code>와 특정 키를 함께 입력하여 텍스트를 편집한다.</li>
<li><strong>gedit</strong> : 우분투 데스크톱에서 사용가능한 그래픽 환경의 텍스트 편집기. 앱 버튼을 누르고 메뉴에서 연필모양의 텍스트 편집키를 클릭하여 실행한다.
터미널에서 <code>gedit [파일명]</code> 형식으로 입력하여 실행도 가능하다.</li>
</ul>
<br>

<h3 id="명령모드에서-사용할-수-있는-키">명령모드에서 사용할 수 있는 키</h3>
<p>vi 편집기에서는 글자단위, 단어단위, 줄단위로 커서를 이동할 수 있다.</p>
<h4 id="커서-이동">커서 이동</h4>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>h</td>
<td>한 칸 왼쪽으로 이동</td>
</tr>
<tr>
<td>l</td>
<td>한 칸 오른쪽으로 이동</td>
</tr>
<tr>
<td>j</td>
<td>한 줄 아래로 이동</td>
</tr>
<tr>
<td>k</td>
<td>한 줄 위로 이동</td>
</tr>
<tr>
<td>w</td>
<td>다음 단어의 첫 글자로 이동(word), 각 특수 문자를 단어로 취급</td>
</tr>
<tr>
<td>W</td>
<td>다음 단어의 첫 글자로 이동, 공백 단위로 이동</td>
</tr>
<tr>
<td>b</td>
<td>이전 단어의 첫 글자로 이동, 각 특수 문자를 단어로 취급</td>
</tr>
<tr>
<td>B</td>
<td>이전 단어의 첫 글자로 이동, 공백 단위로 이동</td>
</tr>
<tr>
<td>e</td>
<td>다음 단어의 마지막 글자로 이동(end word), 각 특수 문자를 단어로 취급</td>
</tr>
<tr>
<td>E</td>
<td>다음 단어의 마지막 글자로 이동, 공백 단위로 이동</td>
</tr>
<tr>
<td>^</td>
<td>커서가 있는 줄의 처음으로 이동</td>
</tr>
<tr>
<td>$</td>
<td>커서가 있는 줄의 마지막으로 이동</td>
</tr>
<tr>
<td><code>enter</code></td>
<td>다음 줄의 첫 글자로 이동</td>
</tr>
<tr>
<td>}</td>
<td>다음 문단으로 이동</td>
</tr>
<tr>
<td>{</td>
<td>이전 문단으로 이동</td>
</tr>
<tr>
<td>(</td>
<td>다음 문장으로 이동</td>
</tr>
<tr>
<td>)</td>
<td>이전 문장으로 이동</td>
</tr>
<tr>
<td>gg</td>
<td>파일 처음 위치로 이동</td>
</tr>
<tr>
<td>G</td>
<td>파일 마지막 위치로 이동</td>
</tr>
</tbody></table>
<h4 id="텍스트-입력">텍스트 입력</h4>
<p>명령모드에서 입력모드로 전환하는 입력 키는 <code>a(append)</code>, <code>i(insert)</code>, <code>o(open)</code> 이다. 대문자와 소문자 키의 기능이 다르므로 주의한다.</p>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>a</td>
<td>커서 위치의 다음 칸부터 입력</td>
</tr>
<tr>
<td>A</td>
<td>커서가 있는 줄의 끝부터 입력</td>
</tr>
<tr>
<td>i</td>
<td>커서 위치부터 입력, insert도 같은 기능</td>
</tr>
<tr>
<td>I</td>
<td>커서가 있는 줄의 맨 앞에서부터 입력</td>
</tr>
<tr>
<td>o</td>
<td>커서 바로 아래에 줄을 만들고 입력</td>
</tr>
<tr>
<td>O</td>
<td>커서 바로 위에 줄을 만들고 입력</td>
</tr>
</tbody></table>
<h4 id="텍스트-삭제">텍스트 삭제</h4>
<p>기본적인 삭제 키는 <code>x(backspace)</code>, <code>d(delete)</code> 이다.</p>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>x</td>
<td>커서 위치의 글자 삭제</td>
</tr>
<tr>
<td>X</td>
<td>커서 바로 앞의 글자 삭제</td>
</tr>
<tr>
<td>dh</td>
<td>커서 바로 앞의 글자 삭제(delete + h), X와 동일</td>
</tr>
<tr>
<td>dl</td>
<td>커서 위치의 글자 삭제(delete + l), x와 동일</td>
</tr>
<tr>
<td>dj</td>
<td>커서가 있는 줄과 그 다음 줄을 삭제(delete + j)</td>
</tr>
<tr>
<td>dk</td>
<td>커서가 있는 줄과 그 앞줄을 삭제(delete + k)</td>
</tr>
<tr>
<td>dw</td>
<td>한 단어를 삭제(delete word)</td>
</tr>
<tr>
<td>d0</td>
<td>커서 위치부터 줄의 처음까지 삭제(delete + 0)</td>
</tr>
<tr>
<td>d$</td>
<td>커서 위치부터 줄의 끝까지 삭제(delete + $)</td>
</tr>
<tr>
<td>D</td>
<td>d$와 동일</td>
</tr>
<tr>
<td>dd</td>
<td>커서가 있는 줄을 삭제</td>
</tr>
<tr>
<td>dgg</td>
<td>커서가 있는 위치부터 처음 위치까지 모두 삭제(delete + gg)</td>
</tr>
<tr>
<td>dG</td>
<td>커서가 있는 위치부터 파일 마지막 위치까지 모두 삭제(delete + G)</td>
</tr>
</tbody></table>
<h4 id="텍스트-수정">텍스트 수정</h4>
<p>명령모드에서 사용할 수 있는 수정 키는 <code>r(replace)</code>, <code>s(subst)</code>, <code>c(change)</code> 이다.</p>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>r</td>
<td>커서 위치의 한 글자 수정</td>
</tr>
<tr>
<td>R</td>
<td>커서 위치부터 esc를 누를 때 까지 다른 글자로 수정, 단 같은 줄에만 해당</td>
</tr>
<tr>
<td>s</td>
<td>커서 위치의 한 글자를 지우고 입력 모드에서 수정</td>
</tr>
<tr>
<td>S</td>
<td>커서가 있는 줄을 지우고 입력 모드에서 수정</td>
</tr>
<tr>
<td>ch</td>
<td>커서 바로 앞의 한 글자를 지우고 입력 모드에서 수정(change + h), S와 동일</td>
</tr>
<tr>
<td>cl</td>
<td>커서 위치의 한 글자를 지우고 입력 모드에서 수정(change + l), s와 동일</td>
</tr>
<tr>
<td>cj</td>
<td>커서가 있는 줄과 그 다음 줄을 수정(change + j)</td>
</tr>
<tr>
<td>ck</td>
<td>커서가 있는 줄과 그 앞줄을 수정(change + k)</td>
</tr>
<tr>
<td>cw</td>
<td>커서 위치부터 한 단어를 수정(change word)</td>
</tr>
<tr>
<td>c0</td>
<td>커서 위치부터 줄의 처음을 지우고 입력 모드에서 수정(change + 0)</td>
</tr>
<tr>
<td>c$</td>
<td>커서 위치부터 줄의 끝까지 지우고 입력 모드에서 수정(change + $)</td>
</tr>
<tr>
<td>C</td>
<td>c$와 동일</td>
</tr>
<tr>
<td>cc</td>
<td>커서가 있는 줄을 지우고 입력 모드에서 수정</td>
</tr>
<tr>
<td>J</td>
<td>커서가 있는 줄과 다음 줄을 연결(join)</td>
</tr>
</tbody></table>
<h4 id="텍스트-복사">텍스트 복사</h4>
<p>복사 키는 <code>y(yank)</code>, 붙여넣기는 <code>p(paste)</code> 이다.</p>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>yw</td>
<td>커서 위치부터 단어 끝까지 복사(yank word)</td>
</tr>
<tr>
<td>y0</td>
<td>커서 위치부터 줄의 처음까지 복사(yank + 0)</td>
</tr>
<tr>
<td>y$</td>
<td>커서 위치부터 줄의 끝까지 복사(yank + $)</td>
</tr>
<tr>
<td>yy</td>
<td>커서가 있는 줄을 복사</td>
</tr>
<tr>
<td>yj</td>
<td>커서가 있는 줄과 그 다음줄을 복사(yank + j)</td>
</tr>
<tr>
<td>yk</td>
<td>커서가 있는 줄과 그 앞줄을 복사(yank + k)</td>
</tr>
<tr>
<td>p</td>
<td>커서의 다음 위치에 붙여넣기</td>
</tr>
<tr>
<td>P</td>
<td>커서가 있는 위치에 붙여넣기</td>
</tr>
</tbody></table>
<h4 id="작업-취소-반복">작업 취소, 반복</h4>
<p>작업 취소는 <code>u(undo)</code>,  다시 실행은 <code>ctrl + r(redo)</code> 이다. 취소된 작업에 한해서만 재실행이 가능하며 <code>.</code>는 명령을 반복한다.</p>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>u</td>
<td>작업 취소</td>
</tr>
<tr>
<td>U</td>
<td>그 줄에 행해진 작업 모두 취소</td>
</tr>
<tr>
<td>ctrl + r</td>
<td>다시 실행</td>
</tr>
<tr>
<td>.</td>
<td>조금전에 했던 명령 반복</td>
</tr>
</tbody></table>
<h4 id="텍스트-검색">텍스트 검색</h4>
<p>명령모드에서 텍스트를 검색할 수 있는 키는 <code>/</code>와 <code>?</code>이다. 파일 내에 동일한 텍스트가 더 있는지 찾으려면 <code>n(next)</code>를 사용한다.</p>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>/[검색어]</td>
<td>현재 커서를 기준으로 후방으로 텍스트 검색</td>
</tr>
<tr>
<td>?[검색어]</td>
<td>현재 커서를 기준으로 전방으로 텍스트 검색</td>
</tr>
<tr>
<td>n</td>
<td>같은 방향으로 검색할 텍스트를 계속 검색</td>
</tr>
<tr>
<td>N</td>
<td>반대 방향으로 검색할 텍스트를 계속 검색</td>
</tr>
</tbody></table>
<h4 id="영역-선택">영역 선택</h4>
<p>명령 모드에서 블록으로 영역을 선택하려면 <code>v(visual)</code>을 누른다.
영역 지정 후 <code>y</code>를 누르면 명령 모드로 돌아오면서 선택한 영역을 복사하며, 명령모드에서 다시 <code>p</code>를 눌러 복사한 내용을 붙여넣기 할 수 있다.</p>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>v</td>
<td>커서가 위치한 곳부터 블록 지정</td>
</tr>
<tr>
<td>V</td>
<td>커서가 있는 줄부터 블록 지정</td>
</tr>
</tbody></table>
<br>

<h3 id="파일-작성하기">파일 작성하기</h3>
<p><code>vi [파일명]</code> 형식으로 새로 생성하거나 추가할 파일 이름과 함께 vi 편집기를 실행한다.</p>
<h4 id="파일-생성-및-내용-작성">파일 생성 및 내용 작성</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/bc8cb579-9d4f-4fb1-8e69-c34f094abb9f/image.png" alt="">
파일을 새로 생성하는 경우 위처럼 파일 명 오른쪽에 새 파일임이 표시된다.
틸트(~) 표시는 줄이 비어있다는 의미이다.</p>
<p>현재 상태는 명령모드이다. </p>
<p><code>a</code> 또는 <code>i</code>를 누르면 화면 아래 정보가 다음과 같이 변경되며 입력모드로 전환된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/1f186b97-80d5-4415-b31b-d9f0010d71f6/image.png" alt=""></p>
<p>한글 입력도 가능하나, 입력모드로 전환하는 a 또는 i는 반드시 영문 상태에서 입력해야한다.</p>
<p>파일 저장을 위해서는 다시 명령모드로 전환 후 ex모드로 전환해야한다.</p>
<p>파일 내용을 모두 입력했다면 <code>esc</code> 키를 눌러 명령 모드로 전환한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/016da6a9-f1b8-4f56-bf65-49c0f8d1dd68/image.png" alt=""></p>
<p>화면 아래 끼워넣기 또는 INSERT 문구가 사라졌다면 <code>:</code>을 눌러 ex모드로 전환하여 명령어를 입력한다.
이때, <code>:wq</code>는 파일을 저장하고 vi를 종료하여 명령행으로 되돌아가는 ex 명령이다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/7388f3ba-65bf-455a-b6e3-9c6dc67bf6c8/image.png" alt=""></p>
<p>명령행 프롬프트가 나타나면 <code>ls</code> 또는 <code>cat</code> 명령으로 파일이 제대로 생성되었는지 확인해본다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/75a22a3a-2940-424f-9496-bba87adb732b/image.png" alt=""></p>
<h4 id="파일-내용-복사-붙여넣기">파일 내용 복사 붙여넣기</h4>
<p>문자열 복사 후 붙여넣기 실습을 해보자.
우선 파일을 다시 vi로 열고, 복사할 단어 앞으로 커서를 옮긴 후 <code>v</code> 키를 누르면 화면 아래 메시지가 비주얼 또는 VISUAL 로 바뀐다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/618a548a-c163-48a7-8ba8-ff3282b60630/image.png" alt=""></p>
<p>커서 이동 키를 누르면 텍스트를 블록으로 지정할 수 있다. 오른쪽 화살표를 눌러 문장 마지막까지 이동한 후 <code>y</code>를 누르면 비주얼 표시가 사라지며 다시 명령모드로 돌아온 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/91a34412-0316-42c3-ab0a-b38fdfc3bd0c/image.png" alt=""></p>
<p>내용을 붙여넣을 위치에 커서를 위치시키고 <code>p</code>를 누르면 복사한 내용을 불러온다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/a7585f9f-1b9c-4c70-86f7-bb2b1b2df78f/image.png" alt=""></p>
<p><code>dd</code>를 누르면 커서가 있는 줄이 삭제된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/52a851b7-c387-42a2-8820-78423b3dceb2/image.png" alt=""></p>
<p>커서를 가장 아래로 옮긴 후 다시 <code>p</code>를 누르면 삭제했던 줄을 불러온다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/acf09b88-5e8b-4b53-b3b4-69f46cc9cf35/image.png" alt=""></p>
<p><code>y</code>와 <code>p</code>가 복사, 붙여넣기라면 <code>d</code>와 <code>p</code>는 잘라내기와 붙여넣기로 볼 수 있다.</p>
<br>

<h3 id="ex-모드에서-사용할-수-있는-키">ex 모드에서 사용할 수 있는 키</h3>
<p>ex 명령의 장점은 일반 명령보다 빠르고 효율적으로 처리할 수 있다는 것이다.
한 번 실행으로 동시에 여러 부분을 변경할 수 있다.</p>
<h4 id="파일-저장-편집기-종료-키">파일 저장, 편집기 종료 키</h4>
<table>
<thead>
<tr>
<th>키</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>:q</td>
<td>아무런 변경 하지 않았을 때 종료</td>
</tr>
<tr>
<td>:q!</td>
<td>변경된 내용을 저장하지 않고 강제 종료</td>
</tr>
<tr>
<td>:wq</td>
<td>저장하고 종료</td>
</tr>
<tr>
<td>:x</td>
<td>wq와 같은 기능</td>
</tr>
<tr>
<td>:w</td>
<td>[파일]:새 이름으로 파일 저장(write)</td>
</tr>
</tbody></table>
<h4 id="자주-사용하는-ex-명령">자주 사용하는 ex 명령</h4>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>:sh</td>
<td>셸을 실행한다. <code>exit</code>명령 또는 <code>ctrl + D</code>를 눌러 vim으로 되돌아 올 수 있다</td>
</tr>
<tr>
<td>:15</td>
<td>행 번호 15로 이동한다</td>
</tr>
<tr>
<td>:r [파일명]</td>
<td>파일 내용을 읽어 현재 위치 아래에 삽입(read)한다</td>
</tr>
<tr>
<td>;10,20w [파일명]</td>
<td>10번부터 20번 줄까지 파일에 쓰기(write)한다</td>
</tr>
<tr>
<td>:10,20d</td>
<td>10번부터 20번 줄까지 삭제(delete)한다</td>
</tr>
<tr>
<td>:0,$4-d</td>
<td>0번부터 끝에서 네줄까지 제외($-4)하고 모두 삭제한다</td>
</tr>
<tr>
<td>:20,25y</td>
<td>20번부터 25번 줄까지 복사(yank)한다</td>
</tr>
<tr>
<td>:10,15m40</td>
<td>10번부터 15번 줄까지 40번 줄 아래로 옮긴다(move)</td>
</tr>
<tr>
<td>:10,15co40</td>
<td>10번부터 15번 줄까지 40번 줄 아래로 복사(copy)한다</td>
</tr>
<tr>
<td>:s/abc/def</td>
<td>커서 위치부터 줄 끝까지 처</td>
</tr>
<tr>
<td>:10,15s/abc/def</td>
<td>10번부터 15번 줄까지 40번 줄 아래로 복사(copy)한다</td>
</tr>
<tr>
<td>s/abc/def/g</td>
<td>커서 위치부터 줄 끝까지 모든 문자열 abc를 def로 바꾼다</td>
</tr>
<tr>
<td>:%s/abc/def/g</td>
<td>파일 전체에서(%) 줄 끝까지 모든 문자열 abc를 def로 바꾼다</td>
</tr>
<tr>
<td>:g/abc/m0</td>
<td>파일에서 문자열 abc를 포함하는 줄을 찾아 맨 위(행 번호0)으로 옮긴다</td>
</tr>
<tr>
<td>:v/abc/m0</td>
<td>파일에서 문자열 abc를 포함하지 않는 줄을 찾아 맨 위로 옮긴다</td>
</tr>
<tr>
<td>:10,15g/abc/m0</td>
<td>10번부터 15번 줄까지 문자열 abc를 포함하는 줄을 찾아 맨 위로 옮긴다</td>
</tr>
</tbody></table>
<p>아래 명령어는 16번줄을 2번 줄로 옮긴다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/932bff1d-8b10-41c6-a55d-2be4bb8ba079/image.png" alt=""></p>
<p><code>[시작 행 번호],[끝 행 번호]</code> 형태로 명령을 적용할 줄을 선택할 수도 있다.
아래 명령어는 1번줄과 2번줄을 복사하여 3번줄 뒤에 붙인다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/9a7a121c-6d72-4879-ae6c-c908b4e82bb5/image.png" alt=""></p>
<p>문자열 치환도 가능하다.
파일 전체(<code>%</code>)에서 모든(<code>g</code>) 문자열 ball game을 찾아 BASEBALL GAME으로 바꾼다(<code>s</code>)
<img src="https://velog.velcdn.com/images/pride-marimo/post/1548254a-f640-44c9-9371-f6075a9ff110/image.png" alt="">
<img src="https://velog.velcdn.com/images/pride-marimo/post/0444a390-a4ef-41bb-99de-45b1fc09b90b/image.png" alt=""></p>
<h4 id="set-명령">set 명령</h4>
<p>ex 모드에서 set 명령을 사용하면 vi 환경 설정 내용을 변경할 수 있다.</p>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>:set number</td>
<td>줄마다 행 번호 보이도록 설정. 단축 명령어는 <code>se nu</code><br>행 번호 해제하려면 <code>set nonumber</code> 또는 <code>set nonu</code>라고 입력한다</td>
</tr>
<tr>
<td>:syntax on</td>
<td>구문 강조 기능 사용</td>
</tr>
<tr>
<td>:set autoindent</td>
<td>자동 들여쓰기 기능 사용</td>
</tr>
<tr>
<td>:set smartindent</td>
<td>똑똑한 들여쓰기 기능 사용</td>
</tr>
<tr>
<td>:set cindent</td>
<td>C 프로그램 들여쓰기 사용</td>
</tr>
<tr>
<td>:set shiftwidth=4</td>
<td>들여쓰기 4칸으로 설정</td>
</tr>
<tr>
<td>:set expandtab</td>
<td>tab을 누르면 공백 삽입</td>
</tr>
<tr>
<td>:set tabstop=4</td>
<td>기본적인 탭 간격은 8칸인데 4칸으로 변경</td>
</tr>
<tr>
<td>:set paste</td>
<td>터미널에서 붙여넣을 때 자동 들여쓰기 활성화</td>
</tr>
<tr>
<td>:set hlsearch</td>
<td><code>/</code>나 <code>?</code>로 검색한 텍스트 강조. 해제하려면 <code>:nohl</code>을 입력한다</td>
</tr>
<tr>
<td>:set showmatch</td>
<td>일치하는 괄호 강조</td>
</tr>
<tr>
<td>:set ruler</td>
<td>현재 커서 위치 표시</td>
</tr>
</tbody></table>
<p>set 명령을 별도의 설정파일 .vimrc로 홈 디렉토리에 저장해두면 나만의 vim 환경을 만들 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/9316113c-e513-470b-8faa-f6219fb078a0/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 명령행 인터페이스(6) - 서비스 관리하기]]></title>
            <link>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A46-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A46-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 11 Apr 2026 09:15:27 GMT</pubDate>
            <description><![CDATA[<p>부팅부터 시작해서 전체 시스템을 관리하는 systemd 도구를 소개한다.
명령행에서 systemd를 다루는 <code>systemctl</code> 명령으로 시스템 서비스를 어떻게 제어하는 지 확인해본다.</p>
<h2 id="systemd--시스템-관리자"><code>systemd</code> : 시스템 관리자</h2>
<p>부팅 과정부터 다시 되새겨보자.</p>
<ul>
<li>BIOS/UEFI가 파티션 정보를 읽고 부트로더 GRUP를 실행한다.</li>
<li>GRUB는 저장장치에서 커널을 찾아 메모리에 올리고 시스템 제어 권한을 커널에 넘긴다.</li>
<li>커널은 시스템 초기화 이미지인 initrd를 메모리에 마운트한다.
(initrd에는 장치드라이버를 비롯한 시스템 초기화 도구들이 들어있다.)</li>
<li>커널은 필요한 도구를 실행하고 저장 장치의 진짜 루트 파일 시스템을 마운트한다.</li>
<li>리눅스 커널은 모든 프로세스의 부모 역할을 하는 PID 1번 프로세스 init을 실행한다.<ul>
<li>init은 사용자가 시스템을 사용할 수 있는 환경을 만들어준다. (시스템초기화)</li>
<li><code>ps 1</code>로 1번 프로세스 systemd를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/4f5c5d95-5617-40bd-8aaa-35507e22c02b/image.png" alt=""></li>
</ul>
</li>
</ul>
<p>시스템 관리자 systemd는 부팅 프로세스만 처리하지 않고 시스템을 전반적으로 관리하는 만능 도구로 개발되었다.</p>
<br>

<h2 id="systemctl-옵션명령--systemd-관리"><code>systemctl [옵션][명령]</code> : systemd 관리</h2>
<h3 id="systemctl로-시스템-상태-조회"><code>systemctl</code>로 시스템 상태 조회</h3>
<p><code>systemctl list-units</code>는 유형별 유닛 목록과 상태를 화면에 표시한다.
옵션이나 추가 명령 없이 <code>systemctl</code> 을 실행한 결과와 동일하나, <code>more</code>나 <code>less</code> 처럼 up/down 버튼으로 화면 스크롤이 가능하다. q를 누르면 스크롤을 종료하고 명령 프롬프트로 돌아온다.</p>
<ul>
<li>UNIT : 유닛 이름. 유니 ㅅ 이름 뒤에 붙는 접미사(automount, device, service 등)로 어떤 유형의 유닛인지 짐작 가능</li>
<li>LOAD : systemd가 유닛 설정 파일을 정상적으로 읽어들였는지 확인</li>
<li>ACTIVE : 유닛이 동작하고 있는지 여부</li>
<li>SUB : 유닛이 활성(active) 상태라면 추가 상태 정보 표기</li>
<li>DESCRIPTION : 유닛에 대한 세부 설명</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/53f9601b-153b-4824-829d-e110483098f2/image.png" alt=""></p>
<p><code>-t</code> 옵션 추가 시 유닛 유형에 따라 결과를 필터링 할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/176974fe-71af-418c-8a94-c94c12a7e578/image.png" alt=""></p>
<p>target은 부팅 과정에서 활성화 할 유닛을 결정하는 유닛이다.
<code>systemctl list-dependencies</code> 는 의존 관계에 있는 유닛을 보여준다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/7a68a371-5027-4f5d-89f2-8f7bf5c7008c/image.png" alt=""></p>
<p><code>systemctl get-default</code>는 현재 기본 target을 보여준다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/2815ac34-ea46-4285-9951-e6d4def4bedb/image.png" alt=""></p>
<p>systemd의 default.target은 graphical.target의 심볼릭링크이다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/77970467-9427-43f5-a8f8-8f97bc255232/image.png" alt=""></p>
<p>명령 인자 없이 <code>systemctl list-dependencies</code>를 실행하면 기본 target과 관련된 유닛 목록을 보여준다. target 유닛을 기준으로 살펴보면 systemd가 부팅 과정에서 시스템을 초기화하고자 어떤 일을 처리하는지 유추할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/482fe01b-6edf-47c2-b0b1-265d26495903/image.png" alt=""></p>
<h3 id="systemctl로-서비스-제어하기"><code>systemctl</code>로 서비스 제어하기</h3>
<p><code>status</code>는 서비스 상태를 보여주는 명령이다.
<code>status</code> 로 조회 시 순서대로 서비스 이름과 설명, 서비스 유닛 설정파일 상태, 서비스 활성화 여부, 프로세스 정보, 서비스 실행과정에서 발생한 로그가 화면에 표시된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b47f1a5b-c230-4776-a096-34328525496b/image.png" alt=""></p>
<p><code>is-active</code> 로 서비스가 현재 활성화 상태인지 확인할 수 있다.
<code>is-enabled</code>로 부팅과정에서 자동 활성화 되는지 여부를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/6fc10eaf-d099-4f6d-83b3-d7e21eafca2a/image.png" alt=""></p>
<p><code>stop</code> 명령으로 서비스를 중지할 수 있다. 시스템 상태를 변경하므로 <code>sudo</code>가 필요하다.
중지 후 상태를 확인해보면 <strong>inactive</strong>로 상태가 변경된 것을 확인해 볼 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/164503ca-068b-44ae-a4e0-4e94baee5ae4/image.png" alt=""></p>
<p>그 외 상태를 변경하는 명령은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>start</code></td>
<td>서비스 시작</td>
</tr>
<tr>
<td><code>restart</code></td>
<td>서비스 재시작</td>
</tr>
<tr>
<td><code>disabled</code></td>
<td>부팅 과정에서 서비스 자동 활성화 해제</td>
</tr>
<tr>
<td><code>enabled</code></td>
<td>부팅 과정에서 서비스 자동 활성화 설정</td>
</tr>
</tbody></table>
<h2 id="journalctl--systemd-로그-정보-조회"><code>journalctl</code> : systemd 로그 정보 조회</h2>
<p><code>systemctl status</code> 로 확인할 수 있는 로그 정보는 systemd-journald가 제공한다. systemd-journald는 부팅부터 발생하는 systemd의 로그 정보를 이진 자료로 저장하므로, <code>journalctl</code> 명령으로만 확인할 수 있다.</p>
<p><code>-u(--unit)</code> 옵션으로 특정 유닛에 대한 로그 정보를 조회 할 수 있다. 로그 정보는 <code>less</code>로 화면에 출력된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/603d31b8-4f8c-473e-a341-f21e94c56edf/image.png" alt=""></p>
<p><code>-e(--pager-end)</code> 옵션으로 최근 로그정보부터 화면 스크롤을 시작할 수 있다. <code>-u</code> 옵션과 함께 사용이 가능하다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/a3efe217-226b-474d-a62a-338dc0a31138/image.png" alt=""></p>
<p><code>-p(--priority)</code> 옵션으로 로그 정보를 중요도에 따라 필터링이 가능하다.</p>
<table>
<thead>
<tr>
<th>로그수준</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>emerg</td>
<td>응급</td>
</tr>
<tr>
<td>alert</td>
<td>경고</td>
</tr>
<tr>
<td>crit</td>
<td>심각</td>
</tr>
<tr>
<td>err</td>
<td>오류</td>
</tr>
<tr>
<td>warning</td>
<td>주의</td>
</tr>
<tr>
<td>notice</td>
<td>알림</td>
</tr>
<tr>
<td>info</td>
<td>의미있는 정보</td>
</tr>
<tr>
<td>debug</td>
<td>디버그 정보</td>
</tr>
</tbody></table>
<p>예를 들어 다음 명령은 장치 설정 과정에서 발생한 오류를 찾아내고자 systemd-udevd의 오류 로그만 조회한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/bfc705bf-4043-4dfd-9887-78eb6c0258f6/image.png" alt=""></p>
<p><code>-S(--since)</code>나 <code>-U(--until)</code> 옵션 사용 시 날짜나 시간을 기준으로 로그 필터링이 가능하다. 혼용 또한 가능하다.
<strong>연-월-일 시:분:초</strong> 형식으로 입력하며, 시간이 생략되면 <strong>00:00:00</strong> 이 기준이 된다.
날짜대신 yesterday, today, tomorrow를 입력하여 어제, 오늘, 내일을 지정할 수도 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/7d5dbf0a-8955-4aee-9632-91c486e94305/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/4a692c5d-957d-43df-8d76-620c57e23300/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 명령행 인터페이스(5) - 패키지 관리하기]]></title>
            <link>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A45-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A45-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 11 Apr 2026 07:36:54 GMT</pubDate>
            <description><![CDATA[<h2 id="패키지-관리-명령">패키지 관리 명령</h2>
<h3 id="dpkg-옵션명령--기본-패키지-관리"><code>dpkg [옵션][명령]</code> : 기본 패키지 관리</h3>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>-i(--install)</code></td>
<td>패키지를 설치하거나 최신 버전으로 업그레이드</td>
</tr>
<tr>
<td><code>-r(--remove)</code></td>
<td>설정파일은 그대로 두고 패키지를 삭제</td>
</tr>
<tr>
<td><code>-P(--purge)</code></td>
<td>패키지와 함께 설정 파일까지 모두 삭제</td>
</tr>
<tr>
<td><code>-C(--audit)</code></td>
<td>패키지가 제대로 설치되었는지 확인</td>
</tr>
<tr>
<td><code>-s(--status)</code></td>
<td>패키지 상태 정보 출력</td>
</tr>
<tr>
<td><code>-L(--listfiles)</code></td>
<td>패키지에 들어있는 파일과 경로 표출</td>
</tr>
<tr>
<td><code>-l(--list)</code></td>
<td>패키지 설치 정보 표출</td>
</tr>
</tbody></table>
<p><code>wget</code> 명령으로 우분투 한국 미러 사이트에서 cosway라는 패키지 파일을 내려받는다. 또는 웹 브라우저로 우분투 패키지 저장소에서 내려받는다.</p>
<p><code>sudo dpkg -i</code> 명령어로 내려받은 deb 파일을 통해 패키지를 설치한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/c540d610-b7d8-4374-b2d4-fa289f3fe07c/image.png" alt=""></p>
<p>설치가 완료되면 다음처럼 명령어를 활용하여 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/3d21afa2-5322-4514-9fa6-a9e88b7470fc/image.png" alt=""></p>
<p><code>-L</code> 옵션으로 설치된 패키지 파일 목록을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b8c9c09a-9e09-4c58-8cd1-aa3573d98bd6/image.png" alt=""></p>
<p><code>-l</code> 옵션으로 시스템에 설치된 패키지 목록을 조회할 수있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/0f5566ea-6840-4284-8b6f-b29f0c265ea6/image.png" alt=""></p>
<h3 id="apt-옵션명령패키지--향상된-패키지-관리-도구"><code>apt [옵션][명령][패키지]</code> : 향상된 패키지 관리 도구</h3>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>update</code></td>
<td>sources.list에서 패키지 목록 호출</td>
</tr>
<tr>
<td><code>upgrade</code></td>
<td>시스템에 설치되어 있는 패키지를 최신 버전으로 업그레이드</td>
</tr>
<tr>
<td><code>full-upgrade</code></td>
<td>시스템에 설치되어 있는 패키지를 업그레이드하며, 의존 관계에 있는 패키지를 추가로 설치하거나 삭제</td>
</tr>
<tr>
<td><code>install</code></td>
<td>패키지 설치. 다른 패키지가 추가로 필요한 경우 함께 설치여부 확인함. 설치되어있는 패키지 버전이 낮다면 업그레이드</td>
</tr>
<tr>
<td><code>remove</code></td>
<td>패키지 삭제</td>
</tr>
<tr>
<td><code>purge</code></td>
<td>패키지와 함께 관련 설정파일도 모두 제거</td>
</tr>
<tr>
<td><code>autoremove</code></td>
<td>미사용하는 불필요한 패키지 제거</td>
</tr>
<tr>
<td><code>search</code></td>
<td>패키지 검색</td>
</tr>
<tr>
<td><code>show</code></td>
<td>패키지 정보 상세 조회</td>
</tr>
</tbody></table>
<h4 id="apt로-패키지-설치"><code>apt</code>로 패키지 설치</h4>
<ul>
<li>패키지 저장소의 주소를 소스 리스트, /etc/apt/sources.list에 추가</li>
<li><code>apt update</code> 명령</li>
<li>패키지 저장소에서 새로운 패키지 목록 호출</li>
<li><code>apt install</code> 명령</li>
<li>패키지 설치</li>
</ul>
<br>

<p>패키지 설치 전 <code>sudo apt update</code>로 패키지 목록을 갱신한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/cb1597f6-5530-4b32-9f39-132cd0d27351/image.png" alt=""></p>
<p><code>apt search [패턴]</code> 형식으로 패키지 이름을 검색한다. 
<img src="https://velog.velcdn.com/images/pride-marimo/post/385eb09d-5e27-46a2-970a-be1ccd5da676/image.png" alt=""></p>
<p><code>apt show</code> 로 설치하려는 패키지 정보를 자세하게 조회할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/c82803b2-c264-4ae1-bd9a-0a5f88f39977/image.png" alt=""></p>
<p><code>apt depends</code> 명령으로 설치하려는 패키지와 의존관계에 있는 패키지 목록을 조회할 수 있다. <code>dpkg</code> 와 달리 <code>apt</code>는 의존관계에 있는 패키지를 자동 설치하므로 매번 확인할 필요는 없다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/56bada94-16e6-487e-8157-ed32e89f329b/image.png" alt=""></p>
<p><code>sudo apt install</code> 명령으로 패키지를 설치한다. 이 때 <code>-y</code> 옵션을 추가하면 패키지를 설치할 지 확인하는 과정을 생략한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/60a55c95-fee1-4f5e-935f-69d49d50c8f8/image.png" alt=""></p>
<p>설치 후 패키지를 실행해보면 프로그램이 실행된다.</p>
<h4 id="apt로-패키지-삭제"><code>apt</code>로 패키지 삭제</h4>
<p><code>sudo apt remove</code>로 패키지를 삭제한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/011be102-382c-48ae-a2ab-5897542d5081/image.png" alt=""></p>
<p>패키지 삭제 중 설정파일이나 임시파일이 일부 남겨지기도 한다.
<code>sudo apt purge</code>로는 패키지를 완전히 삭제할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/c808d444-899a-4c9b-adde-96268e0dcec5/image.png" alt=""></p>
<p>앞서 제거한 패키지들과 의존관계에 있어 더이상 필요없게 된 패키지는 <code>sudo apt autoremove</code>로 삭제할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b45b266d-a77b-4837-84e6-f9c540b2eaf0/image.png" alt=""></p>
<h4 id="apt로-패키지-업데이트"><code>apt</code>로 패키지 업데이트</h4>
<p><code>apt list</code>로 업데이트 가능한 패키지를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/e527c651-6d1e-41e4-a655-e494c87664b2/image.png" alt=""></p>
<p><code>apt upgrade</code> 명령어로 업데이트 가능한 모든 패키지를 새로운 버전으로 갱신한다.
<img src="blob:https://velog.io/f38c48cd-8a06-4ae9-8772-9dfa4688171b" alt="업로드중.."></p>
<blockquote>
<h4 id="apt-사용-중-락-걸리는-경우"><code>apt</code> 사용 중 락 걸리는 경우</h4>
<p>여러 곳에서 원격 호스트에 접속하여 작업 중 패키지를 설치할 때 lock이 걸릴 수 있다. 다른 사용자 또는 다른 프로세스가 동시에 <code>apt</code>를 사용하려고 하여 생기는 문제이다.
이 때, <span style="background-color:#fff5b1"><strong>잠금파일 <em>/var/lib/dpkg/lock</em> 을 삭제</strong>하면 다시 <code>apt</code> 명령을 실행할 수 있다.</span></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 명령행 인터페이스(4) - 파일 관리하기(2)]]></title>
            <link>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A44-%ED%8C%8C%EC%9D%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B02</link>
            <guid>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A44-%ED%8C%8C%EC%9D%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B02</guid>
            <pubDate>Sat, 17 Jan 2026 08:01:13 GMT</pubDate>
            <description><![CDATA[<h2 id="리눅스-시스템-디렉터리">리눅스 시스템 디렉터리</h2>
<p>우분투 설치 시 기본적으로 파일 시스템에 시스템 디렉터리가 생성된다. 시스템 디렉터리는 중요한 의미를 갖기 때문에 함부로 삭제하거나 변경해서는 안된다.</p>
<p>루트 디렉터리 조회 시 시스템 디렉터리 목록을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b7eac5b8-7f55-4526-a6b5-831583df3006/image.png" alt=""></p>
<h4 id="폴더-별-역할">폴더 별 역할</h4>
<ul>
<li><code>/</code> : 모든 디렉터리가 시작하는 뿌리인 최상위 디렉터리. 루트로 읽는다.</li>
<li><code>/boot</code> : <strong>부팅에 필요한 커널, 초기화 이미지, 부트로더</strong> 관련 파일</li>
<li><code>/dev</code> : 시스템에 설치된 장치(마우스, 모니터, 그래픽카드, 저장장치) 파일</li>
<li><code>/etc</code> : 사용자/그룹정보, 파일 시스템테이블, 네트워크 설정파일 등 <strong>시스템 설정 파일</strong></li>
<li><code>/bin</code> : 사용자가 사용하는 가장 <strong>기본적인 명령</strong>들의 실행 파일</li>
<li><code>/lib</code> : <strong>공유 라이브러리 파일</strong>. 시스템 부팅과 응용 프로그램 실행에 필요</li>
<li><code>/home</code> : <strong>사용자 계정 별 홈 디렉터리</strong>. 사용자 계정 생성 시 사용자 계정 이름과 동일한 홈 디렉터리가 이 경로 아래에 생성된다. 사용자는 각자의 파일을 사용자 홈 디렉터리에 저장한다.</li>
<li><code>/root</code> : <strong>루트 계정을 위한 홈 디렉터리</strong>. 일반 사용자는 접근하지 못하도록 권한 설정이 되어있다.</li>
<li><code>/sbin</code> : 루트 권한이 필요한 <strong>시스템 관리 명령</strong></li>
<li><code>/tmp</code> : <strong>임시로 파일을 생성/삭제</strong>하는 공간. 주로 사용자 프로그램에서 임시로 읽어들여야하는 입출력 파일들을 저장하는 데 사용</li>
<li><code>/var</code> : <strong>시스템 운영 중 발생하는 임시 파일(시스템 로그, 웹사이트 콘텐츠, 전자메일 등) 보관</strong>. 크기가 계속 변하는 파일을 저장한다.</li>
<li><code>/usr</code> : 사용자가 추가로 설치한 응용 프로그램 파일 저장</li>
<li><code>/proc</code> : 시스템 정보 제공. 리눅스 커널과 통신하는 가상 파일 시스템 디렉터리로 실제 공간을 차지하지는 않는다. CPU 정보, 인터럽트 목록, 입출력 주소 목록 등 커널이 제공하는 정보가 파일 형태로 저장</li>
<li><code>/opt</code> : 외부에서 제공되는 패키지가 설치</li>
</ul>
<br>

<h2 id="디렉터리-다루기">디렉터리 다루기</h2>
<h3 id="pwd--현재-작업중인-디렉터리-위치를-화면에-표시"><code>pwd</code> : 현재 작업중인 디렉터리 위치를 화면에 표시</h3>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/4ba61e05-0ab2-465f-b4ae-9cdbf318ffab/image.png" alt=""></p>
<h3 id="mkdir--디렉터리-생성"><code>mkdir</code> : 디렉터리 생성</h3>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/01fac645-eb69-409d-8684-66c5133b7e7d/image.png" alt="">
<span style="background-color:#fff5b1"><strong><code>-p(--parent)</code> 옵션을 활용하면 상위 디렉터리를 포함하는 하위 디렉터리를 생성한다.</strong></span>
예를 들어, 현재 작업 디렉터리에 BaseballTeam이라는 디렉터리를 만들고, 다시 그 아래 KBO라는 디렉터리를 생성하려면 mkdir을 두 번 실행해야 하지만, <code>-p</code> 옵션으로 한 번에 생성 가능하다. <strong>옵션 없이 실행하면 오류가 발생</strong>하니 주의하자.
<img src="https://velog.velcdn.com/images/pride-marimo/post/cb62a283-0482-4b01-a516-c273ce43911b/image.png" alt=""></p>
<h3 id="cd--디렉터리-이동"><code>cd</code> : 디렉터리 이동</h3>
<p><code>cd [옵션][디렉터리]</code> 형식으로 작성한다. 
<img src="https://velog.velcdn.com/images/pride-marimo/post/a18cc334-5670-45f7-b0fc-c1869c5e236d/image.png" alt="">
<code>ls -al</code>로 확인 가능한 숨김 폴더 중 <code>.</code> 는 현재 디렉터리, <code>..</code> 는 현재 디렉터리의 상위 디렉터리를 의미한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/af536ce2-f6f8-4faf-bbda-e70d50953ce5/image.png" alt=""></p>
<p><code>cd ..</code>를 입력하면 상위 디렉터리로 이동한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/cbda20ea-2480-476f-876c-1eb45e1e03cc/image.png" alt=""></p>
<p><code>cd</code> 명령 실행 중 대상 디렉터리를 입력하지 않거나 틸트(<code>~</code>)를 입력하면 해당 사용자 계정의 홈 디렉터리로 바로 이동한다. 
<img src="https://velog.velcdn.com/images/pride-marimo/post/f94c5363-7225-4bcd-b0c2-d3c05e2ff964/image.png" alt=""></p>
<blockquote>
<h4 id="상대-경로와-절대-경로를-이용한-이동">상대 경로와 절대 경로를 이용한 이동</h4>
<p>📍<code>pwd</code> 명령은 항상 현재 디렉터리를 절대 경로로 표시한다.</p>
</blockquote>
<ul>
<li><strong>상대 경로</strong> : 특정 디렉터리를 기준으로 디렉터리 경로를 지정
<code>mkdir</code> 명령으로 사용자 홈 디렉터리 아래에 BasketballTeam/NBA 를 생성 후 상대 경로로 이동해보자.
<img src="https://velog.velcdn.com/images/pride-marimo/post/141bab09-13dd-4fcd-bbd8-d22b869855d4/image.png" alt="">
상대 경로로 최하위 디렉터리 NBA까지 이동하려면 하위 디렉터리 경로만 입력하면 된다.</li>
</ul>
<hr>
<ul>
<li><strong>절대 경로</strong> : 최상위 디렉터리인 루트 디렉터리를 기준으로 디렉터리 경로를 지정
다시 홈으로 돌아와 절대 경로로 이동해보자. 절대 경로로 이동 시 모든 경로를 입력해야 한다는 불편함이 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/12eb9d21-086d-4410-abb4-4b530a8e10e4/image.png" alt="">
하지만 다음처럼 BasketballTeam/NBA에서 BaseballTeam/KBO로 이동하는것과 같은 상황에서는 절대경로로 이동하는 편이 낫다. 상대 경로로 이동 시 반드시 상위 경로인 사용자 홈 디렉터리를 거쳐야 하기 때문이다.<br>
* 상대경로로 이동한 경우
![](https://velog.velcdn.com/images/pride-marimo/post/370b9965-7abe-4e23-b65b-ca8a932f9074/image.png)
* 절대경로로 이동한 경우
![](https://velog.velcdn.com/images/pride-marimo/post/7c332679-25f8-414c-95ad-734f3603d113/image.png)



</li>
</ul>
<h3 id="rmdir--디렉터리-삭제"><code>rmdir</code> : 디렉터리 삭제</h3>
<p>앞서 만들어둔 빈 디렉터리를 삭제해보자. <code>rmdir</code>은 빈 디렉터리만 삭제하므로, 디렉터리에 파일이 있다면 <code>rm</code> 명령을 사용해야 한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b8844d3c-da11-4769-a60f-824bce332277/image.png" alt=""></p>
<br>

<h2 id="파일-다루기">파일 다루기</h2>
<h3 id="cp-옵션-원본파일-사본파일--파일-복사"><code>cp [옵션] [원본파일] [사본파일]</code> : 파일 복사</h3>
<p>테스트를 위해 현재 디렉터리에 <code>touch</code> 명령으로 teamdata라는 빈 파일을 만들어 해당 파일을 복사해보자.
<img src="https://velog.velcdn.com/images/pride-marimo/post/11df4ec4-2cf9-4f76-9007-b7f83e2ca7a6/image.png" alt="">
원본(teamdata)와 사본(lions)의 권한, 소유권, 변경 시각이 동일하다.</p>
<p>원본 또는 사본의 경로를 구체적 지정도 가능하다.
아래 명령은 teamdata의 파일을 BaseballTeam/KBO에 twins라는 파일명으로 복사한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/5d82f8c5-13f7-4303-a691-2efd1b0291d5/image.png" alt=""></p>
<h4 id="옵션-활용">옵션 활용</h4>
<ul>
<li><code>-r(--recursive)</code> : 하위 디렉터리에 존재하는 디렉터리와 파일 모두를 복사</li>
<li><code>-a(--archive)</code> : 파일 복사 과정에서 접근 권한, 소유자, 그룹, 파일 수정시간 정보를 그대로 보존</li>
</ul>
<p>아래 명령은 KBO 디렉터리를 MLB 디렉터리로 복사하며 하위 디렉터리의 접근권한, 소유권, 변경시각을 그대로 유지한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/8c34cbcd-eef8-4be6-aec6-d5576327d413/image.png" alt=""></p>
<h3 id="mv-옵션-원본파일-사본파일--파일-이동"><code>mv [옵션] [원본파일] [사본파일]</code> : 파일 이동</h3>
<p><code>mv</code> 명령어는 <code>cp</code> 명령어와 다르게 원본 파일이 삭제되므로 주의해야한다.
아까 생성한 lions 파일을 BaseballTeam/KBO로 옮겨보면 기존 경로에서는 삭제되었고 지정한 경로에 파일이 생성됨을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/9f5855cf-8936-4e26-a11e-ed76b96668bc/image.png" alt=""></p>
<p>원본 파일을 여러개 옮기는 것도 가능하다. KBO 디렉터리에서 MLB 디렉터리로 두 파일을 한 번에 이동시켜보자.
<img src="https://velog.velcdn.com/images/pride-marimo/post/2f951b4c-615d-410f-b826-e9a7c0a223de/image.png" alt=""></p>
<p><code>mv</code> 명령어로 디렉터리도 옮길 수 있다.
MLB 디렉터리를 MajorLeagueBaseball로 옮겨보자.
<img src="https://velog.velcdn.com/images/pride-marimo/post/847417d2-5bc9-4dc4-aaea-fc9618ad9e6a/image.png" alt=""></p>
<h3 id="rm-옵션-파일명--파일-삭제"><code>rm [옵션] [파일명]</code> : 파일 삭제</h3>
<p><code>touch</code> 명령어로 빈 파일을 여러개 생성할 수 도 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/fd6c15e6-bf37-48d6-9eec-7f487edede2d/image.png" alt="">
<code>rm</code> 명령어로 파일을 삭제한다. 동시에 여러 파일을 삭제할 수도 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/c935ea20-f9b9-4ce0-835e-d48944af638c/image.png" alt=""></p>
<h4 id="옵션-활용-1">옵션 활용</h4>
<p><code>-rf</code> 옵션을 붙이면 하위 디렉터리를 포함한 모든 파일을 삭제할 수 있다.</p>
<ul>
<li><code>-r</code> : 재귀 옵션. 하위 디렉터리에 영향을 미친다.</li>
<li><code>-f</code> : 삭제하려는 파일이 있는지 확인하지 않고 강제로 삭제한다.</li>
</ul>
<p>이 때, 대상 파일을 삭제할 권한이 없다면 오류 메시지가 나타난다. <code>rm -rf</code> 명령을 실행하기 전에는 반드시 삭제 대상과 권한을 확인해야한다. 
<span style="background-color:#fff5b1">특히, 루트 디렉터리를 대상으로 삭제 명령을 실행한다면 시스템 파일들이 순식간에 제거되어 복구 불가능한 상태가 되므로, 삭제 대상 파일의 권한 때문에 <code>sudo</code> 명령으로 루트 권한을 얻어야 한다면 <strong><code>rm</code> 명령을 실행하기 전 다시 한 번 삭제 대상을 확인</strong>해야 한다.</span></p>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/f8063e02-dfeb-4a2b-a5df-026d1a31806f/image.png" alt=""></p>
<br>

<h2 id="파일-내용-확인하기">파일 내용 확인하기</h2>
<h3 id="cat--파일-내용-화면에-표시"><code>cat</code> : 파일 내용 화면에 표시</h3>
<p><code>cat</code> 명령은 파일의 내용을 조회한다. /usr/share/common-licenses/GPL 파일을 조회해보자. 이 파일은 리눅스 커널에 적용된 GPL 라이선스 정보이다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/97cdb523-5357-4450-8156-6dcaab159a01/image.png" alt=""></p>
<h4 id="옵션-활용-2">옵션 활용</h4>
<ul>
<li><p><code>-n(--number)</code> : 줄번호 추가
<img src="https://velog.velcdn.com/images/pride-marimo/post/d14d3149-9090-4046-8791-6448fe146b2d/image.png" alt=""></p>
</li>
<li><p><code>&gt;</code> : 출력 재지정. 출력 결과를 화면 대신 파일로 보내 새로운 파일을 생성할 수 있다. 
<code>cat &gt; [파일명]</code> 을 실행하면 빈 줄이 나오고 여기에 파일 내용을 입력한 후 <code>ctrl + d</code>를 눌러 종료하면 다시 명령행으로 나간다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/03fbf11b-2a3f-4151-b1ca-12beaede9aef/image.png" alt="">
내용을 다시 조회해보면 위에서 입력한 내용이 정상 저장됨을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/96742be9-1b4f-411e-bbf0-6417a6ddf824/image.png" alt=""></p>
<blockquote>
<h4 id="입출력-재지정">입출력 재지정</h4>
<p>리눅스의 표준 입력 장치는 키보드, 표준 출력 장치는 모니터 화면이다. <code>&gt;</code> 또는 <code>&lt;</code> 로 표준 입력 및 출력을 다른 대상으로 바꿀 수 있다. 배시가 제공하는 입출력 재지정 기능이다.</p>
<br>
</blockquote>
<ul>
<li><strong>출력 재지정 <code>&gt;</code></strong>
출력 재지정 기호를 이용해 <code>ls</code> 명령의 결과를 표준 출력 장치인 모니터가 아닌 homelist 파일로 출력한다. homelist 파일에는 <code>ls</code> 명령 결과인 홈 디렉터리의 파일 목록이 저장되어 있다. 이 때, 해당 파일이 존재하지 않는다면 자동 생성하여 표기한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/9b063ea8-4746-40a5-b86b-0935a7167d76/image.png" alt=""><br></li>
<li><strong>입력 재지정 <code>&lt;</code></strong>
입력 재지정 기호를 이용하면 표준 입력 장치인 키보드 입력 대신 파일에서 입력받아 처리할 수 있다. 
<code>sort</code> 는 결과를 순서대로 정렬하는 명령인데, <code>-r</code> 옵션을 활용하면 결과를 역으로 정렬할 수 있다. 
표준 입력을 재지정하여 앞서 만든 homelist 파일의 내용을 <code>sort</code> 명령으로 넘긴다. 명령 결과 파일 목록을 알파벳 역순으로 정렬하여 화면에 표시한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/688eeb63-7b69-4642-9236-d0104c73fa2b/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="more-less--한-화면씩-스크롤하여-파일-내용-조회"><code>more</code>, <code>less</code> : 한 화면씩 스크롤하여 파일 내용 조회</h3>
<p><code>cat</code> 은 파일 내용을 끝까지 한 번에 다 보여주기 때문에 화면에 표시할 내용이 많다면 모두 확인하기는 어렵다. 분량이 많은 파일은 <code>more</code>나 <code>less</code>로 한 화면씩 스크롤 하는 편이 낫다.</p>
<h4 id="화면-전환-키">화면 전환 키</h4>
<table>
<thead>
<tr>
<th>키</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td>space 바, f</td>
<td>다음 화면</td>
</tr>
<tr>
<td>b</td>
<td>이전 화면</td>
</tr>
<tr>
<td>enter</td>
<td>한 줄씩 표기</td>
</tr>
<tr>
<td>q</td>
<td>화면을 종료하고 명령행으로 복귀</td>
</tr>
</tbody></table>
<p>아까 열었던 GPL 파일을 <code>more</code> 명령으로 호출해보자. 화면 아래에 표시되는 정보로 스크롤의 위치를 가늠할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/7b343e62-3a5d-475a-8d23-e621044de902/image.png" alt=""></p>
<p><code>less</code>를 활용해도 동일한 키로 파일 내용을 조회할 수 있다. <code>less</code> 명령어는 <code>more</code> 명령어보다 더 많은 키를 활용할 수 있다.</p>
<table>
<thead>
<tr>
<th>키</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td>g</td>
<td>첫 화면으로 이동</td>
</tr>
<tr>
<td>G (shift + g)</td>
<td>마지막 화면으로 이동</td>
</tr>
<tr>
<td>/</td>
<td>문자열 검색 <span style="color:gray">ex) /GNU</span><br> 명령어 실행 시 해당 단어가 하이라이트 된다. <br>이 때, <code>n</code>과 <code>N(shift+n)</code>을 눌러 검색된 문자열을 건너뛰며 이동할 수 있다.</td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/pride-marimo/post/98691cae-54a3-44c6-8ac5-7cb48d6c0c23/image.png" alt=""></td>
<td></td>
</tr>
</tbody></table>
<h3 id="head-tail--파일의-처음head-또는-미지막tail을-기준으로-파일-내용-출력"><code>head</code>, <code>tail</code> : 파일의 처음(head) 또는 미지막(tail)을 기준으로 파일 내용 출력</h3>
<p>/var/log/syslog는 시스템이 남기는 로그 파일로 운영 과정에서 발생하는 이벤트 기록을 저장한다. 일반적으로 로그 파일은 용량이 크고 내용이 자주 바뀌므로 일부만 모니터링하는 것이 유용하다.</p>
<h4 id="head--파일의-처음을-기준으로-파일-출력"><code>head</code> : 파일의 처음을 기준으로 파일 출력</h4>
<p>특별히 옵션을 사용하지 않으면 파일의 첫번째 줄 부터 열번째 줄 까지 출력한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/2127e41b-0717-465e-88a3-515e01867430/image.png" alt="">
특정 줄까지 출력하고 싶다면 <code>-n(--lines)</code> 옵션을 사용한다. 다음 명령어는 처음부터 15번째 줄까지 출력한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/dd3207ab-b60d-4640-aef6-09d4ae20ef55/image.png" alt=""></p>
<h4 id="tail--파일의-마지막을-기준으로-파일-출력"><code>tail</code> : 파일의 마지막을 기준으로 파일 출력</h4>
<p><code>head</code>와 마찬가지로 옵션을 지정하지 않으면 파일의 마지막 줄 부터 열번째 줄 까지 출력하며, <code>-n</code> 옵션을 추가해 보고싶은 줄의 범위를 지정할 수 있다. 
<img src="https://velog.velcdn.com/images/pride-marimo/post/23d68e5e-057c-41b6-96fe-da95bf7735bf/image.png" alt=""></p>
<p><code>-f(--follow)</code> 옵션은 실시간으로 파일을 모니터링 한다. 명령을 실행하면 명령 프롬프트를 반환하지 않고 지속적으로 시스템이 변하는 상황을 보여주며, ctrl+c를 누르면 명령행으로 되돌아온다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/d64826a6-4353-4b30-9fc2-3f9bd3a848ef/image.png" alt=""></p>
<br>

<h2 id="파일-검색하기">파일 검색하기</h2>
<h3 id="find-탐색경로-옵션-표현식--파일-찾기"><code>find [탐색경로] [옵션] [표현식]</code> : 파일 찾기</h3>
<p>탐색 경로를 생략하면 현재 작업 중인 디렉터리부터 검색을 시작한다.
이 때, 읽기 권한이 없는 디렉터리는 검색을 건너뛴다. <strong><code>sudo</code>로 루트 권한을 얻으면 모든 디렉터리를 대상으로 검색</strong>할 수 있는데, 루트 디렉터리부터 시작해서 모든 파일을 검색하려면 탐색 경로에 루트 디렉터리를 명시한다. 
다만, <strong>파일 시스템 전체의 파일을 검색하므로 시간이 오래걸린다.</strong></p>
<h4 id="옵션-목록">옵션 목록</h4>
<table>
<thead>
<tr>
<th>옵션</th>
<th>기능</th>
</tr>
</thead>
<tbody><tr>
<td><code>-name</code></td>
<td>파일 이름으로 검색</td>
</tr>
<tr>
<td><code>-perm</code></td>
<td>파일 권한 대상으로 검색</td>
</tr>
<tr>
<td><code>-type</code></td>
<td>파일 종류를 대상으로 검색</td>
</tr>
<tr>
<td><code>-size</code></td>
<td>파일 크기를 대상으로 검색</td>
</tr>
<tr>
<td><code>-links</code></td>
<td>링크 수를 댓아으로 검색</td>
</tr>
<tr>
<td><code>-user</code></td>
<td>사용자 ID를 대상으로 검색</td>
</tr>
<tr>
<td><code>-atime</code>, <code>-mtime</code>, <code>-ctime</code></td>
<td>특정 기간동안 접근, 수정, 변경된 파일을 검색<br><code>-atime</code> : 파일 접근 시간<br><code>-mtime</code> : 파일 내용 변경 시간<br><code>-ctime</code> : 파일 속성 변경 시간</td>
</tr>
<tr>
<td><code>-maxdepth</code></td>
<td>탐색할 디렉터리 깊이 제한</td>
</tr>
<tr>
<td><code>-i</code></td>
<td>대소문자 구분 없이 검색</td>
</tr>
</tbody></table>
<h4 id="옵션-활용-예제">옵션 활용 예제</h4>
<ul>
<li><p><strong><code>-name</code></strong> : 파일 명으로 검색
<img src="https://velog.velcdn.com/images/pride-marimo/post/4cceaa66-4d68-4644-8108-384859c9003a/image.png" alt=""></p>
</li>
<li><p><strong><code>-type</code></strong> : 파일 종류를 지정하여 검색
다음 명령어에서 <code>d</code>는 디렉터리를 의미한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/e788a9eb-103e-4dfe-aafa-a0abe4b45e8d/image.png" alt=""></p>
</li>
<li><p><strong><code>-mtime</code></strong> : 특정 기간 동안 내용이 변경된 파일 검색
인자로 변경된 기간을 받는데, <code>-</code>는 미만, <code>+</code>는 초과를 의미한다.
예를 들어, <code>-mtime +7</code>은 수정한지 일주일이 넘은 파일들을 대상으로 한다.
아래 명령어는 현재 디렉터리(<code>.</code>)를 기준으로 수정한지 하루가 되지 않은(<code>-mtime -1</code>) 파일들을 검색한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/53c62e80-f192-401f-84a3-924dab2e4611/image.png" alt=""></p>
</li>
<li><p><strong>검색 옵션 중복 사용</strong></p>
<ul>
<li><p>상위 디렉터리(<code>..</code>)부터 100MB가 넘는(<code>-size +100M</code>) 일반 파일(<code>-type f</code>)을 검색
<img src="https://velog.velcdn.com/images/pride-marimo/post/663ba8f8-6825-4552-8db3-75bb46c9881e/image.png" alt=""></p>
</li>
<li><p>루트 디렉터리(<code>/</code>)부터 시작해서 두 단계의 하위 디렉터리(<code>-maxdepth 2</code>)까지 bin이란 이름을 가진(<code>-name bin</code>) 파일 또는 디렉터리를 검색
검색 결과 /bin, /usr/bin은 포함하지만 /usr/local/bin은 제외함을 볼 수 있다. 
<img src="https://velog.velcdn.com/images/pride-marimo/post/e48a3c83-c6c9-419e-b4cc-2c5bc38ad285/image.png" alt=""></p>
</li>
<li><p>/etc 디렉터리에서 대소문자 구분 없이(<code>-i</code>) conf로 끝나는(<code>-name *conf</code>) 파일(<code>-type f</code>)을 검색
<img src="https://velog.velcdn.com/images/pride-marimo/post/3c08f969-fa76-480c-a4f6-20d3a7bd1f7a/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<blockquote>
<h4 id="파일-명을-대체하는-메타문자">파일 명을 대체하는 메타문자</h4>
</blockquote>
<ul>
<li><strong><code>*</code> : 모든 문자 대체. <code>find -name a*b</code> 형태로 사용</strong>
<span class="color:gray;"> <em>ex) a*b : a로 시작하여 b로 끝나는 모든 이름. aannbb, anb 등 모두 조회</em> </span><br></li>
<li><strong><code>?</code> : 정확히 1개의 문자만 대체. <code>find -name a?b</code> 형태로 사용</strong>
<span class="color:gray;"> <em>ex) a?b : a로 시작하고 중간에 임의의 문자 하나가 있으며 b로 끝나는 이름. anb 형태만 조회, aannbb는 조회 불가</em> </span><br></li>
<li><strong><code>[문자]</code> : 괄호 안에 있는 문자를 대체. <code>find -name [A,B,C]_number</code> 형태로 사용</strong>
<span class="color:gray;"> <em>ex) [A,B,C]_number : A나 B또는 C로 시작하고 _number로 끝나는 파일 이름. A_number, B_number, C_number가 검색됨</em> </span>
<br><span style="background-color:#fff5b1">이 케이스는 표준식처럼 <strong><code>-</code>를 이용해 대체할 영문자나 숫자의 범위를 지정</strong>할 수 있다.</span> 예를 들어, _number[0-9]_는 number로 시작해서 0부터 9까지 숫자가 붙는 문자인 number0, number1, ... number9에 대응한다.
같은 원리로 _[A-C]number_는 [A,B,C]number와 동일한 검색 결과를 도출한다.
<br>다만 이 경우, <span style="background-color:#fff5b1">범위를 지정할 때 <strong>a-Z처럼 대/소문자를 섞어쓰거나 숫자와 영문자를 섞어 범위를 지정할 수 없다.</strong> [] 안에는 동일한 성질의 문자 범위만 지정할 수 있다.</span></li>
</ul>
<h3 id="echo--path-내용-확인"><code>echo</code> : PATH 내용 확인</h3>
<p>명령 행에서 명령을 입력하면 셸은 환경변수 PATH에 정의된 디렉터리들을 검색하여 실행한다. 각 디렉터리는 콜론(:)으로 구분하며, 왼쪽에서 오른쪽으로 검색한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/aa66110c-fcd9-4244-9c97-7e2b58674933/image.png" alt=""></p>
<h3 id="which-파일명--명령의-절대-경로-호출"><code>which [파일명]</code> : 명령의 절대 경로 호출</h3>
<p><code>which</code> 명령어는 PATH 변수의 디렉터리를 검색해 명령의 절대 경로를 찾아 보여준다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/5be6c04c-34ff-4a6c-bb24-0b6c55ab8384/image.png" alt=""></p>
<h3 id="whereis--명령과-해당-명령의-소스파일-매뉴얼-페이지-경로-검색"><code>whereis</code> : 명령과 해당 명령의 소스파일, 매뉴얼 페이지 경로 검색</h3>
<p><code>whereis</code> 명령어는 설정 파일을 찾을 때 유용하다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/261cfe04-d9af-43ee-8686-f392b5990353/image.png" alt=""></p>
<h3 id="grep-옵션-표현식-파일명--파일-내용-검색"><code>grep [옵션] [표현식] [파일명]</code> : 파일 내용 검색</h3>
<p>아무 옵션 없이 명령을 실행하면 지정한 파일에서 검색된 문자열을 포함하는 행을 보여준다. 터미널에서는 검색어가 붉게 표시되어 보인다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/fdae9382-bd0c-4537-9e6d-4649bf20cd69/image.png" alt=""></p>
<p>파일 명 대신 <code>*</code>를 쓰면 현재 디렉터리에 있는 파일들을 모두 검색한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/f1b268b2-aa10-4bf4-b5d0-cf38337af07c/image.png" alt=""></p>
<h4 id="옵션-활용-3">옵션 활용</h4>
<ul>
<li><p><code>-r(--recursive)</code> : 현재 디렉터리부터 하위 디렉터리를 포함한 모든 파일에서 문자열 검색
<img src="https://velog.velcdn.com/images/pride-marimo/post/4ce2e841-8c52-456c-b175-4de45dbd1de4/image.png" alt=""></p>
</li>
<li><p><code>-c(--count)</code> : 해당 문자열이 포함된 행 수 출력
<img src="https://velog.velcdn.com/images/pride-marimo/post/408f76c4-9b8a-4012-8941-43b450af124f/image.png" alt=""></p>
</li>
<li><p><code>-n(--line-number)</code> : 해당 문자열이 포함된 행과 행 번호를 함께 출력
<img src="https://velog.velcdn.com/images/pride-marimo/post/813e1a03-f56d-4da8-bd65-f2b2c9105e44/image.png" alt=""></p>
</li>
<li><p><code>-i(--ignore-case)</code> : 대,소문자 구분 없이 문자 검색
<img src="https://velog.velcdn.com/images/pride-marimo/post/22b01e8b-0f62-417b-ba3e-e625cf346476/image.png" alt=""></p>
</li>
</ul>
<blockquote>
<h4 id="파이프를-활용한-검색">파이프(<code>|</code>)를 활용한 검색</h4>
<p>파이프는 여러 명령을 동시에 사용하는 도구로, 프로세스 사이에 정보를 전달하는 통로 역할을 수행한다.
<span style="background-color:#fff5b1"><strong><code>grep</code> 명령</strong>은 단독으로 사용하기보다 <strong>파이프로 다른 명령과 조합</strong>하여 결과를 얻는데 유용하게 쓴다.</span>
<br>예를 들어, 파일 목록을 출력하는 <code>ls -l</code> 결과에 <code>grep</code>을 사용하면 파일 목록에서 data라는 문자열을 포함한 결과만 표시한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/0d559c0c-c08e-4a5a-ac83-c21d2da66920/image.png" alt="">
파이프는 일괄적으로 출력을 입력으로 보내 처리하므로, <code>명령A|명령B</code>로 입력하면 명령A의 출력을 명령B의 입력으로 보내버린다.
<br>홈 디렉터리에서 <code>ls -l</code> 명령을 실행하면 숨김파일까지 모두 표시하므로 출력 결과가 한 화면을 넘는 결과가 일반적이다. 이 때, <code>more</code>나 <code>less</code>와 조합하여 사용하면 훨씬 조회가 수월하다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/559c3f71-f285-4580-89b1-6e8eda77b2b9/image.png" alt="">
<code>명령A|명령B|명령C</code> 형태로도 사용이 가능한데, 명령 A의 출력을 명령 B의 입력으로, 다시 명령 B의 출력을 명령 C의 입력으로 전달한다.
<br>다음과 같은 형태로 활용할 수 있다.<br>- 파일 목록을 조회(<code>-ls -l</code>)하여 역순으로 정렬(<code>sort -r</code>)하고 화면 단위로 결과를 출력(<code>more</code>)
<img src="blob:https://velog.io/0d0afc71-6a7f-4910-9314-85f3075da857" alt="업로드중.."></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 명령행 인터페이스(4) - 파일 관리하기(1)]]></title>
            <link>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A44-%ED%8C%8C%EC%9D%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B01</link>
            <guid>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A44-%ED%8C%8C%EC%9D%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B01</guid>
            <pubDate>Sat, 20 Dec 2025 09:55:12 GMT</pubDate>
            <description><![CDATA[<p>파일 관리 명령은 빈번히 사용되므로 충분한 연습이 필요하다. 파일 관리 명령을 통해 복잡한 작업도 빠르고 효율적으로 처리할 수 있다.</p>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ls</td>
<td>파일 목록 조회</td>
</tr>
<tr>
<td>chown</td>
<td>파일 소유권 변경</td>
</tr>
<tr>
<td>chmod</td>
<td>파일 접근 권한 변경</td>
</tr>
<tr>
<td>pwd</td>
<td>현재 작업 중인 디렉터리 위치 표시</td>
</tr>
<tr>
<td>cd</td>
<td>작업 디렉터리 위치 변경</td>
</tr>
<tr>
<td>mkdir, rmdir</td>
<td>디렉터리 생성/삭제</td>
</tr>
<tr>
<td>touch</td>
<td>빈 파일 생성</td>
</tr>
<tr>
<td>cp, mv, rm</td>
<td>파일 복사, 이동, 삭제</td>
</tr>
<tr>
<td>cat</td>
<td>파일 내용 화면에 표시</td>
</tr>
<tr>
<td>more, less</td>
<td>파일 내용 스크롤</td>
</tr>
<tr>
<td>head, tail</td>
<td>파일 내용 중 일부를 표시</td>
</tr>
<tr>
<td>find, which, whereis, grep</td>
<td>파일 검색</td>
</tr>
</tbody></table>
<h2 id="리눅스에서-파일이란">리눅스에서 파일이란?</h2>
<p>리눅스에서는 <strong>모든 것이 파일</strong>이다. 텍스트, 이미지, 영상 뿐 아니라 파일을 묶는 디렉터리, 네트워크 소켓 등 자료 흐름과 시스템 장치까지 파일로 처리한다.
<span style="background-color:#fff5b1">리눅스에서는 <strong>파일의 대·소문자를 엄격하게 구분하여 서로 다른 파일로 간주</strong>한다.</span> 파일 이름은 최대 256자까지  가능하며, 공백을 포함한 특수문자를 사용할 수 는 있으나 되도록 사용하지 않는 편이 좋다.
<br></p>
<h2 id="ls--파일-목록-화면에-표시하기"><code>ls</code> : 파일 목록 화면에 표시하기</h2>
<p><code>ls [옵션] [파일]</code> : 파일 목록을 화면에 표시한다. 아무 옵션 없이 명령을 실행하면 파일과 디렉터리 명만 출력한다. </p>
<ul>
<li>아무 옵션, 인자 없이 <code>ls</code>만 호출 : 현재 작업중인 디렉터리 표시
<img src="https://velog.velcdn.com/images/pride-marimo/post/2cb1e6a7-092a-48fa-9fc2-fb60750d8bee/image.png" alt=""></li>
<li>인자로 디렉터리 경로 지정하는 경우 : 해당 디렉터리의 하위 디렉터리와 파일 목록 표시
<img src="https://velog.velcdn.com/images/pride-marimo/post/686e12d6-afe5-44e2-b667-491aff584655/image.png" alt=""></li>
</ul>
<p>그 외 자주 사용되는 옵션 별 기능과 예시 화면이다.</p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>-l</code></td>
<td>파일 세부 정보 표시</td>
<td><img src="https://velog.velcdn.com/images/pride-marimo/post/baf6a5b0-aace-4ddf-b2f6-d7b1d2ae6610/image.png" alt=""></td>
</tr>
<tr>
<td><code>-lh</code></td>
<td><code>-h</code> : 파일 용량 단위를 MB, GB로 변환하여 표기. <code>-l</code> 옵션과 붙여 <code>-lh</code> 형식으로 사용 가능</td>
<td><img src="https://velog.velcdn.com/images/pride-marimo/post/0bb258ba-913b-47da-9b2d-7ccabee92ca4/image.png" alt=""></td>
</tr>
<tr>
<td><code>-a</code></td>
<td>숨은 파일 까지 표기. 모든 디렉터리에는 현재 디렉터리를 의미하는 <code>.</code>와 상위 디렉터리를 의미하는 <code>..</code>이 포함되어있다. 이 외에 <code>.</code>으로 시작하는 파일들은 모두 숨긴 파일임을 의미한다.</td>
<td><img src="https://velog.velcdn.com/images/pride-marimo/post/cc48b1e7-3bff-4da9-9c63-d469b23f89bb/image.png" alt=""></td>
</tr>
<tr>
<td><code>-al</code></td>
<td>현재 디렉터리에 있는 모든 파일의 세부 정보 표기. <code>-a</code>와 <code>-l</code> 옵션을 붙여 사용하는 형태</td>
<td><img src="https://velog.velcdn.com/images/pride-marimo/post/7f3e8459-6e12-4964-9e81-e0bcdee5d4f8/image.png" alt=""></td>
</tr>
</tbody></table>
<p>파일 정보를 조회할 때 다음과 같이 표시된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/747f092b-6610-451a-a37d-a30c32dd2f81/image.png" alt=""></p>
<p>이 중 접근권한은 다음과 같은 기준으로 표기된다.</p>
<ul>
<li>첫번째 자리: 디렉토리(d), 일반파일(-)</li>
<li>2~4번째 자리 : 소유자에 대한 읽기(r), 쓰기(w), 실행(x) 권한 유무</li>
<li>5~7번째 자리 : 소유그룹에 대한 읽기(r), 쓰기(w), 실행(x) 권한 유무</li>
<li>8~10번째 자리 : 다른 모든 사용자에 대한 읽기(r), 쓰기(w), 실행(x) 권한 유무</li>
</ul>
<p><span style="background-color:#fff5b1">첫번째 자리를 제외한 각 권한은 <strong>r, w, x 순으로 표기</strong>되며, 해당 <strong>권한이 없을 경우 -로 표기</strong>된다.</span>
위 이미지에서의 권한 부여 상태를 분석하면, 현재 조회된 음악 파일은 <strong>디렉토리</strong> 이며, <strong>소유자는 읽기, 쓰기, 실행</strong>을 모두 할 수 있고, <strong>소유그룹과 다른 모든 사용자는 읽기, 실행</strong>을 할 수 있다.</p>
<br>

<h2 id="chown-chmod--파일-소유권-및-접근-권한-설정"><code>chown, chmod</code> : 파일 소유권 및 접근 권한 설정</h2>
<h3 id="touch--파일의-날짜와-시간-정보를-건드려-바꾸기"><code>touch</code> : 파일의 날짜와 시간 정보를 &#39;건드려 바꾸기&#39;</h3>
<p><code>touch [옵션] [파일명]</code> 형태로 사용하며, 파일의 날짜/시간정보를 바꿀 수 있다. 아무 옵션 없이 새로운 파일 이름을 지정하면 비어있는 파일을 생성할 수 있다.</p>
<h3 id="chown--파일-소유권-설정"><code>chown</code> : 파일 소유권 설정</h3>
<p>기본적으로 파일 소유권은 파일을 생성한 사용자와 사용자가 속해있는 그룹에 있다. 파일의 소유권을 변경하려면 반드시 시스템관리자 권한으로 실행해야하므로 <code>sudo</code> 명령을 붙여야 한다.
<code>touch</code> 로 빈 파일을 하나 생성하여 정보를 조회하면 해당 파일의 권한을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/30ebe69b-71d3-4141-afb5-16769da2980a/image.png" alt="">
권한이 <code>rs-rs-r--</code> 로 소유자와 그룹에 속한 사용자가 읽고 쓸 수 있으며, 그 외 사용자는 읽기만 가능하도록 설정되어있음을 확인할 수 있다.</p>
<h4 id="1-touch-명령-시-sudo--u-사용자명-명령을-앞에-덧붙여-파일-생성부터-소유권-설정하기">1) <code>touch</code> 명령 시 <code>sudo -u [사용자명]</code> 명령을 앞에 덧붙여 파일 생성부터 소유권 설정하기</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/063e9e7a-ff7b-437e-a9ac-c46953e66948/image.png" alt="">
위처럼 <code>/tmp</code> 경로에 임시 파일을 생성할 때 <code>tester</code>라는 계정에 소유권을 부여한 후 <code>ls -l</code> 명령어로 해당 파일의 상세정보를 조회해 보면 소유권이 명령을 실행한 시스템 관리자인 user가 아닌 tester로 잡혀있음을 확인할 수 있다.</p>
<h4 id="2-chown-옵션-사용자-계정-및-그룹-파일으로-이미-생성된-파일의-소유권-변경하기">2) <code>chown [옵션] [사용자 계정 및 그룹] [파일]</code>으로 이미 생성된 파일의 소유권 변경하기</h4>
<ul>
<li><strong>파일 소유자만 변경하기</strong>
<img src="https://velog.velcdn.com/images/pride-marimo/post/314a5fce-30c5-4c6e-a1bc-33837d4f9413/image.png" alt=""></li>
<li><strong>파일 소유자, 그룹 정보 동시 변경하기</strong>
사용자 계정과 그룹 사이에 <code>.</code> 또는 <code>:</code>를 넣어 소유자와 그룹을 동시에 변경할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/c24fb1e8-bdea-40dd-bfca-38c762d28c6b/image.png" alt=""></li>
<li><strong>디렉토리 소유권 변경하기</strong>
실습을 위해 /tmp 디렉토리에 <span style="background-color:#fff5b1">/inventory라는 디렉토리를 생성(<code>mkdir</code>)</span>여 빈 파일 <span style="background-color:#fff5b1">phone과 picture를 생성(<code>touch</code>)</span>하였다. 이 때 권한은 tester로 설정하였으며, <code>ls -l</code> 명령어로 조회 시 tester 계정으로 권한이 잘 설정되어있음을 확인할 수 있다.  <img src="https://velog.velcdn.com/images/pride-marimo/post/110169c6-08a1-4011-8a97-b151af4f00d8/image.png" alt=""><img src="https://velog.velcdn.com/images/pride-marimo/post/44aa6b06-a473-4909-a5a0-59fa3cbc839f/image.png" alt=""><img src="https://velog.velcdn.com/images/pride-marimo/post/0083f4ff-6e54-4c5a-a3df-65bdea027782/image.png" alt=""><ul>
<li><strong><code>chown</code> 으로 디렉토리만 소유권 변경</strong>
단순히 <code>chown</code>만 사용하면 디렉토리의 소유권만 변경되고, 하위 파일들의 소유권은 기존과 동일하게 tester로 설정되어있음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/d9a3a4b4-fd81-463d-8b9c-ca947c546ae2/image.png" alt=""><img src="https://velog.velcdn.com/images/pride-marimo/post/9903b58c-5807-4d5f-892c-e58d6d4b8ee8/image.png" alt=""></li>
<li><strong><code>chown -R</code> 으로 해당 디렉토리 + 하위 디렉토리 및 파일까지 모두 소유권 변경</strong>
재귀를 뜻하는 <code>-R</code> 옵션을 추가하면 해당 디렉토리와 디렉토리 하위에 있는 파일 및 디렉토리들의 소유권까지 모두 한번에 변경할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/2fafd707-72b3-407c-8693-9a67a4aee9a9/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="chmod--파일의-접근권한-변경하기"><code>chmod</code> : 파일의 접근권한 변경하기</h3>
<p><code>chown</code>과 달리 파일 소유자도 명령을 내릴 수 있다.</p>
<h4 id="1-chmod-레퍼런스연산자접근권한-파일-형식">1) <code>chmod [레퍼런스][연산자][접근권한] [파일]</code> 형식</h4>
<ul>
<li><strong>레퍼런스</strong> : 변경할 대상 소유자<em>(user)</em>, 그룹<em>(group)</em>, 다른 모든 사용자_(other)_를 뜻하며 차례로 <code>u</code>,<code>g</code>,<code>o</code>로 표시한다. <code>a</code>는 소유자와 그룹, 다른 사용자 모두를 의미한다. </li>
<li><strong>연산자</strong> : 권한 부여 시 <code>+</code>, 권한 해제 시 <code>-</code>를 사용한다.</li>
<li><strong>접근권한</strong> : 읽기<em>(read)</em>, 쓰기<em>(write)</em>, 실행_(execute)_을 뜻하며 각각 <code>r</code>,<code>w</code>,<code>x</code>로 표기한다.</li>
</ul>
<p>예제로 아까 생성한 /tmp/tempfile 의 권한을 모든 사용자와 그룹에게 rwx권한을 전부 부여한다. 이 결과로 시스템에 존재하는 모든 사용자가 이 파일을 읽고 쓰고 실행할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/2191f646-3f75-4c97-8d85-298e8c9d90f5/image.png" alt=""></p>
<p>반대로 다음처럼 <code>o-rwx</code>를 사용하여 다른 사용자들에게서만 권한을 해제할 수도 있다. 
<img src="https://velog.velcdn.com/images/pride-marimo/post/a2d0ffba-0914-4a5e-941c-f8a269ae765f/image.png" alt="">
다른 사용자들은 파일을 읽거나 쓰거나 실행할 수 없는 상태(rwxrwx---)로 변경됨을 확인할 수 있다.</p>
<h4 id="2-chmod-8진수-파일-형식">2) <code>chmod [8진수] [파일]</code> 형식</h4>
<p><code>[레퍼런스][연산자][접근권한]</code> 대신 8진수의 숫자 표현을 사용할 수 있다. 익숙해지면 이 방식이 훨씬 편리하다.
파일의 소유자, 그룹 사용자, 기타 사용자의 읽기, 쓰기, 실행 권한은 다음과 같이 8진수 값이 부여되어있다.</p>
<table>
<thead>
<tr>
<th>8진수 값</th>
<th>변경 대상</th>
<th>접근 권한</th>
</tr>
</thead>
<tbody><tr>
<td>400</td>
<td>파일 소유자</td>
<td>읽기</td>
</tr>
<tr>
<td>200</td>
<td>파일 소유자</td>
<td>쓰기</td>
</tr>
<tr>
<td>100</td>
<td>파일 소유자</td>
<td>실행</td>
</tr>
<tr>
<td>40</td>
<td>소유자가 속한 그룹</td>
<td>읽기</td>
</tr>
<tr>
<td>20</td>
<td>소유자가 속한 그룹</td>
<td>쓰기</td>
</tr>
<tr>
<td>10</td>
<td>소유자가 속한 그룹</td>
<td>실행</td>
</tr>
<tr>
<td>4</td>
<td>다른 사용자</td>
<td>읽기</td>
</tr>
<tr>
<td>2</td>
<td>다른 사용자</td>
<td>쓰기</td>
</tr>
<tr>
<td>1</td>
<td>다른 사용자</td>
<td>실행</td>
</tr>
</tbody></table>
<p><span style="background-color:#fff5b1"><strong>부여 할 접근 권한에 따라 각 값을 더한다.</strong></span></p>
<p>모든 사용자에게 읽기 권한만 부여하고 싶다면 접근 권한을 r--r--r-- 로 변경해야 한다. 
이 때, <span style="background-color:#fff5b1">파일 소유자 읽기권한 <strong>400 + *<em>그룹 읽기 권한 *</em>40 + *<em>다른 사용자 읽기 권한 *</em>4</strong>를 더하여 <strong>444</strong>로 표현할 수 있다.</span>
<img src="https://velog.velcdn.com/images/pride-marimo/post/346d09df-5503-4898-8730-dad11c904748/image.png" alt=""></p>
<p>접근 권한을 처음 처럼 되돌려보자. 파일 소유자 읽기(400), 쓰기(200)와 그룹 읽기(40), 쓰기(20)가 가능해야하고 다른 사용자는 읽기(4)만 가능해야 한다. 결과를 계산하면 400+200+40+20+4 = 664 이다. 
<img src="https://velog.velcdn.com/images/pride-marimo/post/f9c42783-c26d-43e2-a8a8-85b354a99bd3/image.png" alt=""></p>
<p>이 때, 파일의 소유자가 아니라면 <code>chmod</code> 명령을 실행할 수 없다. <code>chown</code>으로 파일 소유권을 변경한 후 명령을 실행해보면 권한이 없음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/7bba1239-1908-4777-9caa-7ec6718e4962/image.png" alt=""></p>
<blockquote>
<p>※ 권한번호 <code>777</code>은 모든 사용자에게 읽기, 쓰기, 실행 권한을 부여하는 번호이다.</p>
</blockquote>
<p>반대로 권한이 없을 때, <code>sudo</code> 명령을 사용하여 루트 권한을 획득하면 다른 사용자 소유의 파일 접근 권한을 변경할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/7d675165-454e-4342-973d-862d38fb39c3/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 명령행 인터페이스(3) - 프로세스 관리하기]]></title>
            <link>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A43-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A43-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 29 Nov 2025 07:02:16 GMT</pubDate>
            <description><![CDATA[<h3 id="프로세스-관리-명령">프로세스 관리 명령</h3>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>ps</td>
<td>프로세스 목록 조회</td>
</tr>
<tr>
<td>top</td>
<td>프로세스 상태, 시스템 자원 정보 실시간 조회</td>
</tr>
<tr>
<td>lsof</td>
<td>프로세스가 사용중인 파일 목록 조회</td>
</tr>
<tr>
<td>jobs</td>
<td>작업 목록 조회</td>
</tr>
<tr>
<td>bg, fg</td>
<td>프로세스를 백그라운드 또는 포그라운드에서 실행</td>
</tr>
<tr>
<td>kill</td>
<td>프로세스에 신호를 보내 상태 변경</td>
</tr>
<tr>
<td>사용자가 저장 장치에 있는 프로그램을 실행하면 프로그램이 프로세스 상태로 메모리에 적재된다. <span style="background-color:#fff5b1"><strong>프로세스는 계층적으로 구성</strong>되며, 프로세스마다 자기 자신을 만들어 준 부모 프로세스가 존재한다.</span> 부모 프로세스로 생성된 자식 프로세스는 부모 프로세스의 속성을 상속받는다. 부팅 과정 중 리눅스 커널이 시작하는 <code>init</code>는 유일하게 부모 프로세스가 없는 1번, 시스템 초기화 프로세스이다.</td>
<td></td>
</tr>
<tr>
<td>* <strong>PID</strong> (Process ID) : 프로세스 식별번호. 프로세스가 시작할 때 할당받는다.</td>
<td></td>
</tr>
<tr>
<td>* <strong>UID</strong> (User ID) : 프로세스를 소유하는 사용자 계정을 식별하는 번호. 프로세스에 대한 사용자 권한을 알 수 있다.</td>
<td></td>
</tr>
<tr>
<td>* <strong>PPID</strong> (Parent PID) : 부모 프로세스의 ID</td>
<td></td>
</tr>
</tbody></table>
<h2 id="ps-top--프로세스-목록-조회"><code>ps</code>, <code>top</code> : 프로세스 목록 조회</h2>
<h4 id="ps-명령어-옵션-목록"><code>ps</code> 명령어 옵션 목록</h4>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>-a</code></td>
<td>다른 사용자의 프로세스 상태도 표시</td>
</tr>
<tr>
<td><code>-x</code></td>
<td>화면에 보이지 않는 프로세스까지 표시</td>
</tr>
<tr>
<td><code>-u</code></td>
<td>프로세스를 사용한 사용자와 실행시간 표시</td>
</tr>
<tr>
<td><code>-ax</code></td>
<td>현재 실행 중인 모든 프로세스 표시</td>
</tr>
<tr>
<td><code>-aux</code></td>
<td>프로세스의 시스템 자원 사용률 표시</td>
</tr>
<tr>
<td><code>-e</code></td>
<td>실행 중인 모든 프로세스를 대상으로 프로세스 목록 출력</td>
</tr>
<tr>
<td><code>-f</code></td>
<td>완전한 형식으로 프로세스 목록 출력</td>
</tr>
<tr>
<td><code>-ef</code></td>
<td>PID로 정렬되어 있는 프로세스 목록 출력</td>
</tr>
<tr>
<td><code>f</code></td>
<td>프로세스 사이 상속 관계를 시각적으로 표현. <code>-f</code> 옵션과 다르다.</td>
</tr>
<tr>
<td>* <code>ps</code> 명령만 입력(옵션 미사용) : 현재 로그인 한 사용자가 실행하는 프로세스만 표시</td>
<td></td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/pride-marimo/post/b0f4e547-63e8-49d0-9f2f-68d9c192d92c/image.png" alt=""></td>
<td></td>
</tr>
<tr>
<td>* <code>ps -ax</code> : 현재 실행중인 모든 프로세스 조회. 이 때, 프로세스 목록 중 대괄호로 둘러싸인 프로세스는 커널이 생성한 프로세스이다.</td>
<td></td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/pride-marimo/post/3787e894-3671-47ee-9bd1-9d9713b49fa7/image.png" alt=""></td>
<td></td>
</tr>
<tr>
<td>* <code>ps -aux</code> : 프로세스의 시스템 자원 사용률</td>
<td></td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/pride-marimo/post/bdf85e65-4458-4f6b-9e81-cfe26ea7d9bc/image.png" alt=""></td>
<td></td>
</tr>
</tbody></table>
<ul>
<li>user : 프로세스 소유자 이름</li>
<li>PID : 프로세스 식별 번호</li>
<li>%CPU : 프로세스가 CPU를 차지하는 비율</li>
<li>%MEM : 프로세스가 메모리를 점유하는 비율</li>
<li>VSZ <span style="color:gray;"><em>(Virtual Set siZe)</em></span> : 리눅스가 프로세스에 할당한 가상 메모리 크기(실제 프로세스가 할당한 가상 메모리를 모두 사용하지는 않음)</li>
<li>RSS <span style="color:gray;"><em>(Resident Set Size)</em></span> : 프로세스가 현재 사용하는 메모리 크기(프로세스 사이에 공유되는 메모리 정보는 제외)</li>
<li>STAT : 프로세스의 현재 상태</li>
<li>START : 프로세스가 시작된 시간</li>
<li>TIME : 프로세스의 총 사용 시간</li>
<li>COMMAND : 프로세스를 실행한 명령</li>
</ul>
<blockquote>
<h4 id="프로세스-상태-값">프로세스 상태 값</h4>
<table>
<thead>
<tr>
<th>표기</th>
<th>상태</th>
</tr>
</thead>
<tbody><tr>
<td><strong>R</strong> <span style="color:gray;"><em>(Run/Runnable)</em></span></td>
<td>CPU를 사용하는 상태 / 대기하는 상태</td>
</tr>
<tr>
<td><strong>D</strong> <span style="color:gray;"><em>(in Disk wait)</em></span></td>
<td>입출력이 완료될 때 까지 대기. 깨울 수 없는 수면 상태. 입출력 완료 시 다시 실행 대기(R)로 전환</td>
</tr>
<tr>
<td><strong>S</strong> <span style="color:gray;"><em>(Sleeping)</em></span></td>
<td>스스로 대기 중인 수면 상태. 시간이 경과하거나 특정 이벤트가 발생하면 다시 실행 대기(R)로 전환</td>
</tr>
<tr>
<td><strong>T</strong> <span style="color:gray;"><em>(sTopped)</em></span></td>
<td>외부 신호로 일시 정지된 상태. 또 다른 외부 신호를 수신하면 다시 실행 대기로 전환.</td>
</tr>
<tr>
<td><strong>Z</strong> <span style="color:gray;"><em>(Zombie)</em></span></td>
<td>실행 완료한 프로세스는 부모 프로세스에 종료 신호를 보내고, 부모 프로세스에서 종료 신호를 받아들이면 프로세스가 소멸된다. 이 때 부모 프로세스에 문제가 있어 신호를 수신하지 못하면 좀비 상태로 남아있게 된다.</td>
</tr>
</tbody></table>
</blockquote>
<ul>
<li><p><code>ps -ef</code> : PID로 정렬되어 있는 프로세스 목록 출력
<img src="https://velog.velcdn.com/images/pride-marimo/post/b26e1792-eea1-46f6-b5d5-c45a2d693e70/image.png" alt=""></p>
<ul>
<li>PPID : 부모 프로세스의 PID</li>
<li>C : 프로세스의 CPU 점유 상태</li>
<li>STIME : 프로세스가 시작된 시간</li>
<li>TTY : 프로세스가 시작되고 있는 터미널</li>
<li>그 외 항목은 <code>-aux</code> 옵션과 동일하다.</li>
</ul>
</li>
<li><p><code>ps -f</code> : 프로세스 목록 상세 조회. 
<img src="https://velog.velcdn.com/images/pride-marimo/post/64a41304-8275-4fb6-be96-227947c17a8d/image.png" alt="">
현재 실행 중인 배시 셸과 셸에서 실행시킨 <code>ps</code> 명령 사이의 관계를 확인할 수 있다.
<code>ps</code>명령의 PPID가 배시 셸의 PID와 동일하므로 <code>ps</code>는 배시 셸의 자식 프로세스이다. 따라서 <span style="background-color:#fff5b1"><strong>명령 행에서 입력하여 실행하는 명령은 배시 셸의 자식 프로세스</strong>라는 사실을 알 수 있다.</span></p>
</li>
<li><p><code>ps f</code> : 프로세스 사이의 상속 관계 표시. 여러 프로세스 사이의 관계를 한눈에 파악하는데 도움이 된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/5d49ba61-3205-42cf-9f23-06014231dddd/image.png" alt=""></p>
<h4 id="top--실시간-메모리-점유율-cpu-사용률-확인"><code>top</code> : 실시간 메모리 점유율, CPU 사용률 확인</h4>
<p>실시간 메모리 점유율, CPU 사용률 확인에는 <code>ps -aux</code>보다 <code>top</code>이 더 유용하다. <code>ps</code>는 개별 프로세스 상태를 보여주지만, <code>top</code>은 전체적인 프로세스를 알아볼 때 사용한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/a21921eb-41a7-4696-beae-f4e78eb2ea28/image.png" alt=""></p>
</li>
<li><p>윗부분은 시스템 가동 시간, 평균 부하, 전체 프로세스 정보, 메모리 정보 등 시스템 상태를 요약하여 보여준다.</p>
</li>
<li><p>아래는 각 프로세스 정보를 CPU를 가장 많이 점유하는 순서대로 출력한다.</p>
<blockquote>
<ul>
<li>약 5초마다 내용이 업데이트 되며, <code>Space</code> 또는 <code>Enter</code>키를 누르면 프로세스 목록이 바로 갱신된다.</li>
</ul>
</blockquote>
<ul>
<li><p>필요 시 단축키를 통해 프로세스 목록을 다양한 순서로 정렬할 수 있다.</p>
<ul>
<li><code>Shift</code> + <code>p</code> : CPU 사용량(%CPU) 순</li>
<li><code>Shift</code> + <code>m</code> : 메모리 사용량(%MEM) 순</li>
<li><code>Shift</code> + <code>n</code> : PID 순</li>
<li><code>Shift</code> + <code>t</code> : CPU 사용 시간별(TIME+)</li>
<li><code>&lt;</code> 또는 <code>&gt;</code> : 정렬 순서 결정</li>
<li><code>q</code> : <code>top</code> 종료</li>
</ul>
</li>
<li><p>PID : 프로세스ID</p>
</li>
<li><p>USER : 프로세스 소유자</p>
</li>
<li><p>PR : 프로세스의 우선순위</p>
</li>
<li><p>NI : 우선순위를 지정하는 nice값</p>
</li>
<li><p>VIRT : 프로세스가 점유하는 가상 메모리 양(KB)</p>
</li>
<li><p>RES : 스왑되지 않은 물리 메모리양(KB)</p>
</li>
<li><p>SHR : 공유 메모리양(KB)</p>
</li>
<li><p>S : 프로세스 상태</p>
</li>
<li><p>%CPU : CPU 점유율</p>
</li>
<li><p>%MEM : 메모리 점유율</p>
</li>
<li><p>TIME+ : 프로세스가 CPU를 사용한 누적 시간</p>
</li>
<li><p>COMMAND : 프로세스를 실행한 명령</p>
</li>
</ul>
</li>
</ul>
<br>

<h2 id="lsof--열린-파일-목록-조회"><code>lsof</code> : 열린 파일 목록 조회</h2>
<p>리눅스 커널은 파일을 <strong>파일 기술자</strong>라는 숫자 값으로 구분한다. 리눅스는 모든 것을 파일로 추상화해서 다루기 때문에 일반 파일뿐만 아니라 입출력 장치, 네트워크 소켓, 파이프 같은 실행 중인 프로세스와 관련 된 시스템 장치를 파일 기술자로 관리한다.
이중 0은 표준 입력, 1은 표준 출력, 2는 표준 오류로 예약되어있다.</p>
<ul>
<li><p><code>lsof</code> : 열린 파일 목록을 화면에 표시. 사용자 명령 또는 시스템 프로세스로 사용중인 파일 목록 조회. 이 때 <span style="background-color:#fff5b1">일부 파일은 로그인 해 있는 사용자 권한으로 조회할 수 없어 표시되지 않지만, <code>sudo</code>명령을 붙이면 루트 권한으로 시스템 파일까지 확인할 수 있다. </span></p>
<ul>
<li><p><strong>필드 목록</strong></p>
<ul>
<li>COMMAND : 실행한 명령</li>
<li>PID : 프로세스 식별 번호</li>
<li>USER : 사용자</li>
<li>FD : 파일 기술자. 숫자 외에 cwd(현재 작업 디렉터리), rtd(루트 디렉터리), txt(텍스트 파일), mem(메모리 매핑 파일)으로 표시</li>
<li>TYPE : 파일 종류. DIR(디렉터리), REG(일반파일), CHR(문자 장치 파일), FIFO(파이프나 소켓), IPv4(네트워크 연결)로 표시</li>
<li>DEVICE : 장치 번호</li>
<li>SIZE/OFF : 파일 크기</li>
<li>NODE : 노드 번호</li>
<li>NAME : 파일 이름</li>
</ul>
</li>
<li><p><code>lsof -i</code> : 네트워크 연결 정보 조회. 연결 정보는 NAME 필드에서 확인할 수 있으며, 연결된 상태의 TCP정보는 <code>[호스트명]:[포트번호]-&gt;[원격호스트명]:[포트번호]</code> 형식으로 표기된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/2e73a3b9-d813-4a16-a6aa-3ad962b096a2/image.png" alt=""></p>
<blockquote>
<p><code>-i</code> 옵션 뒤에 프로토콜과 포트 번호를 명시하여 결과를 필터링 할 수 있다. 예를 들어 <code>lsof -i TCP:80</code>은 네트워크 연결 중 TCP 80번 포트(http)와 연결 정보만 보여준다.</p>
</blockquote>
</li>
</ul>
</li>
<li><p><code>lsof -u [사용자명]</code> : 사용자 소유의 열린 파일 목록 조회</p>
</li>
<li><p><code>lsof -c [명령]</code> : 특정 명령으로 사용되는 파일 목록 조회</p>
</li>
<li><p><code>lsof -p [PID]</code> : 프로세스가 사용하는 파일 목록 조회. 시스템 파일 조회 시 루트 권한이 필요하다.</p>
</li>
</ul>
<br>

<h2 id="작업-제어하기">작업 제어하기</h2>
<p>여러 작업을 동시에 처리하는 데 작업 제어 명령을 사용한다.</p>
<p>프로세스를 하나 실행시켰다가 중지시키며 작업을 제어해보자.
<img src="https://velog.velcdn.com/images/pride-marimo/post/446df8d5-f54c-4c14-9942-d738bc62ef49/image.png" alt=""></p>
<ul>
<li><code>wget [옵션] [url링크]</code>은 네트워크를 사용하여 파일을 받아오는 명령이다. </li>
<li>우분투 서버 iso 이미지를 내려받는 중에 <code>ctrl</code> + <code>z</code>를 눌러 작업을 중단한다.</li>
<li>하단 [1]은 작업을 제어하는 작업ID(job ID)이다.</li>
</ul>
<h4 id="jobs--목록을-화면에-표시하는-작업-제어-명령"><code>jobs</code> : 목록을 화면에 표시하는 작업 제어 명령</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/ff797ca5-df8b-4ba7-83d4-255954fa9d24/image.png" alt=""></p>
<ul>
<li><code>jobs -l</code> : 해당 프로세스의 PID를 함께 출력
<img src="https://velog.velcdn.com/images/pride-marimo/post/9bd4e7e1-bc90-4c1a-8fdf-19fa2c3b3c05/image.png" alt=""></li>
</ul>
<h4 id="bg-작업id-또는--추가-중단된-프로세스를-백그라운드에서-실행"><code>bg [작업ID]</code> 또는 <code>&amp;</code> 추가: 중단된 프로세스를 백그라운드에서 실행</h4>
<p>PID와 달리 작업ID는 퍼센트(%)를 붙여서 구분한다. 아까 중지시킨 <code>wget</code> 명령을 백그라운드에서 실행한 후 <code>jobs</code>로 확인해보면 작업이 진행중인 상태로 변경됨을 볼 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/c5ba5be0-4f61-426d-8ada-429b9ba02e3c/image.png" alt="">
<img src="https://velog.velcdn.com/images/pride-marimo/post/f3667f2e-a13f-4e7f-8c5d-058ad70032a4/image.png" alt=""></p>
<h4 id="fg-작업id--백그라운드에서-진행중인-작업을-다시-포그라운드로-실행"><code>fg [작업ID]</code> : 백그라운드에서 진행중인 작업을 다시 포그라운드로 실행</h4>
<p>작업을 포그라운드로 가져오면 작업이 끝날때까지 대기하거나 <code>ctrl</code> + <code>c</code>로 작업을 강제 종료할 수 밖에 없다.</p>
<h4 id="sleep-시간--지정한-시간만큼-대기"><code>sleep [시간]</code> : 지정한 시간만큼 대기</h4>
<p>백그라운드에서 실행되는 작업을 대기 시키려면 <code>&amp;</code>를 붙이면 된다.
ex) <code>sleep 30&amp;</code> : 30초 대기</p>
<p>언제든 <code>jobs -l</code> 명령으로 진행 중인 프로세스의 작업ID와 PID를 확인할 수 있다.</p>
<br>

<h2 id="kill--프로세스-상태-변경"><code>kill</code> : 프로세스 상태 변경</h2>
<p><code>kill</code> 명령은 실행 중인 프로세스에 시스템 신호를 보내 프로세스 상태를 변경한다.
신호 종류는 <code>kill -l</code>로 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/261af79d-f90e-410d-9eef-fd777759962d/image.png" alt=""></p>
<ul>
<li><code>kill [옵션] [PID]</code> 형태로 입력한다.
아무 옵션이 없으면 15번 SIGTERM 신호를 보내 프로세스를 종료한다.<h4 id="자주-사용되는-옵션">자주 사용되는 옵션</h4>
</li>
<li><strong><code>kill -9 [PID]</code> : SIGKILL. 프로세스 강제 종료.</strong>
대부분의 프로세스는 SIGTERM 신호로 종료되지만, 종료되지 않는 경우 SIGKILL을 사용하여 강제 종료시킨다.</li>
<li><strong><code>kill -1 [PID]</code> : SIGHUP. 프로세스를 멈추지 않고 다시 실행</strong>
환경설정을 변경하여 서비스를 다시 실행할 때 사용가능하여 서버를 운영하는데 유용하다.</li>
</ul>
<blockquote>
<h4 id="쉬운-입력을-위한-배시의-명령-히스토리">쉬운 입력을 위한 배시의 명령 히스토리</h4>
</blockquote>
<ul>
<li>커서 이동키 <code>↑</code>를 누르면 이전에 자신이 내렸던 명령을 불러올 수 있다.</li>
<li>명령 행에서 <code>history</code> 명령을 내리면 이전에 자신이 실행했던 명령 목록을 조회할 수 있다.<ul>
<li>이전에 실행했던 명령을 검색하고자 <code>history</code>명령 결과를 파이프를 이용해서 <code>grep</code> 명령으로 넘기는 방식을 자주 사용한다.
ex) <code>history | grep [명령]</code>
<img src="https://velog.velcdn.com/images/pride-marimo/post/f3821ade-187a-4792-8f46-ab87e166c921/image.png" alt=""></li>
</ul>
</li>
<li><code>!!</code>라고 입력하면 바로 전에 실행한 명령을 다시 실행한다.</li>
<li><code>history</code> 명령 결과는 <code>[번호] [명령]</code> 형식이다. <code>![번호]</code>를 입력하면 번호에 해당하는 명령을 다시 실행한다.</li>
</ul>
<blockquote>
<h4 id="실행중인-작업-목록에서-pid-알아내기">실행중인 작업 목록에서 PID 알아내기</h4>
<p><code>wget</code> 명령을 백그라운드 실행 후 PID를 찾아보자.</p>
</blockquote>
<ul>
<li><code>jobs -l</code> : 백그라운드에서 실행중인 작업목록에서 PID를 알아낼 수 있다.</li>
<li><code>ps -ef | grep wget</code> : <code>ps -ef</code> 결과 중에서 <code>grep</code>으로 문자열 wget을 검색하여 화면에 표시한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 명령행 인터페이스(2) - 사용자, 그룹 관리하기]]></title>
            <link>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A42-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B7%B8%EB%A3%B9-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A42-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B7%B8%EB%A3%B9-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 08 Nov 2025 06:00:15 GMT</pubDate>
            <description><![CDATA[<h3 id="자주-쓰이는-사용자-계정-관리-명령">자주 쓰이는 사용자 계정 관리 명령</h3>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>whoami</code></td>
<td>로그인 한 사용자 계정 표시</td>
</tr>
<tr>
<td><code>who</code></td>
<td>사용자의 접속 정보 표시</td>
</tr>
<tr>
<td><code>w</code></td>
<td>현재 접속한 사용자 정보와 시스템 정보를 함께 표시</td>
</tr>
<tr>
<td><code>sudo</code></td>
<td>시스템 관리 권한 획득</td>
</tr>
<tr>
<td><code>useradd</code>, <code>userdel</code></td>
<td>사용자 계정 추가 및 삭제</td>
</tr>
<tr>
<td><code>passwd</code></td>
<td>로그인 비밀번호 변경</td>
</tr>
<tr>
<td><code>usermod</code></td>
<td>사용자 계정 정보 수정</td>
</tr>
<tr>
<td><code>groups</code></td>
<td>사용자가 속해있는 그룹 조회</td>
</tr>
<tr>
<td><code>groupadd</code>, <code>groupdel</code></td>
<td>그룹을 생성 및 삭제</td>
</tr>
<tr>
<td><code>gpasswd</code></td>
<td>그룹 정보 수정</td>
</tr>
</tbody></table>
<br>

<h2 id="whoami-who-w--로그인한-사용자-정보-조회하기"><code>whoami, who, w</code> : 로그인한 사용자 정보 조회하기</h2>
<ul>
<li><p><code>whoami</code> : 현재 로그인 한 사용자 계정 조회
<img src="https://velog.velcdn.com/images/pride-marimo/post/49c0836c-a9ca-41df-b4db-9156d4eacd6a/image.png" alt=""></p>
</li>
<li><p><code>who</code> : 로그인한 사용자 계정, 접속한 터미널 정보, 로그인 시각, 접속한 IP 주소를 차례로 보여준다. 접속자가 여럿이면 각각의 접속정보가 표시된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/745d5093-b37a-497b-8b32-f312d0d6ce4a/image.png" alt=""></p>
<ul>
<li><code>:0</code> : 그래픽 환경에서 생성된 첫번째 터미널임을 의미한다.</li>
<li><code>tty</code> : 기본 터미널 장치. 우분투 서버에 직접 접속하는 콘솔 환경인 경우 <code>tty1</code>로 표시된다.</li>
<li><code>pts</code> : 의사 터미널 장치. ssh를 이용해 원격 접속하면 터미널 정보가 <code>pts/0</code>으로 표시된다,</li>
</ul>
</li>
<li><p><code>w</code> : 현재 접속해 있는 사용자 정보와 함께 시스템 정보를 화면에 출력한다.
시스템 시간 정보, 부팅 이후 경과한 시간, 접속한 사용자 수, 시스템의 평균 부하율이 차례로 표시된다. <code>uptime</code> 명령 결과와 동일하다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b3a76f68-96cf-4e46-97eb-bd7a8a5f8f66/image.png" alt=""></p>
<ul>
<li><code>USER</code> : 접속한 사용자 계정</li>
<li><code>TTY</code> : 터미널</li>
<li><code>FROM</code> : IP주소</li>
<li><code>IDLE</code> <code>JCPU</code> <code>PCPU</code> : 사용자가 사용하고 있는 CPU 점유율 정보</li>
<li><code>WHAT</code> : 접속자가 현재 사용하고 있는 명령</li>
</ul>
</li>
</ul>
<br>

<h2 id="sudo--루트-권한-획득하기"><code>sudo</code> : 루트 권한 획득하기</h2>
<p>리눅스는 여러 사용자가 동시에 사용 가능한 다중 사용자 운영체제이다. 사용자는 권한에 따라 시스템 자원에 대한 접근, 사용여부가 결정된다.</p>
<p>일반적으로 리눅스는 사용자 실수로 시스템에 돌이킬 수 없는 문제가 발생하는 사고를 방지하고자 루트 사용자의 로그인을 제한한다. <span style="background-color:#fff5b1"><strong>시스템 관리자도 일반 사용자 계정으로 로그인</strong>해야하며, <strong>루트 권한이 필요할 때 루트 권한을 획득</strong>하는 과정을 거친다.</span></p>
<h3 id="루트-권한이-없어-허가-거부-오류-메시지가-보이는-경우">루트 권한이 없어 허가 거부 오류 메시지가 보이는 경우</h3>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/d6fae196-96dd-4e66-b634-db1bb721a384/image.png" alt=""></p>
<h3 id="루트-권한-부여-후-명령-처리">루트 권한 부여 후 명령 처리</h3>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/904018ee-8ba5-4283-91c0-79dbe8da6537/image.png" alt=""></p>
<ul>
<li><code>apt update</code> : 패키지 저장소에서 새로운 패키지 목록을 가져오는 명령. 실행 시 루트 권한이 필요하다.</li>
<li><strong><code>sudo</code> 로 획득한 루트 권한은 일정 기간 유지되므로 명령을 실행할 때마다 매번 패스워드를 입력할 필요는 없다.</strong></li>
</ul>
<br>

<h2 id="useradd--사용자-계정-추가하기"><code>useradd</code> : 사용자 계정 추가하기</h2>
<h4 id="useradd-옵션-사용자-계정-형식으로-입력한다"><code>useradd [옵션] [사용자 계정]</code> 형식으로 입력한다.</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/de3db753-58a9-411f-89c4-1cf4c3ff79e0/image.png" alt=""></p>
<ul>
<li>옵션 <code>-m(--create-home)</code>을 사용하면 사용자 계정을 추가함과 동시에 홈 디렉터리를 함께 생성한다.</li>
<li><code>ls</code> 명령으로 새로 생성된 홈 디렉터리를 확인할 수 있다.<br>

</li>
</ul>
<h4 id="passwd--사용자-계정의-패스워드를-변경한다"><code>passwd</code> : 사용자 계정의 패스워드를 변경한다.</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/bb269bcd-916a-4bd1-9849-bfdad2c88213/image.png" alt=""></p>
<ul>
<li><p>다른 사용자의 패스워드를 변경하려면 루트 권한이 필요하므로 <code>sudo</code>를 함께 쓴다.</p>
</li>
<li><p>새로 설정할 패스워드를 입력 후 확인을 위해 재입력한다.</p>
</li>
<li><p>현재 로그인 해 있는 사용자 계정의 패스워드를 변경할 때는 sudo 없이 passwd만 입력해도 된다.</p>
<br>

</li>
</ul>
<h4 id="cat-etcpasswd-로-사용자-계정-목록을-확인할-수-있다"><code>cat etc/passwd</code> 로 사용자 계정 목록을 확인할 수 있다.</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/2ae16cb2-8904-4602-9074-e2e5312277fc/image.png" alt=""></p>
<ul>
<li><code>cat</code> : 파일 내용을 화면에 표시한다.</li>
<li>파일의 각 행은 사용자 계정 정보를 의미한다.
콜론(:)으로 구분해둔 필드는 왼쪽부터 <code>[사용자계정]:[패스워드]:[UID]:[GID]:[추가정보]:[홈디렉터리]:[로그인셸]</code> 을 의미한다.<ul>
<li><strong>사용자 계정</strong> : 시스템 운영을 위해 자동으로 생성된 사용자 계정이 대부분이며, 설치 과정에서 등록한 사용자, useradd로 추가한 사용자 계정을 확인할 수 있다.</li>
<li><strong>패스워드</strong> : 아주 오래된 리눅스 시스템은 사용자 패스워드를 passwd 파일에 평문으로 저장했지만, 지금은 모든 패스워드를 x로 표시한다. 누구도 알아보지 못하도록 암호화하여 저장된다.</li>
<li><strong>UID</strong> : 사용자 계정을 식별하는 고유번호(User ID)이다.</li>
<li><strong>GID</strong> : 사용자가 속해있는 그룹 식별번호(Group ID)이다. 
UID와 GID가 동일한 이유는 일반적으로 사용자 계정이 만들어지면 계정 이름과 동일한 그룹이 자동 생성되기 때문이다.</li>
<li><strong>추가정보</strong> : 설치 과정에서 사용자 실제 이름을 입력했다면 추가 정보로 저장된다. <code>adduser</code> 명령에서 입력하는 정보들이 여기에 저장된다.</li>
<li><strong>홈 디렉터리</strong> : 로그인 후 사용하는 기본 작업 디렉터리 경로이다.</li>
<li><strong>로그인 셸</strong> : 로그인 후 사용할 기본 셸을 지정한다.</li>
</ul>
</li>
</ul>
<br>

<h4 id="adduser--useradd-와-passwd를-한번에-사용한다"><code>adduser</code> : useradd 와 passwd를 한번에 사용한다.</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/34531068-4d54-4dbf-921d-248dd5d91ce5/image.png" alt=""></p>
<ul>
<li><code>adduser</code>의 인자로 추가할 사용자 계정을 넘겨 실행한다</li>
<li>패스워드를 설정한다.</li>
<li>사용자 추가 정보를 입력한다. 여기서는 사용자 이름만 입력했다.</li>
<li>입력이 끝나면 Y를 눌러 사용자를 등록한다.</li>
</ul>
<p>사용자 등록 후 <code>cat /etc/passwd</code>로 파일을 조회하면 새로 등록한 사용자 정보를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/a92d2272-dc27-4384-a74d-6efacba54feb/image.png" alt=""></p>
<h4 id="userdel-옵션-사용자-계정--사용자-계정-삭제"><code>userdel [옵션] [사용자 계정]</code> : 사용자 계정 삭제</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/1a4b1862-30bc-486d-a718-f9b10abc827b/image.png" alt=""></p>
<ul>
<li><p><code>-r(--remove)</code> : 사용자 홈 디렉터리까지 함께 제거한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/8763fed6-7f5b-4b69-af59-eee6b9105443/image.png" alt=""></p>
</li>
<li><p><code>-r</code> 옵션을 사용하지 않은 경우 계정 삭제 후에도 홈 디렉터리가 남아있다. 이 때 <code>rm -rf</code>로 디렉터리를 삭제할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/9dd02f5a-2573-4254-bcd6-041c26ea11ad/image.png" alt=""></p>
</li>
</ul>
<br>

<h2 id="사용자-계정-전환하기">사용자 계정 전환하기</h2>
<p><code>sudo</code>를 실행할 수 있는 권한은 sudo 설정 파일인 <code>/etc/sudoers</code>에서 지정한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/567cc1f7-1094-4524-a3c9-c4fab96bbce5/image.png" alt="">
<img src="https://velog.velcdn.com/images/pride-marimo/post/38d899e6-6b48-4b1d-a2cb-0d9267982020/image.png" alt="">
사용자 계정 root, 그룹 admin(<code>%admin</code>), 그룹 sudo(<code>%sudo</code>)에 속하는 사용자는 모든 명령에 대해 <code>sudo</code>로 루트 권한을 얻을 수 있다.</p>
<h4 id="su-옵션-사용자-계정--사용자-계정-전환"><code>su [옵션] [사용자 계정]</code> : 사용자 계정 전환</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/7f37f567-4097-473b-9e9a-3f9d5302e77b/image.png" alt=""></p>
<ul>
<li><code>whoami</code>로는 <code>su</code>명령으로 전환한 사용자 계정이 표시된다.</li>
<li><code>who</code>로는 로그인한 사용자 계정 정보를 보여준다. </li>
<li><code>su</code> 명령을 자주 사용할 때 처음 로그인한 사용자 계정을 확인하기위해 <code>who</code>가 쓰인다.</li>
</ul>
<h4 id="groups--사용자가-속한-그룹-조회"><code>groups</code> : 사용자가 속한 그룹 조회</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/79d62192-a4dd-41f3-8d57-5cc9a896c613/image.png" alt="">
계정을 생성할 때 자동으로 같은 이름의 사용자 그룹이 생성된다. </p>
<p>새로 추가한 사용자와 달리 우분투를 설치하면서 등록한 사용자는 다양한 시스템 그룹에 속해있다. sudoers에서 살펴보았듯이 sudo 그룹에 속한 사용자는 <code>sudo</code> 명령을 사용할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/475ab193-eb53-4fff-adcf-56155084a5ef/image.png" alt=""></p>
<h4 id="루트-권한이-없는-사용자는-sudo를-사용할-수-없다">루트 권한이 없는 사용자는 <code>sudo</code>를 사용할 수 없다.</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/939b7106-46c4-46e7-af01-bdcb47557a6a/image.png" alt=""></p>
<h4 id="exit--su-명령-전-원래-사용자-계정으로-복귀"><code>exit</code> : <code>su</code> 명령 전 원래 사용자 계정으로 복귀</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/83083558-cbf7-4f3f-b228-239ba69614c0/image.png" alt=""></p>
<h4 id="usermod-옵션-사용자-계정--사용자-계정-정보-변경"><code>usermod [옵션] [사용자 계정]</code> : 사용자 계정 정보 변경</h4>
<p>루트 권한을 가진 시스템 관리자는 <code>sudo</code> 로 다른 사용자 계정 정보를 변경할 수 있다. 
다음 명령은 사용자 계정을 sudo 그룹에 포함시킨다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/d70c825e-69b1-414c-88c5-8885fb417e28/image.png" alt=""></p>
<ul>
<li><code>-a(--append)</code> : 변경 대신 정보를 추가하는 옵션
<span style="background-color:#fff5b1"><strong><code>-a</code> 없이 명령을 실행하면</strong> 사용자 계정의 정보는 &#39;추가&#39;가 아니라 &#39;변경&#39; 되어버린다. 즉, <strong>기존 정보가 모두 삭제</strong>되므로 주의해야 한다.</span></li>
<li><code>-G(--groups)</code> : 사용자 계정의 그룹을 대상으로 한다.</li>
</ul>
<p>아까 tester 계정은 사용자 권한이 없어 <code>sudo</code> 명령을 실행할 수 없었다.
sudo 그룹에 추가된 후 <code>sudo</code> 명령을 실행할 수 있음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/c5df5467-ff61-4b28-a080-ae19035251c3/image.png" alt=""></p>
<blockquote>
<p>새로운 사용자 계정 추가 시 <code>-G</code> 옵션을 사용하여 바로 그룹을 지정할 수도 있다.</p>
</blockquote>
<pre><code class="language-bash">sudo useradd -m -G sudo testuser</code></pre>
<p>위 명령어는 새로 testuser라는 계정을 생성할 때 바로 sudo 그룹에 포함시킨다.</p>
<p>sudo 그룹 권한을 해지할 수도 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/85d7a8ff-7497-487e-97f8-e1446ee99074/image.png" alt=""></p>
<ul>
<li><code>-G</code> 옵션으로 사용자 그룹 정보를 재지정한다.</li>
<li>해당 사용자 계정으로 로그인(<code>su</code>)하여 그룹 정보를 확인해보면 sudo 그룹 권한이 사라져있음을 확인할 수 있다.</li>
<li>단순히 그룹 사용자를 삭제하려면 그룹 관리 명령인 <code>gpasswd</code>를 사용하는 것이 더 편리하다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/583af8a2-1d73-4fd2-ab27-932ff61971a4/image.png" alt=""></li>
</ul>
<br>

<h2 id="그룹-관리하기">그룹 관리하기</h2>
<h4 id="groupadd-옵션-그룹--새로운-그룹-생성"><code>groupadd [옵션] [그룹]</code> : 새로운 그룹 생성</h4>
<ul>
<li><p><code>-a(--add)</code> : 그룹에 사용자 추가 (단건)</p>
<pre><code class="language-bash">sudo gpasswd -a [사용자 계정] [그룹]</code></pre>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/0d722594-0bf8-4aa9-9283-7b796da777dd/image.png" alt=""></p>
</li>
<li><p><code>-M(--members)</code> : 그룹에 사용자 추가 (다건)</p>
<pre><code class="language-bash">sudo gpasswd -M [사용자 계정],[사용자 계정],[사용자 계정] [그룹]</code></pre>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/ccffe4e2-dc20-46d9-af1f-f1da25be6977/image.png" alt=""></p>
</li>
<li><p><code>groups [사용자 계정]</code> : 해당 사용자가 속해있는 그룹을 화면에 표시한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/ff47b65f-4316-4556-ac33-d0ac75d2c5c5/image.png" alt=""></p>
</li>
<li><p><code>/etc/grouop</code>에서 그룹 정보를 조회할 수 있다.
각 필드는 <code>[그룹]:[패스워드]:[GID]:[사용자 목록]</code> 을 의미한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/9304ae51-7044-49a3-93cf-24378fae95e4/image.png" alt=""></p>
</li>
<li><p><code>-d(--delete)</code> : 그룹에서 사용자 삭제 </p>
<pre><code class="language-bash">sudo gpasswd -d [사용자 계정] [그룹]</code></pre>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/ae0009a3-0525-461e-bde0-c25fffecdfce/image.png" alt="">
groups로 삭제됨을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b791649f-8535-4c73-8cb7-a15e4c4435e1/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><code>groupdel</code> : 그룹을 삭제
<img src="https://velog.velcdn.com/images/pride-marimo/post/1f3e0a35-f591-455a-9421-82be2bacb641/image.png" alt="">
<img src="https://velog.velcdn.com/images/pride-marimo/post/3f866e70-ad72-4e64-ab66-65fbde5ef4db/image.png" alt="">
<code>/etc/group</code> 파일에서 salesA 그룹이 삭제됨을 확인할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 명령행 인터페이스(1) - 시스템 시작, 종료]]></title>
            <link>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A41-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%8B%9C%EC%9E%91-%EC%A2%85%EB%A3%8C</link>
            <guid>https://velog.io/@pride-marimo/1.-%EB%AA%85%EB%A0%B9%ED%96%89-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A41-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%8B%9C%EC%9E%91-%EC%A2%85%EB%A3%8C</guid>
            <pubDate>Sat, 25 Oct 2025 09:31:37 GMT</pubDate>
            <description><![CDATA[<h2 id="명령행-인터페이스cli란">명령행 인터페이스(CLI)란?</h2>
<p><strong>사용자가 키보드로 문자열 명령을 입력</strong>하고 <strong>컴퓨터가 처리한 결과를 화면에서 확인</strong>하는 상호작용이다.
명령행 인터페이스는 시스템 자원을 덜 소모하고 효율적으로 시스템을 관리할 수 있다. 특히 서비스를 빠르고 안정적으로 제공하는 일 이더 중요한 서버 컴퓨터에서는 그래픽 사용자 인터페이스(GUI)보다 명령행 인터페이스를 선호한다. 서버 서비스에 꼭 필요한 패키지만 제공하는 우분투 서버도 명령행으로 제어한다.
그러므로 우분투 데스크톱을 사용하더라도 명령행 인터페이스를 익혀야 한다. 
<br></p>
<h2 id="bash-셸에서-명령행-인터페이스-사용해보기">Bash 셸에서 명령행 인터페이스 사용해보기</h2>
<p>Bash 셸에서 HelloBash라는 텍스트를 입력해보자.</p>
<ul>
<li>터미널 실행 후 <code>HelloBash</code> 문자열 입력
<img src="https://velog.velcdn.com/images/pride-marimo/post/0a9e9dc5-5735-4371-9de0-61f707561f11/image.png" alt=""><ul>
<li>1행: 셸이 사용자에게서 명령을 받을 준비가 되었음을 의미한다.</li>
<li>2행: 출력할 텍스트를 바로 입력하고 엔터를 누르면 셸이 해석을 시도한다.</li>
<li>3행: 해당 문자열은 셸이 해석할 수 없는 아무 의미 없는 명령이므로 오류 메세지를 출력한다.</li>
<li>4행: 명령 실행이 끝나고 다음 사용자 입력을 받고자 새로운 프롬프트가 나타난다.</li>
</ul>
</li>
<li><code>echo HelloBash</code> 문자열 입력
<img src="https://velog.velcdn.com/images/pride-marimo/post/dec2e097-0199-4273-aeeb-1df9772e96b8/image.png" alt="">
이 때 셸은 입력된 문자열을 echo 와 HelloBash로 나누어 처리한다. echo는 배시가 제공하는 문자열 출력 명령으로, 뒤에 오는 문자열은 echo 명령이 출력할 문자열 인자로 분석한다.<ul>
<li>1행: 명령어 echo와 문자열 HelloBash를 입력 후 엔터를 누른다.</li>
<li>2행: echo 명령의 결과로 사용자가 입력한 문자열 HelloBash를 화면에 출력한다.
(정상적 명령 수행으로 오류가 발생하지 않는다.)</li>
<li>3행: 새로운 프롬프트가 나타나며 셸이 다시 준비가 되었음을 알 수 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="셸-명령-옵션">셸 명령 옵션</h3>
<p>어떤 명령들은 세부 기능을 선택할 수 있도록 옵션을 제공한다.</p>
</blockquote>
<ul>
<li><strong>옵션은 기호 <code>--</code>나 <code>-</code>로 시작</strong>하며 영문 대/소문자로 입력한다. 
일반적으로 <code>--</code>로 시작하는 옵션은 이름으로 의미를 알기 쉽지만 여러 문자를 입력해야하며, <code>-</code>로 시작하는 축약형 옵션은 짧게 입력할 수 있어 사용에는 편리하지만 어느정도 암기가 필요하다.<br>
예를 들어 사용자를 추가하는 <code>useradd</code> 명령은 사용법을 알려주는 <code>--help</code>와 <code>-h</code> 옵션을 모두 지원한다.<pre><code class="language-bash">useradd --help
useradd -h</code></pre>
</li>
<li><strong>여러 옵션을 붙여 쓰는 것도 가능</strong>하다.
예를 들어 파일 목록을 화면에 보여주는 <code>ls</code> 명령은 숨김 파일까지 모두 표시하는 <code>-a</code> 옵션과 파일 정보를 함께 출력하는 <code>-l</code> 옵션을 제공하는데, 두 옵션을 동시에 사용해서 적용할 수 있다. <pre><code class="language-bash">#옵션을 붙여 쓰는 경우 순서와 상관없이 명령을 처리한다.
ls -al
ls -la </code></pre>
</li>
</ul>
<br>

<h2 id="시스템-시작-종료하기">시스템 시작, 종료하기</h2>
<p>시스템을 시작하고 종료하는 명령어는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>명령</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>logout</td>
<td>셸 사용을 종료하고 로그인 대기 상태로 돌아간다.</td>
</tr>
<tr>
<td>printenv</td>
<td>설정된 모든 환경변수를 출력한다.</td>
</tr>
<tr>
<td>export</td>
<td>환경변수를 등록한다.</td>
</tr>
<tr>
<td>unset</td>
<td>등록한 환경변수를 삭제한다.</td>
</tr>
<tr>
<td>shutdown</td>
<td>시스템을 종료한다.</td>
</tr>
<tr>
<td>reboot</td>
<td>시스템을 다시 시작한다.</td>
</tr>
</tbody></table>
<h3 id="셸-시작하기">셸 시작하기</h3>
<ul>
<li><p>우분투 데스크톱 : 터미널 앱 실행. 한글 입출력 가능, 명령어 <code>logout</code> 실행 시 터미널 종료
<img src="https://velog.velcdn.com/images/pride-marimo/post/a3600048-f436-41b2-9dbe-66c17b7b54d8/image.png" alt=""></p>
</li>
<li><p>우분투 서버 : 기본 콘솔 환경. 계정 로그인 시 활용 가능. 한글 입출력 불가능. 명령어 <code>logout</code> 실행 시 첫화면으로 돌아가 로그인 대기
<img src="https://velog.velcdn.com/images/pride-marimo/post/58c29911-2e06-43ed-98d7-eb316e35f6e8/image.png" alt=""></p>
</li>
</ul>
<h3 id="셸-환경변수">셸 환경변수</h3>
<h4 id="printenv"><code>printenv</code></h4>
<p><code>printenv</code>는 <strong>현재 설정된 모든 환경변수를 출력</strong>한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/1669f7f8-e47a-4366-a914-cbd87f817486/image.png" alt=""></p>
<p>이 중 핵심 내용만 몇 가지 정리하였다.</p>
<table>
<thead>
<tr>
<th>변수명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>SHELL</td>
<td>현재 로그인 한 셸 정보</td>
</tr>
<tr>
<td>PWD</td>
<td>현재 작업 디렉터리 경로</td>
</tr>
<tr>
<td>LOGNAME</td>
<td>로그인 한 사용자 이름</td>
</tr>
<tr>
<td>HOME</td>
<td>사용자 홈 디렉토리 경로</td>
</tr>
<tr>
<td>LANG</td>
<td>로케일 설정</td>
</tr>
<tr>
<td>PATH</td>
<td>실행할 명령을 찾는 경로</td>
</tr>
</tbody></table>
<h4 id="기본-변수-등록">기본 변수 등록</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/feb7cd26-d241-444d-8c5e-226bf54d9465/image.png" alt=""></p>
<p>[변수명]=[값]의 형식으로 입력하여 셸에서 사용할 변수를 정의한다.
이 때, 변수 이름, 등호, 값 사이에 공백이 없어야한다.
변수를 사용할 때는 변수이름 앞에 $를 붙여야 한다.</p>
<h4 id="export"><code>export</code></h4>
<p>사용자가 실행하는 명령은 셸의 자식 프로세스로 동작하므로, 방금 등록한 VAR 변수는 자식 프로세스에서 동작할 수 없어 사용에 제약이 있다. 환경변수를 조회해도 해당 변수는 찾아볼 수 없다.</p>
<p><code>export</code> 명령은 변수를 환경변수로 내보낸다. VAR을 export 명령으로 등록 후 환경변수를 확인하면 VAR 변수가 등록됨을 확인할 수 있다.</p>
<pre><code class="language-bash">export VAR=1</code></pre>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/2a9a0857-e188-43ea-aac3-d69aa1531cdf/image.png" alt=""></p>
<blockquote>
<p>❗<strong><code>export</code> 로 내보낸 환경변수는 로그인 상태에서만 유지된다.</strong>
터미널을 닫거나 로그아웃 이후 환경변수는 모두 초기화된다. <strong>환경변수를 유지하려면 환경 설정 파일에 등록</strong>해야 한다.
현재 로그인 한 사용자에 해당하는 환경변수는 홈 디렉터리의 배시 환경 설정 파일 <code>.bashsrc</code>에 <code>export</code>명령으로 등록할 수 있다. 
시스템 전체 등록 시 <code>/etc/environment</code>에 등록하면 된다.</p>
</blockquote>
<h4 id="unset"><code>unset</code></h4>
<p><code>unset</code> 명령은 환경변수를 삭제한다.</p>
<pre><code class="language-bash">unset VAR</code></pre>
<h3 id="시스템-종료하기">시스템 종료하기</h3>
<p><code>shutdown</code> 명령은 시스템을 종료한다. 우분투 서버, 우분투 데스크톱 터미널 모두에서 동일하게 동작한다.
shutdown은 루트권한이 필요하므로 앞에 <code>sudo</code> 명령을 붙여야 한다. </p>
<pre><code class="language-bash">sudo shutdown -h now</code></pre>
<ul>
<li><code>shutdown [옵션][시간]</code> 형식으로 입력한다.</li>
<li><code>-h</code>는 명령을 실행한 이후 전원을 차단하는 옵션이다.</li>
<li><code>now</code>는 지금 즉시 명령을 실행한다. <h4 id="예약-종료">예약 종료</h4>
</li>
<li>몇 분 후에 종료할 지 옵션 지정 시 now 자리에 <code>15</code> 를 입력하면 된다.<pre><code class="language-bash">sudo shutdown -h 15</code></pre>
</li>
<li>종료 시각을 지정할 수 있다. 3시 30분에 종료하고 싶다면 <code>03:30</code>을 입력하면 된다.<pre><code class="language-bash">sudo shutdown -h 03:30</code></pre>
</li>
<li>예약 종료를 취소하려면 옵션 <code>-c</code>를 사용한다.<pre><code class="language-bash">sudo shutdown -c</code></pre>
</li>
<li>재부팅 시 옵션 <code>-r</code>을 사용하거나 <code>reboot</code> 명령어를 사용한다.<pre><code class="language-bash">sudo shutdown -r now
sudo reboot        #shutdown -r과 동일한 동작</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[0. 우분투 서버 기본 설정]]></title>
            <link>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%AC-%EC%84%9C%EB%B2%84-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95-8tq9h51v</link>
            <guid>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%AC-%EC%84%9C%EB%B2%84-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95-8tq9h51v</guid>
            <pubDate>Sat, 25 Oct 2025 08:26:05 GMT</pubDate>
            <description><![CDATA[<p>우분투 서버 설치 후 기본 설정 사항들을 정리하였다.
<br></p>
<h2 id="ip-설정-확인">ip 설정 확인</h2>
<p><code>ip a</code> 명령어로 네트워크 인터페이스 enp1s0의 inet 항목에서 받아온 IP주소를 확인할 수 있다. 주소 정보는 사용자별로 다를 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/0cbdd495-c8ce-40b7-80ca-01622165ef55/image.png" alt=""></p>
<h2 id="언어-설정-확인">언어 설정 확인</h2>
<p><code>locale</code> 명령어로 en_US.UTF-8로 잘 설정되었는지 확인한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b4fc1f1b-8ab7-40eb-b267-01c6022af301/image.png" alt=""></p>
<p>혹시 로케일 설정이 잘못되었다면 <code>sudo dpkg-reconfigure locales</code> 명령어로 재설정할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/a0e7dbea-6aa4-4f25-a777-b2bee4276603/image.png" alt=""></p>
<h2 id="시간대-설정">시간대 설정</h2>
<p>서버 설치 과정에서 시간대 선택이 생략되었으므로 설치후 반드시 시간대 설정을 변경해야한다.</p>
<h4 id="1-date-명령어로-현재-서버-시간대-확인">1) <code>date</code> 명령어로 현재 서버 시간대 확인</h4>
<p>국제 표준 협정시계시(UTC)로 설정되어있음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/1c86fafa-aff6-49e3-aa5e-0f2c1e07cfde/image.png" alt=""></p>
<h4 id="2-timedatectl-set-timezone-명령으로-시간대-변경">2) <code>timedatectl set-timezone</code> 명령으로 시간대 변경</h4>
<p>우리나라에 해당하는 &#39;Asia/Seoul&#39;로 입력한다.
변경 후 <code>date</code> 명령어를 실행하면 한국 표준시(KST)로 변경되었음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b1cea719-7f48-4648-a04b-d34be174f02d/image.png" alt=""></p>
<h2 id="패키지-저장소-주소-변경">패키지 저장소 주소 변경</h2>
<p>기본 패키지 저장소는 우분투 공식 패키지 저장소의 미러링 서버인 <code>kr.archive.ubuntu.com</code>으로 설정되어있다. 
이 주소를 KAIST 미러링 서버 <code>ftp.kaist.ac.kr</code> 또는 카카오의 미러링 서버 <code>mirror.kakao.com</code>로 변경하면 더 빠른 패키지 설치가 가능하다.</p>
<h4 id="1-vi-명령으로-소스-리스트-파일-수정">1) vi 명령으로 소스 리스트 파일 수정</h4>
<ul>
<li><p><code>sudo vi /etc/apt/sources.list.d/ubuntu.sources</code> 로 수정할 파일 열기
<img src="https://velog.velcdn.com/images/pride-marimo/post/8b90d744-2d5c-47f5-ba3e-fd6d2e80aeb4/image.png" alt=""></p>
</li>
<li><p><code>:%s/kr.archive.ubuntu.com/mirror.kakao.com/g</code> 로 텍스트 일괄 치환
<img src="https://velog.velcdn.com/images/pride-marimo/post/edc1b60a-ba13-4945-b11f-fa0407d3daef/image.png" alt=""></p>
</li>
<li><p><code>:wq</code> 로 파일 변경내용 저장 및 편집기 종료</p>
<h4 id="3-패키지-저장소-목록-갱신">3) 패키지 저장소 목록 갱신</h4>
<p><code>sudo apt update</code></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[0. 우분투(Ubuntu) 서버 설치하기]]></title>
            <link>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%ACUbuntu-%EC%84%9C%EB%B2%84-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%ACUbuntu-%EC%84%9C%EB%B2%84-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 07 Sep 2025 06:52:33 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>VMware 설치 주소 : <a href="https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion">https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion</a></li>
</ul>
</blockquote>
<ul>
<li>Ubuntu 설치 주소 : <a href="https://ubuntu.com/download/server">https://ubuntu.com/download/server</a></li>
<li>Ubuntu 설치 버전 : 24.04.3 LTS</li>
<li>Ubuntu 설치 환경<ul>
<li>VMware Workstation 17 Player</li>
</ul>
</li>
</ul>
<p>테스트와 실습 위주로 진행할 예정이고, 이후 원활한 화면 캡처를 위해 VMware에서 사용을 위해 VMware에 설치를 진행한다.
VMware가 이미 설치되었다는 전제 하에 Ubuntu Server 설치과정만 포스팅하였다.</p>
<h2 id="vmware에-ubuntu2404-설치">VMware에 Ubuntu24.04 설치</h2>
<h3 id="1-vmware-실행-및-가상머신-생성-선택">1. VMware 실행 및 가상머신 생성 선택</h3>
<p><strong>Create a New Virtual Machine</strong> 을 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/77f4f76d-b0da-4856-b557-5310fa1d9d82/image.png" alt=""></p>
<h3 id="2-하드웨어-용량-확인-및-사용자-설정">2. 하드웨어 용량 확인 및 사용자 설정</h3>
<h4 id="1-설치-할-iso-이미지-선택">1) 설치 할 iso 이미지 선택</h4>
<ul>
<li><strong>Installer disc image file</strong> 을 선택한 후 <strong>Browse</strong>를 클릭하여 내려받은 Ubuntu iso 파일을 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/799e7f67-f24c-438e-85f2-c83360672c30/image.png" alt=""></li>
<li><strong>Next</strong> 를 클릭하여 다음 단계로 이동한다.</li>
</ul>
<h4 id="2-서버-명-설정">2) 서버 명 설정</h4>
<ul>
<li>VMware에 표기 될 서버 명과 설치 위치를 설정한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/dcd3ac62-e0d1-4010-ab5d-3589c60f1250/image.png" alt=""></li>
</ul>
<h4 id="3-저장공간-크기-설정">3) 저장공간 크기 설정</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/pride-marimo/post/0f938cf6-adef-4cd3-b53c-535150137468/image.png" alt=""></li>
</ul>
<h4 id="4-설정-정보-확인-및-설치">4) 설정 정보 확인 및 설치</h4>
<ul>
<li>설정된 정보가 맞는지 확인 후 <strong>Finish</strong>를 클릭하면 가상머신 환경이 구축된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/8f83a3b6-585a-44da-a619-b061e15d74a5/image.png" alt=""></li>
</ul>
<h3 id="3-ubuntu-server-실행">3. Ubuntu Server 실행</h3>
<p>설치가 완료되면 자동으로 Ubuntu가 실행된다. 자동실행 되지 않는 경우 메인화면에서 Ubuntu를 선택하여 실행하면 된다.</p>
<h4 id="1-try-or-install-ubuntu-server-선택-및-언어-선택">1) Try or install Ubuntu Server 선택 및 언어 선택</h4>
<ul>
<li>한국어 미지원이므로 영어 선택 후 엔터를 누른다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/d6dbbb7e-4716-40ff-a1a6-d5942aac8747/image.png" alt=""></li>
</ul>
<h4 id="2-설치도구-업데이트-및-키보드-설정">2) 설치도구 업데이트 및 키보드 설정</h4>
<ul>
<li>인터넷이 연결된 경우 설치도구 업데이트 여부를 선택할 수 있다. 필자는 업데이트 없이 설치를 진행한다.</li>
<li>키보드 배열을 선택한다. Layout, Variant 모두 기본값인 <strong>&#39;English (US)&#39;</strong> 선택 후 <strong>&#39;Done&#39;</strong>을 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/172479ed-2475-4a68-977a-121d804978fb/image.png" alt=""></li>
</ul>
<h4 id="3-인스톨-타입-설정">3) 인스톨 타입 설정</h4>
<ul>
<li>인스톨 타입을 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/17cf4d06-860f-46ac-82b3-eb124b59ae67/image.png" alt=""></li>
</ul>
<h4 id="4-네트워크-설정">4) 네트워크 설정</h4>
<ul>
<li>enp1s0는 시스템에 설치된 이더넷 네트워크 인터페이스 명이다.
일반적으로 DHCP를 이용해 IP주소를 자동으로 받아오는 환경에서는 DHCPv4항목에 자동으로 IP주소가 할당된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/d6be0144-6e1d-40c8-9f44-e53dd921a526/image.png" alt=""></li>
<li>프록시 정보를 입력한다. 프록시 없이 연결 가능한 환경에서는 주소를 비워두고 <strong>&#39;Done&#39;</strong>을 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/aed03f42-0e00-48c9-8d1d-138c3853bbac/image.png" alt=""></li>
</ul>
<h4 id="5-패키지-저장소-설정">5) 패키지 저장소 설정</h4>
<ul>
<li>우분투 패키지를 받아올 저장소 주소를 선택한다. 기본적으로 설정되어있는데, 아래 국내 사이트를 이용하면 속도가 더 빠르다는 장점이 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/e89f0ecd-2580-4dcf-9c7c-b88f6a0c7cd0/image.png" alt=""><ul>
<li>카카오 : <a href="http://mirror.kakao.com/">http://mirror.kakao.com/</a></li>
<li>카이스트 : <a href="http://ftp.kaist.ac.kr/">http://ftp.kaist.ac.kr/</a></li>
</ul>
</li>
</ul>
<h4 id="6-storage-설정">6) Storage 설정</h4>
<ul>
<li>storage를 설정한다. 기본값으로 진행하였다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/76443551-3ac1-470e-99b6-8342668857e6/image.png" alt="">
<img src="https://velog.velcdn.com/images/pride-marimo/post/33de4cf5-df47-4f73-96d4-d99f84e17461/image.png" alt="">
<img src="https://velog.velcdn.com/images/pride-marimo/post/49ff6044-443f-427f-a80e-3f1dfc08e1b4/image.png" alt=""></li>
</ul>
<h4 id="7-시스템-정보-입력">7) 시스템 정보 입력</h4>
<ul>
<li>Ubuntu 로그인할 때 사용하는 정보로 서버 이름 및 비밀번호 설정한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/d7dfc0b5-157a-4316-a13b-3ae91c601404/image.png" alt=""></li>
<li>Upgrage관련해서는 Skip for now를 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/1ded3a22-fb7e-4a61-9113-064311366014/image.png" alt=""></li>
</ul>
<h4 id="8-ssh-설정">8) SSH 설정</h4>
<ul>
<li>OpenSSH 서버 패키지를 설치할 지 결정한다. 이 때 OpenSSH 서버를 설치하면 우분투 서버 설치가 끝난 직후 콘솔 로그인 대신 원격 접속이 가능하다.
서버 설치 이후에도 설치할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/4f85e3eb-fcd8-42e3-af78-ba346cb57c31/image.png" alt=""></li>
</ul>
<h4 id="9-설치할-패키지-선택">9) 설치할 패키지 선택</h4>
<ul>
<li>우분투 서버는 서버 서비스에 꼭 필요한 패키지만 제공한다. 추가적으로 필요한 패키지가 있는 경우 선택한다.
불필요한 도구는 되도록 설치하지 않는 편이 보안 유지를 위해 바람직하다는 점을 참고하자.
<img src="https://velog.velcdn.com/images/pride-marimo/post/8e95b2be-0c75-4fc2-8ee0-3f988a0ad504/image.png" alt=""></li>
</ul>
<h4 id="10-설치-종료">10) 설치 종료</h4>
<ul>
<li>설치가 종료되면 선택 화면이 보인다. <strong>&#39;Reboot Now&#39;</strong>를 선택하여 재기동한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/3a0d5741-650e-4d62-bff8-423d33d4c5eb/image.png" alt=""></li>
</ul>
<h2 id="우분투-서버-기본-설정">우분투 서버 기본 설정</h2>
<h3 id="ip-설정-확인">ip 설정 확인</h3>
<p><code>ip a</code> 명령어로 네트워크 인터페이스 enp1s0의 inet 항목에서 받아온 IP주소를 확인할 수 있다. 주소 정보는 사용자별로 다를 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/0cbdd495-c8ce-40b7-80ca-01622165ef55/image.png" alt=""></p>
<h3 id="언어-설정-확인">언어 설정 확인</h3>
<p><code>locale</code> 명령어로 en_US.UTF-8로 잘 설정되었는지 확인한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/b4fc1f1b-8ab7-40eb-b267-01c6022af301/image.png" alt=""></p>
<p>혹시 로케일 설정이 잘못되었다면 <code>sudo dpkg-reconfigure locales</code> 명령어로 재설정할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/a0e7dbea-6aa4-4f25-a777-b2bee4276603/image.png" alt=""></p>
<h3 id="시간대-설정">시간대 설정</h3>
<p>서버 설치 과정에서 시간대 선택이 생략되었으므로 설치후 반드시 시간대 설정을 변경해야한다.</p>
<h4 id="1-date-명령어로-현재-서버-시간대-확인">1) <code>date</code> 명령어로 현재 서버 시간대 확인</h4>
<p>국제 표준 협정시계시(UTC)로 설정되어있음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/1c86fafa-aff6-49e3-aa5e-0f2c1e07cfde/image.png" alt=""></p>
<h4 id="2-timedatectl-set-timezone-명령으로-시간대-변경">2) <code>timedatectl set-timezone</code> 명령으로 시간대 변경</h4>
<p>우리나라에 해당하는 &#39;Asia/Seoul&#39;로 입력한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[0. 우분투(Ubuntu) 설치하기(2)]]></title>
            <link>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%ACUbuntu-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B02</link>
            <guid>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%ACUbuntu-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B02</guid>
            <pubDate>Sat, 26 Jul 2025 07:11:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>VMware 설치 주소 : <a href="https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion">https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion</a></li>
</ul>
</blockquote>
<ul>
<li>Ubuntu 설치 주소 : <a href="https://ubuntu.com/download/desktop">https://ubuntu.com/download/desktop</a></li>
<li>Ubuntu 설치 버전 : 24.04.2 LTS</li>
<li>Ubuntu 설치 환경<ul>
<li>VMware Workstation 16 Player</li>
</ul>
</li>
</ul>
<p>테스트와 실습 위주로 진행할 예정이고, 이후 원활한 화면 캡처를 위해 VMware에서 사용을 위해 VMware에 설치를 진행한다.
VMware가 이미 설치되었다는 전제 하에 Ubuntu 설치과정만 포스팅하였다.</p>
<h2 id="vmware에-ubuntu2404-설치">VMware에 Ubuntu24.04 설치</h2>
<h3 id="1-vmware-실행-및-가상머신-생성-선택">1. VMware 실행 및 가상머신 생성 선택</h3>
<p><strong>Create a New Virtual Machine</strong> 을 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/5feac700-415c-4494-ba74-44aa2c519651/image.png" alt=""></p>
<h3 id="2-하드웨어-용량-확인-및-사용자-설정">2. 하드웨어 용량 확인 및 사용자 설정</h3>
<h4 id="1-설치-할-iso-이미지-선택">1) 설치 할 iso 이미지 선택</h4>
<ul>
<li><strong>Installer disc image file</strong> 을 선택한 후 <strong>Browse</strong>를 클릭하여 내려받은 Ubuntu iso 파일을 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/94b42d4e-6e89-4ac4-9f84-97d69570c968/image.png" alt=""></li>
<li><strong>Next</strong> 를 클릭하여 다음 단계로 이동한다.</li>
</ul>
<h4 id="2-사용자-정보-세팅">2) 사용자 정보 세팅</h4>
<p>사용자 명, 패스워드를 설정 한 후 Next 버튼을 클릭한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/d5d7b7a0-6b99-4569-8582-67b6527353ba/image.png" alt=""></p>
<h4 id="3-서버-명-설정">3) 서버 명 설정</h4>
<p>VMware에 표기 될 서버 명과 설치 위치를 설정한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/9c1c5407-9a95-46d1-96a7-4c79b297a66d/image.png" alt=""></p>
<h4 id="4-저장공간-크기-설정">4) 저장공간 크기 설정</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/4e46b83c-30d3-4f1e-98e7-f6accf2e256f/image.png" alt=""></p>
<h4 id="5-설정-정보-확인-및-설치">5) 설정 정보 확인 및 설치</h4>
<p>설정된 정보가 맞는지 확인 후 <strong>Finish</strong>를 클릭하면 가상머신 환경이 구축된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/be3938cd-a624-480a-88f8-f3f4615f81e9/image.png" alt=""></p>
<h3 id="3-ubuntu-server-실행">3. Ubuntu Server 실행</h3>
<p>설치가 완료되면 자동으로 Ubuntu가 실행된다. 자동실행 되지 않는 경우 메인화면에서 Ubuntu를 선택하여 실행하면 된다.</p>
<h4 id="1-try-or-install-ubuntu-server-선택-및-언어-선택">1) Try or install Ubuntu Server 선택 및 언어 선택</h4>
<ul>
<li>한국어 미지원이므로 영어 선택 후 엔터를 누른다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/d6dbbb7e-4716-40ff-a1a6-d5942aac8747/image.png" alt=""></li>
</ul>
<h4 id="2-설치도구-업데이트-및-키보드-설정">2) 설치도구 업데이트 및 키보드 설정</h4>
<ul>
<li>인터넷이 연결된 경우 설치도구 업데이트 여부를 선택할 수 있다. 필자는 업데이트 없이 설치를 진행한다.</li>
<li>키보드 배열을 선택한다. Layout, Variant 모두 기본값인 <strong>&#39;English (US)&#39;</strong> 선택 후 <strong>&#39;Done&#39;</strong>을 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/172479ed-2475-4a68-977a-121d804978fb/image.png" alt=""></li>
</ul>
<h4 id="3-키보드-설정">3) 키보드 설정</h4>
<ul>
<li>사용중인 키보드 유형을 설정한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/17cf4d06-860f-46ac-82b3-eb124b59ae67/image.png" alt=""></li>
</ul>
<h4 id="4-와이파이-설정">4) 와이파이 설정</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/1ddab1a4-4fa6-4ecc-8faf-ef6bd1e8613d/image.png" alt=""></p>
<h4 id="5-우분투-설치">5) 우분투 설치</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/489811b4-cdc5-4e6e-85f0-cabeae1768f0/image.png" alt=""></p>
<h4 id="6-세부-설정">6) 세부 설정</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/742e080f-758b-4248-adb9-68e13f769478/image.png" alt=""></p>
<h4 id="7-세부-설정할-항목-선택">7) 세부 설정할 항목 선택</h4>
<p>기본 사양으로 선택하여 설정한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/1b2db750-0518-404c-b0b4-2029637d17bc/image.png" alt=""></p>
<h4 id="8-추가-프로그램-설정">8) 추가 프로그램 설정</h4>
<p>모두 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/42093409-efd9-4e3e-a98d-fbfe353ec283/image.png" alt=""></p>
<h4 id="9-설치-방법-선택">9) 설치 방법 선택</h4>
<p>디스크 지우고 우분투 설치를 선택한다. 듀얼부팅과 달리 가상환경이므로 이 방법을 선택해도 안전하다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/30721c1b-5c42-4d99-81c1-cc987c4357b4/image.png" alt=""></p>
<h4 id="10-계정-정보-설정">10) 계정 정보 설정</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/965f8bba-d709-4350-bdd7-7cb352680ed0/image.png" alt=""></p>
<h4 id="11-시간대-설정">11) 시간대 설정</h4>
<p>서울로 설정한다. 기본적으로 자동 서울 설정이 된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/9948ff36-ab90-4c28-a2a6-04b7d5b54252/image.png" alt=""></p>
<h4 id="12-시스템-재시작">12) 시스템 재시작</h4>
<p>설치 완료 시 시스템을 재시작한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[0. 우분투(Ubuntu) 설치하기(1)]]></title>
            <link>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%ACUbuntu-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B01</link>
            <guid>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%ACUbuntu-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B01</guid>
            <pubDate>Sat, 26 Jul 2025 06:15:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>Ubuntu 설치 주소 : <a href="https://ubuntu.com/download/desktop">https://ubuntu.com/download/desktop</a></li>
</ul>
</blockquote>
<ul>
<li>Ubuntu 설치 버전 : 24.04.2 LTS</li>
<li>Ubuntu 설치 환경<ul>
<li>OS : Window10</li>
<li>CPU : 2GHz 듀얼코어 프로세서</li>
<li>RAM : 8GB</li>
</ul>
</li>
</ul>
<p>지난 포스팅에 이어 우분투OS를 설치한다.
설치를 위한 사전 작업은 지난 포스팅을 참고하면 된다.</p>
<br>

<h3 id="4-우분투os-설치">4. 우분투OS 설치</h3>
<h4 id="1-bios-부팅-순서-변경">1) BIOS 부팅 순서 변경</h4>
<p>우분투OS 설치용 USB를 연결하고 컴퓨터를 재부팅하여 BIOS/UEFI 설정도구로 진입한다.
재부팅 시 로고 출력될 때 BIOS 진입 키를 연타하면 설정 페이지로 진입할 수 있다.</p>
<blockquote>
<p><strong>메인보드 제조사별 상세 BIOS 진입키</strong></p>
</blockquote>
<ul>
<li>HP 메인보드: F10</li>
<li>ASRock 메인보드: F2 or DEL</li>
<li>ASUS 메인보드:  F2 or DEL</li>
<li>Intel 메인보드: DEL</li>
<li>ECS(ELITEGROUP) 메인보드: DEL</li>
<li>SONY 메인보드: F2</li>
<li>LG, TG삼보, DELL 메인보드: F2 or DEL</li>
<li>Lenovo 메인보드: F1 (데스크탑), F2(노트북)</li>
<li>GIGABYTE 메인보드: F2 or DEL</li>
<li>Acer, Samsung 메인보드: F2 or DEL</li>
</ul>
<p>제조사별로 방법이 다르나, <strong>부팅 순서(boot order, boot sequence), 부팅 옵션(boot option), 부팅 우선순위(boot priority)와 같은 부팅 관련 메뉴</strong>를 찾아 <strong>USB를 가장 먼저 부팅하도록 변경</strong>한다.
부팅 순서를 바꾸었으면 변경한 내용을 저장하고 설정 도구를 종료 및 <strong>재부팅</strong>한다.</p>
<p>📌나중에 우분투 설치 끝난 이후에 <span style="color:red"><strong>USB를 제거</strong>하고 <strong>부팅 순서를 우분투가 설치된 저장 장치로 변경</strong></span>하여 부팅해야한다. </p>
<h4 id="2-우분투-설치">2) 우분투 설치</h4>
<ul>
<li>Install Ubuntu 선택</li>
<li>타 소프트웨어 설치 선택
인터넷이 연결되어 있고 엔비디아 지포스 그래픽 카드가 설치되어 있다면, Other Option 항목들을 모두 설치해주면 편하다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/fd2811a6-6e3a-4f00-a01a-9580a89b8818/image.png" alt=""></li>
<li>설치 형식에서 <strong>기타</strong>를 선택한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/70572a3d-e3d4-4266-9ad4-538803b44813/image.png" alt=""></li>
<li>앞 포스팅에서 우분투 설치를 위해 비워둔 미할당 영역에 우분투를 설치한다.<ul>
<li><strong>남은 공간</strong> 클릭</li>
<li>좌측 하단의 <strong>+ 버튼</strong> 클릭</li>
<li>파티션 종류 <strong>논리 파티션</strong> 선택</li>
<li>파티션 위치 <strong>이 공간이 시작하는 지점</strong></li>
<li>용도 <strong>EXT4 저널링 파일 시스템</strong></li>
<li>마운트 위치 <strong>/</strong> 로 설정</li>
<li><strong>OK 버튼</strong> 클릭
<img src="https://velog.velcdn.com/images/pride-marimo/post/b172c845-c86e-4abe-99a9-046637742144/image.png" alt=""></li>
</ul>
</li>
<li><strong>지금 설치</strong> 클릭하여 설치 진행 
📌 <strong>바뀐 점을 디스크에 쓰시겠습니까</strong> 라는 경고 창이 뜨면 <strong>계속</strong> 버튼을 눌러 진행한다.</li>
<li>지역 설정 : <strong>서울</strong>
<img src="https://velog.velcdn.com/images/pride-marimo/post/9948ff36-ab90-4c28-a2a6-04b7d5b54252/image.png" alt=""></li>
<li>로그인 계정 및 암호 설정
<img src="https://velog.velcdn.com/images/pride-marimo/post/965f8bba-d709-4350-bdd7-7cb352680ed0/image.png" alt="">
📌 우분투os로 부팅 시에 자동으로 바탕화면까지 진입하게 하려면,</li>
<li><em>자동으로 로그인*</em> 을 체크, 로그인 암호를 쳐야 바탕화면까지 진입하게 하려면, <strong>로그인할 때 암호 입력</strong> 을 체크한다.</li>
<li>설치 완료 시 <strong>지금 재시작</strong> 선택</li>
<li>듀얼 부팅 선택
검은 화면에서 부팅 할 OS를 선택할 수 있다.
Ubuntu로 실행 할 경우 Ubuntu를 선택하거나 10초이상 그대로 놓아두면 자동으로 Ubuntu로 실행된다.
Window로 실행 할 경우 하단의 Window Boot Manager를 선택하면 Window로 실행된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[0. 우분투(Ubuntu) 설치하기(0)]]></title>
            <link>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%ACUbuntu-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@pride-marimo/0.-%EC%9A%B0%EB%B6%84%ED%88%ACUbuntu-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 19 Jul 2025 07:45:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>Ubuntu 설치 주소 : <a href="https://ubuntu.com/download/desktop">https://ubuntu.com/download/desktop</a></li>
</ul>
</blockquote>
<ul>
<li>Ubuntu 설치 버전 : 24.04.2 LTS</li>
<li>Ubuntu 설치 환경<ul>
<li>OS : Window10</li>
<li>CPU : 2GHz 듀얼코어 프로세서</li>
<li>RAM : 8GB</li>
</ul>
</li>
</ul>
<h2 id="윈도우-pc에-ubuntu2404-듀얼부팅-설치">윈도우 PC에 Ubuntu24.04 듀얼부팅 설치</h2>
<h3 id="1-우분투-iso-이미지-다운로드">1. 우분투 iso 이미지 다운로드</h3>
<p><a href="https://ubuntu.com/download/desktop">https://ubuntu.com/download/desktop</a> 에서 download버튼을 클릭하여 iso 이미지를 내려받는다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/73a12f2a-b899-49c7-99f7-f43fd0e72814/image.png" alt=""></p>
<br/>

<h3 id="2-우분투-설치부팅-usb-디스크-만들기">2. 우분투 설치(부팅) usb 디스크 만들기</h3>
<p>과거에는 내려받은 iso를 CD나 DVD로 옮겨야 했으나, 현재는 USB에서 실행하게 할 수 있다.
우분투로 부팅 가능한 USB 저장 장치를 만들어보자.</p>
<h4 id="1-루퍼스rufus-설치">1) 루퍼스(Rufus) 설치</h4>
<p><a href="https://rufus.ie/ko/">https://rufus.ie/ko/</a> 에서 사양에 맞는 파일을 선택하여 내려받는다.
표준은 설치 방식이고 포터블은 무설치 방식으로 동작한다. 둘 다 작동상의 차이는 없으니 어떤 것을 받던 무방하다. 필자는 포터블 방식으로 받았다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/ddbabe94-c32c-4c15-aee5-7bd7f98f5cff/image.png" alt=""></p>
<h4 id="2-usb-설정">2) USB 설정</h4>
<ul>
<li>OS 설치를 위해 빈 usb를 PC에 연결한다.</li>
<li>내려받은 Rufus 프로그램을 실행한다.</li>
<li>프로그램에서 usb에 세팅할 부팅 디스크 속성을 설정한다.<ul>
<li>장치에서 연결한 usb를 선택한다.</li>
<li>부팅 선택 란에서 <strong>선택</strong> 버튼을 클릭하여 내려받은 우분투 데스크톱 iso 이미지를 선택한다.</li>
<li>파티션 방식을 <strong>GPT</strong>로 선택한다.</li>
<li><strong>시작</strong> 버튼을 클릭한다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/6145b076-681d-497c-adf1-4674cf8a01f4/image.png" alt=""></p>
<p>📌유의사항 : <span style="color:red">설치 환경의 펌웨어가 <strong>BIOS</strong>라면 파티션 방식을 <strong>MBR</strong>로 선택해야 한다.</span> 
파티션 방식을 잘못 선택하면 USB 저장 장치로 부팅할 수 없으며, 다시 루퍼스로 우분투 설치 매체를 생성해야 한다.</p>
<h4 id="3-iso-이미지-모드로-쓰기-선택-및-ok-버튼-클릭">3) ISO 이미지 모드로 쓰기 선택 및 OK 버튼 클릭</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/1639a2e6-6012-49bf-a5f5-4320bdda822e/image.png" alt=""></p>
<h4 id="4-설치-시작">4) 설치 시작</h4>
<p>설치가 시작되면 USB 저장 장치의 모든 자료가 삭제된다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/cc5146fa-e526-4e46-b655-2df35683df2b/image.png" alt="">
설치 완료 시 닫기 버튼을 클릭한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/e609849c-300b-42c7-91e1-1d952886ad7b/image.png" alt=""></p>
<h4 id="5-usb에-복사된-파일-확인">5) USB에 복사된 파일 확인</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/6759da4a-c221-4d33-b73e-185ab21f1367/image.png" alt=""></p>
<br/>

<h3 id="3-우분투-설치-공간-마련-파티션-조정">3. 우분투 설치 공간 마련 (파티션 조정)</h3>
<p>윈도우 디스크 관리 도구로 새로운 파티션을 생성하여 우분투 OS가 설치될 공간을 마련해보자.</p>
<h4 id="1-디스크-관리-도구-접근">1) 디스크 관리 도구 접근</h4>
<p><img src="https://velog.velcdn.com/images/pride-marimo/post/5b26ea20-717a-44d7-a7b7-dad74340fb71/image.png" alt=""></p>
<h4 id="2-볼륨-축소">2) 볼륨 축소</h4>
<p>줄이려는 볼륨(보통 C: 또는 주 파티션으로 가장 용량이 많은 파티션)을 마우스 우클릭 한 뒤, <strong>볼륨 축소</strong>를 클릭한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/e20673a7-b2b3-47ba-abd5-98e5f7a1af27/image.png" alt="">
지금 할당하는 공간에 우분투OS가 설치되고 우분투 환경의 저장공간으로 활용된다. 사용할 공간 만큼 축소한다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/8ec0c1bf-1527-440b-a427-08109aab28e5/image.png" alt="">
다음과 같이 할당되지 않은 공간이 생긴다.
<img src="https://velog.velcdn.com/images/pride-marimo/post/fa50412b-3174-4e35-ab22-c218c23f4609/image.png" alt=""></p>
<br/>

<p>우분투 설치를 위한 사전작업은 모두 완료되었다.
다음 포스팅에서는 실제 설치를 진행해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[16. SerialDate 리팩터링]]></title>
            <link>https://velog.io/@pride-marimo/16.-SerialDate-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81</link>
            <guid>https://velog.io/@pride-marimo/16.-SerialDate-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81</guid>
            <pubDate>Mon, 30 Jun 2025 08:43:53 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.jfree.org/jcommon/api/index.html">JCommon 라이브러리</a>
위 링크에서 JCommon 라이브러리를 제공한다. 이 중 <code>org.jfree.date</code>라는 패키지가 있는데, 이 중 <code>SerialDate</code>라는 클래스를 리팩터링 해본다.</p>
<p>SerialDate는 날짜를 표현하는 자바 클래스이다. 하지만 자바는 이미 <code>java.util.Date</code>, <code>java.util.Calendar</code>와 같은 클래스를 제공하는데, 왜 하필 SerialDate를 써야할까?
<br/></p>
<h3 id="첫째-돌려보자">첫째, 돌려보자</h3>
<p>클린코드 교재의 473쪽 B-2의 코드를 참고하고 진행한다.
우선, 클로버(코드커버리지 분석도구)를 통해 SerialDateTests 클래스를 검사한다. 검사결과 이 클래스는 50%정도만 테스트를 진행하고 있다는 사실을 확인할 수 있다. 따라서 더 높은 코드 커버리지를 위해 단위 테스트 케이스를 구현하고, 해당 단위 테스트에서 실패한 경우(주석 처리한 부분)를 성공시킬 수 있도록 원본 클래스를 리팩터링한다.</p>
<ul>
<li>23~63행 주석부분 통과하도록 수정<ul>
<li>SerialDate 설계 상 불통이 옳으나, 저자 기준 통과해야 함</li>
<li>대소문자 구분 없이 모두 통과해야할 것으로 추정 </li>
<li><blockquote>
<p>259행, 263행 <code>equalsIgnoreCase</code>로 변경</p>
</blockquote>
</li>
</ul>
</li>
<li>32행, 45행 주석 유지<ul>
<li>&#39;tues&#39;, &#39;thurs&#39; 약어 지원 여부 불분명</li>
</ul>
</li>
<li>153~154행 불통 -&gt; 통과해야함</li>
<li>163~213행 stringToMonthCode 메서드 수정<pre><code class="language-java">if  ((result &lt; 1) || result &gt; 12) {
  result = -1;
  for (int i = 0; i &lt; maonthNames.length; i++) {
      if (s.equalsIgnoreCase(shortMonthNames[i])) {
          result = i + 1;
          break;
      }
      if (s.equalsIgnoreCase(monthNames[i])) {
          result = i + 1;
          break;
      }
  }
}</code></pre>
</li>
<li>318행 : 672행의 <code>getFollowingDayOfWeek</code> 메서드 버그 표시됨<ul>
<li>메서드 버그 수정 <code>if(baserDOW &gt;= targetWeekday) {</code></li>
</ul>
</li>
<li>719행 실행 오류 : 718행 if문 항상 거짓으로 처리됨<ul>
<li>버그 수정<pre><code class="language-java">int delta = targetDOW = base.getDayOfWeek();
int positiveDelta = delta + 7;
int adjust = positiveDelta % 7;
if (adjust &gt; 3)
    adjust -= 7;
return SerialDate.addDays(adjust, base);</code></pre>
</li>
</ul>
</li>
<li>417행, 429행 : <code>weekInMonthToString</code>, <code>relativeToString</code> 오류 문자열 대신 <code>IllegalArgumentException</code>으로 테스트 통과 처리</li>
</ul>
<br/>

<h3 id="둘째-고쳐보자">둘째, 고쳐보자</h3>
<p>위와 같이 변경한 SerialDate는 모든 테스트 케이스를 통과한다. 이제 SerialDate를 올바로 고쳐보자. 코드 변경 시 마다 JCommon 단위테스트와 자체 단위테스트를 모두 실행하였다.</p>
<ul>
<li>초반 선언부 주석은 너무 오래되었다. 법적 정보()만 남겨두고 나머지는 제거한다.</li>
<li>enum을 모두 독자적인 소스 파일로 이동.</li>
<li>정적 변수(<code>dateFormatSymbols</code>)와 정적 메서드(<code>getMonthNames</code>, <code>isLeapYear</code>, <code>lastDayOfMonth</code>)는 새 클래스로 이동</li>
<li>일부 추상 메서드는 <code>DayDate.java</code>로 이동</li>
<li><code>Month.make</code>를 <code>Month.fromInt</code>로 변경. 다른 enum도 똑같이 변경하고 모든 enum에 toInt() 접근자를 생성, index 필드를 private로 정의.</li>
<li>plusYears와 plusMonths의 중복을 <code>correctLastDayOfMonth</code>라는 새 메서드를 통해 제거</li>
<li>사용하던 숫자 1 대신 모두 <code>Month.JANUARY.toInt()</code> 혹은 <code>Day.SUNDAY.toInt()</code>로 적절히 변경. SpreadsheetDate 코드를 살펴보고 알고리즘을 조금 손봤다.</li>
</ul>
<br/>

<h3 id="결론">결론</h3>
<p>위 처럼 수정 결과, DayDate 코드 커버리지는 84.9%로 감소하였다. 테스트하는 코드가 줄어서가 아니라 클래스 크기가 작아지는 바람에 테스트하지 않는 코드의 비중이 커졌기 때문이다. 테스트하지 않는 코드는 너무 사소해 테스트할 필요도 없는 코드만 남았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[15. JUnit 들여다보기]]></title>
            <link>https://velog.io/@pride-marimo/15.-JUnit-%EB%93%A4%EC%97%AC%EB%8B%A4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@pride-marimo/15.-JUnit-%EB%93%A4%EC%97%AC%EB%8B%A4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 06 Jun 2025 07:29:50 GMT</pubDate>
            <description><![CDATA[<p>JUnit은 자바 프레임워크 중 가장 유명하다. 여기서는 JUnit 프레임워크에서 가져온 코드를 리팩터링 해본다.</p>
<br/>

<h3 id="junit-프레임워크">JUnit 프레임워크</h3>
<p>JUnit의 다양한 모듈 중 ComparisonCompactor 모듈을 살펴보자. 문자열 비교 오류를 파악할 때 유용하다. ABCDE와 ABXDE를 받아 <code>&lt;...B[X]D...&gt;</code> 를 반환한다.</p>
<h4 id="테스트-케이스">테스트 케이스</h4>
<pre><code class="language-java">package junit.tests.framework;

import junit.framework.ComparisonCompactor;
import junit.framework.TestCase;

public class ComparisonCompactorTest extends TestCase {

    public void testMessage() {
        String failure = new ComparisonCompactor(0, &quot;b&quot;, &quot;c&quot;).compact(&quot;a&quot;);
        assertTrue(&quot;a expected:&lt;[b]&gt; but was:&lt;[c]&gt;&quot;.equals(failure));
    }

    public void testStartSame() {
        String failure = new ComparisonCompactor(1, &quot;ba&quot;, &quot;bc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;b[a]&gt; but was:&lt;b[c]&gt;&quot;, failure);
    }

    public void testEndSame() {
        String failure = new ComparisonCompactor(1, &quot;ab&quot;, &quot;cb&quot;).compact(null);
        assertEquals(&quot;expected:&lt;[a]b&gt; but was:&lt;[c]b&gt;&quot;, failure);
    }

    public void testSame() {
        String failure = new ComparisonCompactor(1, &quot;ab&quot;, &quot;ab&quot;).compact(null);
        assertEquals(&quot;expected:&lt;ab&gt; but was:&lt;ab&gt;&quot;, failure);
    }

    public void testNoContextStartAndEndSame() {
        String failure = new ComparisonCompactor(0, &quot;abc&quot;, &quot;adc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...[b]...&gt; but was:&lt;...[d]...&gt;&quot;, failure);
    }

    public void testStartAndEndContext() {
        String failure = new ComparisonCompactor(1, &quot;abc&quot;, &quot;adc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;a[b]c&gt; but was:&lt;a[d]c&gt;&quot;, failure);
    }

    public void testStartAndEndContextWithEllipses() {
        String failure = new ComparisonCompactor(1, &quot;abcde&quot;, &quot;abfde&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...b[c]d...&gt; but was:&lt;...b[f]d...&gt;&quot;, failure);
    }

    public void testComparisonErrorStartSameComplete() {
        String failure = new ComparisonCompactor(2, &quot;ab&quot;, &quot;abc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;ab[]&gt; but was:&lt;ab[c]&gt;&quot;, failure);
    }

    public void testComparisonErrorEndSameComplete() {
        String failure = new ComparisonCompactor(0, &quot;bc&quot;, &quot;abc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;[]...&gt; but was:&lt;[a]...&gt;&quot;, failure);
    }

    public void testComparisonErrorEndSameCompleteContext() {
        String failure = new ComparisonCompactor(2, &quot;bc&quot;, &quot;abc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;[]bc&gt; but was:&lt;[a]bc&gt;&quot;, failure);
    }

    public void testComparisonErrorOverlappingMatches() {
        String failure = new ComparisonCompactor(0, &quot;abc&quot;, &quot;abbc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...[]...&gt; but was:&lt;...[b]...&gt;&quot;, failure);
    }

    public void testComparisonErrorOverlappingMatchesContext() {
        String failure = new ComparisonCompactor(2, &quot;abc&quot;, &quot;abbc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;ab[]c&gt; but was:&lt;ab[b]c&gt;&quot;, failure);
    }

    public void testComparisonErrorOverlappingMatches2() {
        String failure = new ComparisonCompactor(0, &quot;abcdde&quot;, &quot;abcde&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...[d]...&gt; but was:&lt;...[]...&gt;&quot;, failure);
    }

    public void testComparisonErrorOverlappingMatches2Context() {
        String failure = new ComparisonCompactor(2, &quot;abcdde&quot;, &quot;abcde&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...cd[d]e&gt; but was:&lt;...cd[]e&gt;&quot;, failure);
    }

    public void testComparisonErrorWithActualNull() {
        String failure = new ComparisonCompactor(0, &quot;a&quot;, null).compact(null);
        assertEquals(&quot;expected:&lt;a&gt; but was:&lt;null&gt;&quot;, failure);
    }

    public void testComparisonErrorWithActualNullContext() {
        String failure = new ComparisonCompactor(2, &quot;a&quot;, null).compact(null);
        assertEquals(&quot;expected:&lt;a&gt; but was:&lt;null&gt;&quot;, failure);
    }

    public void testComparisonErrorWithExpectedNull() {
        String failure = new ComparisonCompactor(0, null, &quot;a&quot;).compact(null);
        assertEquals(&quot;expected:&lt;null&gt; but was:&lt;a&gt;&quot;, failure);
    }

    public void testComparisonErrorWithExpectedNullContext() {
        String failure = new ComparisonCompactor(2, null, &quot;a&quot;).compact(null);
        assertEquals(&quot;expected:&lt;null&gt; but was:&lt;a&gt;&quot;, failure);
    }

    public void testBug609972() {
        String failure = new ComparisonCompactor(10, &quot;S&amp;P500&quot;, &quot;0&quot;).compact(null);
        assertEquals(&quot;expected:&lt;[S&amp;P50]0&gt; but was:&lt;[]0&gt;&quot;, failure);
    }
}</code></pre>
<p>위와 같은 방식으로 테스트 케이스를 작성하여 ComparisonCompactor 모듈에 대한 코드 커버리지 분석을 수행하여 테스트케이스가 모든 행, 모든 if문, 모든 for문을 실행할 수 있도록 하였다. </p>
<br>

<h4 id="comparisoncompactorjava-원본">ComparisonCompactor.java 원본</h4>
<pre><code class="language-java">public class ComparisonCompactor {

    private static final String ELLIPSIS = &quot;...&quot;;
    private static final String DELTA_END = &quot;]&quot;;
    private static final String DELTA_START = &quot;[&quot;;

    private int fContextLength;
    private String fExpected;
    private String fActual;
    private int fPrefix;
    private int fSuffix;

    public ComparisonCompactor(int contextLength, String expected, String actual) {
        fContextLength = contextLength;
        fExpected = expected;
        fActual = actual;
    }

    public String compact(String message) {
        if (fExpected == null || fActual == null || areStringsEqual()) {
            return Assert.format(message, fExpected, fActual);
        }

        findCommonPrefix();
        findCommonSuffix();
        String expected = compactString(fExpected);
        String actual = compactString(fActual);
        return Assert.format(message, expected, actual);
    }

    private String compactString(String source) {
        String result = DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END;
        if (fPrefix &gt; 0) {
            result = computeCommonPrefix() + result;
        }
        if (fSuffix &gt; 0) {
            result = result + computeCommonSuffix();
        }
        return result;
    }

    private void findCommonPrefix() {
        fPrefix = 0;
        int end = Math.min(fExpected.length(), fActual.length());
        for (; fPrefix &lt; end; fPrefix++) {
            if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix)) {
                break;
            }
        }
    }

    private void findCommonSuffix() {
        int expectedSuffix = fExpected.length() - 1;
        int actualSuffix = fActual.length() - 1;
        for (; actualSuffix &gt;= fPrefix &amp;&amp; expectedSuffix &gt;= fPrefix; actualSuffix--, expectedSuffix--) {
            if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) {
                break;
            }
        }
        fSuffix = fExpected.length() - expectedSuffix;
    }

    private String computeCommonPrefix() {
        return (fPrefix &gt; fContextLength ? ELLIPSIS : &quot;&quot;) + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix);
    }

    private String computeCommonSuffix() {
        int end = Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length());
        return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 &lt; fExpected.length() - fContextLength ? ELLIPSIS : &quot;&quot;);
    }

    private boolean areStringsEqual() {
        return fExpected.equals(fActual);
    }
}</code></pre>
<br>

<h3 id="comparisoncompactorjava-리팩터링">ComparisonCompactor.java 리팩터링</h3>
<p>저자들이 아주 좋은 상태로 모듈을 남겨 두었지만 우리는 보이스카우트 규칙에 따라 처음 왔을 때 보다 더 깨끗하게 해 놓고 떠나야한다. 개선 방법들은 다음과 같다.</p>
<h4 id="접두어-f-제거n6">접두어 f 제거[N6]</h4>
<p>가장 먼저 거슬리는 부분은 멤버 변수 앞에 붙인 접두어 f다. 오늘날 사용하는 개발 환경에서는 이처럼 변수 이름에 범위를 명시할 필요가 없다.</p>
<pre><code class="language-java">private int contextLength;
private String expected;
private String actual;
private int prefix;
private int suffix;</code></pre>
<h4 id="조건문-캡슐화g28">조건문 캡슐화[G28]</h4>
<p>compact 함수 시작부에 캡슐화되지 않은 조건문이 있다.
의도를 명확히 표현하려면 조건문을 메서드로 뽑아내 적절한 이름을 붙여 캡슐화해야 한다.</p>
<pre><code class="language-java">//개선 전
public String compact(String message) {
    if (expected == null || actual == null || areStringsEqual()) {
        return Assert.format(message, expected, actual);
    }

    findCommonPrefix();
    findCommonSuffix();
    String expected = compactString(this.expected);
    String actual = compactString(this.actual);
    return Assert.format(message, expected, actual);
}</code></pre>
<pre><code class="language-java">//개선 후
public String compact(String message) {
    if (shouldNotCompact()) {
        return Assert.format(message, expected, actual);
    }

    findCommonPrefix();
    findCommonSuffix();
    String expected = compactString(this.expected);
    String actual = compactString(this.actual);
    return Assert.format(message, expected, actual);
}

private boolean shouldNotCompact() {
    return expected == null || actual == null || areStringsEqual();
}</code></pre>
<h4 id="명확한-이름-사용n4">명확한 이름 사용[N4]</h4>
<p>함수에 이미 expected라는 지역 변수가 있는데 fExpected에서 f를 빼버리는 바람에 this.expected라는 식으로 사용하게 되었다. 멤버변수와 이름이 같은 변수를 사용할 이유가 없다.</p>
<pre><code class="language-java">String compactExpected = compactString(expected);
String compactActual = compactString(actual);</code></pre>
<h4 id="부정-조건-미사용g29">부정 조건 미사용[G29]</h4>
<p>부정문은 긍정문보다 이해하기 약간 더 어렵다. 그러므로 첫 문장 if를 긍정으로 만들어 조건문을 반전한다.</p>
<pre><code class="language-java">public String compact(String message) {
    if (canBeCompacted()) {
        findCommonPrefix();
        findCommonSuffix();
        String compactExpected = compactString(expected);
        String compactActual = compactString(actual);
        return Assert.format(message, compactExpected, compactActual);
    } else {
        return Assert.format(message, expected, actual);
    }
}

private boolean canBeCompacted() {
    return expected != null &amp;&amp; actual != null &amp;&amp; !areStringsEqual();
}</code></pre>
<h4 id="이름으로-부수-효과-설명n7">이름으로 부수 효과 설명[N7]</h4>
<p>문자열을 압축하는 함수라지만 실제로 canBeCompacted가 false이면 압축하지 않는다. 그리고 단순한 압축 문자열이 아닌 형식이 갖춰진 문자열을 반환한다. 인수를 고려하면 가독성이 훨씬 좋아진다.</p>
<pre><code class="language-java">public String formatCompactedComparison(String message) {</code></pre>
<h4 id="함수는-한-가지-업무만-수행g30">함수는 한 가지 업무만 수행[G30]</h4>
<p>if 문 안에서 예상 문자열과 실제 문자열을 진짜로 압축한다.
이 부분을 분리하여 형식을 맞추는 작업은 formatCompactedComparison, 압축은 compacteExpectedAndActual이 수행하도록 변경한다.</p>
<pre><code class="language-java">...

private String compactExpected;
private String compactActual;

...

public String formatCompactedComparison(String message) {
    if (canBeCompacted()) {
        compactExpectedAndActual();
        return Assert.format(message, compactExpected, compactActual);
    } else {
        return Assert.format(message, expected, actual);
    }       
}

private compactExpectedAndActual() {
    findCommonPrefix();
    findCommonSuffix();
    compactExpected = compactString(expected);
    compactActual = compactString(actual);
}</code></pre>
<h4 id="일관적-함수-사용g11">일관적 함수 사용[G11]</h4>
<p>새 함수에서 마지막 두 줄은 변수를 반환하지만 첫째 줄과 둘째 줄은 반환값이 없다.
함수 사용 방식이 일관적이지 못하므로 findCommonPrefix와 findCommonSuffix를 변경해 접두어 값과 접미어 값을 반환한다.</p>
<pre><code class="language-javaprivate">    prefixIndex = findCommonPrefix();
    suffixIndex = findCommonSuffix();
    String compactExpected = compactString(expected);
    String compactActual = compactString(actual);
}

private int findCommonPrefix() {
    int prefixIndex = 0;
    int end = Math.min(expected.length(), actual.length());
    for (; prefixIndex &lt; end; prefixIndex++) {
        if (expected.charAt(prefixIndex) != actual.charAt(prefixIndex)) {
            break;
        }
    }
    return prefixIndex;
}

private int findCommonSuffix() {
    int expectedSuffix = expected.length() - 1;
    int actualSuffix = actual.length() - 1;
    for (; actualSuffix &gt;= prefixIndex &amp;&amp; expectedSuffix &gt;= prefix; actualSuffix--, expectedSuffix--) {
        if (expected.charAt(expectedSuffix) != actual.charAt(actualSuffix)) {
            break;
        }
    }
    return expected.length() - expectedSuffix;
}</code></pre>
<h4 id="정확한-이름-사용n1">정확한 이름 사용[N1]</h4>
<p>변수가 하는 일과 관련되는 정확한 이름으로 다시 명명한다.</p>
<pre><code class="language-java">private int prefixIndex;
private int suffixIndex;</code></pre>
<h4 id="숨겨진-시각적인-결합-없애기g31g32">숨겨진 시각적인 결합 없애기[G31][G32]</h4>
<p>공통되는 문자열을 앞에서부터 찾은 다음 뒤에서부터 찾도록 만들어져 있지만 호출자가 알기 어렵다. 따라서 다음과 같이 내용을 수정한다.</p>
<ul>
<li>findCommonPrefix 호출</li>
<li>findCommonSuffix 호출하는 메소드 생성</li>
<li>findCommonSuffixAndSuffix();<pre><code class="language-java">private void findCommonPrefixAndSuffix() {
  findCommonPrefix();
  int suffixLength = 1;
  for (; suffixOverlapsPrefix(suffixLength); suffixLength++) {
      if (charFromEnd(expected, suffixLength) != charFromEnd(actual, suffixLength)) {
          break;
      }
  }
  suffixIndex = suffixLength;
}
</code></pre>
</li>
</ul>
<p>private char charFromEnd(String s, int i) {
    return s.charAt(s.length() - i);
}</p>
<p>private boolean suffixOverlapsPrefix(int suffixLength) {
    return actual.length() = suffixLength &lt; prefixLength || expected.length() - suffixLength &lt; prefixLength;
}</p>
<pre><code>
#### 정확한 이름 사용[G33]
변수가 하는 일과 관련되는 정확한 이름으로 다시 명명한다.
```java
private int suffixLength;</code></pre><h4 id="중간-점검">중간 점검</h4>
<pre><code class="language-java">public class ComparisonCompactor {
    ...
    private int suffixLength;
    ...

    private void findCommonPrefixAndSuffix() {
        findCommonPrefix();
        suffixLength = 0;
        for (; suffixOverlapsPrefix(suffixLength); suffixLength++) {
            if (charFromEnd(expected, suffixLength) != charFromEnd(actual, suffixLength)) {
                break;
            }
        }
    }

    private char charFromEnd(String s, int i) {
        return s.charAt(s.length() - i - 1);
    }

    private boolean suffixOverlapsPrefix(int suffixLength) {
        return actual.length() = suffixLength &lt;= prefixLength || expected.length() - suffixLength &lt;= prefixLength;
    }

    ...
    private String compactString(String source) {
        String result = DELTA_START + source.substring(prefixLength, source.length() - suffixLength) + DELTA_END;
        if (prefixLength &gt; 0) {
            result = computeCommonPrefix() + result;
        }
        if (suffixLength &gt; 0) {
            result = result + computeCommonSuffix();
        }
        return result;
    }

    ...
    private String computeCommonSuffix() {
        int end = Math.min(expected.length() - suffixLength + contextLength, expected.length());
        return expected.substring(expected.length() - suffixLength, end) + (expected.length() - suffixLength &lt; expected.length() - contextLength ? ELLIPSIS : &quot;&quot;);
    }
}</code></pre>
<h4 id="불필요한-코드-제거g9">불필요한 코드 제거[G9]</h4>
<p>불필요한 if문들을 제거하고, compactString 구조를 다듬어 좀 더 깔끔하게 만든다.</p>
<pre><code class="language-java">private String compactString(String source) {
        return computeCommonPrefix() + DELTA_START +  source.substring(prefixLength, source.length() - suffixLength) + DELTA_END + computeCommonSuffix();
}</code></pre>
<br>

<h3 id="comparisoncompactorjava-최종본">ComparisonCompactor.java 최종본</h3>
<p>모듈은 일련의 분석 함수와 일련의 조합 함수로 나뉘고, 전체 함수는 위상적으로 정렬했으므로 각 함수가 사용된 직후에 정의된다. 분석 함수가 먼저 나오고 조합 함수가 그 뒤를 이어서 나온다.</p>
<pre><code class="language-java">public class ComparisonCompactor {

    private static final String ELLIPSIS = &quot;...&quot;;
    private static final String DELTA_END = &quot;]&quot;;
    private static final String DELTA_START = &quot;[&quot;;

    private int contextLength;
    private String expected;
    private String actual;
    private int prefixLength;
    private int suffixLength;

    public ComparisonCompactor(int contextLength, String expected, String actual) {
        this.contextLength = contextLength;
        this.expected = expected;
        this.actual = actual;
    }

    public String formatCompactedComparison(String message) {
        String compactExpected = expected;
        String compactactual = actual;
        if (shouldBeCompacted()) {
            findCommonPrefixAndSuffix();
            compactExpected = comapct(expected);
            compactActual = comapct(actual);
        }         
        return Assert.format(message, compactExpected, compactActual);      
    }

    private boolean shouldBeCompacted() {
        return !shouldNotBeCompacted();
    }

    private boolean shouldNotBeCompacted() {
        return expected == null &amp;&amp; actual == null &amp;&amp; expected.equals(actual);
    }

    private void findCommonPrefixAndSuffix() {
        findCommonPrefix();
        suffixLength = 0;
        for (; suffixOverlapsPrefix(suffixLength); suffixLength++) {
            if (charFromEnd(expected, suffixLength) != charFromEnd(actual, suffixLength)) {
                break;
            }
        }
    }

    private boolean suffixOverlapsPrefix(int suffixLength) {
        return actual.length() = suffixLength &lt;= prefixLength || expected.length() - suffixLength &lt;= prefixLength;
    }

    private void findCommonPrefix() {
        int prefixIndex = 0;
        int end = Math.min(expected.length(), actual.length());
        for (; prefixLength &lt; end; prefixLength++) {
            if (expected.charAt(prefixLength) != actual.charAt(prefixLength)) {
                break;
            }
        }
    }

    private String compact(String s) {
        return new StringBuilder()
            .append(startingEllipsis())
            .append(startingContext())
            .append(DELTA_START)
            .append(delta(s))
            .append(DELTA_END)
            .append(endingContext())
            .append(endingEllipsis())
            .toString();
    }

    private String startingEllipsis() {
        prefixIndex &gt; contextLength ? ELLIPSIS : &quot;&quot;
    }

    private String startingContext() {
        int contextStart = Math.max(0, prefixLength = contextLength);
        int contextEnd = prefixLength;
        return expected.substring(contextStart, contextEnd);
    }

    private String delta(String s) {
        int deltaStart = prefixLength;
        int deltaend = s.length() = suffixLength;
        return s.substring(deltaStart, deltaEnd);
    }

    private String endingContext() {
        int contextStart = expected.length() = suffixLength;
        int contextEnd = Math.min(contextStart + contextLength, expected.length());
        return expected.substring(contextStart, contextEnd);
    }

    private String endingEllipsis() {
        return (suffixLength &gt; contextLength ? ELLIPSIS : &quot;&quot;);
    }
}</code></pre>
<br>

<h3 id="결론">결론</h3>
<p>저자들은 우수한 모듈을 만들었다. 하지만 세상에 개선이 불필요한 모듈은 없다.
코드를 처음보다 조금 더 깨끗하게 만드는 책임은 우리 모두에게 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[14. 점진적인 개선]]></title>
            <link>https://velog.io/@pride-marimo/14.-%EC%A0%90%EC%A7%84%EC%A0%81%EC%9D%B8-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@pride-marimo/14.-%EC%A0%90%EC%A7%84%EC%A0%81%EC%9D%B8-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Sat, 24 May 2025 07:45:05 GMT</pubDate>
            <description><![CDATA[<p>출발은 좋았으나 확장성이 부족했던 모듈을 샘플로 하여 분석, 개선, 정리하는 과정을 통해 점진적으로 코드를 개선해본다. 여기서는 main 함수로 넘어오는 문자열을 분석하는 유틸리티를 Args를 구현한다.</p>
<br/>

<h4 id="간단한-args-사용법">간단한 Args 사용법</h4>
<p>Args 클래스는 (형식 또는 스키마 지정, 명령행 인수 배열) 형태로 선언한다.</p>
<ul>
<li><p>명령행 인수 정의 : 부울인수 l / 정수인수 p / 문자열인수 d</p>
<pre><code class="language-java">public static void main(String[] args) {
  try {
      Args arg = new Args(&quot;l,p#,d*&quot;, args);
      boolean logging = arg.getBoolean(&#39;l&#39;);
      int port = arg.getInt(&#39;p&#39;);
      String directory = arg.getString(&#39;d&#39;);
      executeApplication(logging, port, directory);

  } catch(ArgsException e) {
      System.out.printf(&quot;Argument error: %s\n&quot;, e.errorMessage());
  }
}</code></pre>
<br/>

</li>
</ul>
<h3 id="args-1차-초안">Args: 1차 초안</h3>
<p>돌아는 가지만, 상당히 길고 복잡하다. 인수 유형이 늘어날수록 코드가 지저분해지고 복잡해진다. </p>
<pre><code class="language-java">public class Args {
    private String schema;
    private String[] args;
    private boolean valid = true;
    private Set&lt;Character&gt; unexpectedArguments = new TreeSet&lt;Character&gt;();
    private Map&lt;Character, Boolean&gt; booleanArgs = new HashMap&lt;Character, Boolean&gt;();
    private int numberOfArguments = 0;

    public Args(String schema, String[] args) throws ParseException {
        this.schema = schema;
        this.args = args;
        valid = parse();
    }

    public boolean isValid() {
        return valid;
    }

    private boolean parse() throws ParseException {
        if(schema.length() == 0 &amp;&amp; args.length == 0)
            return true;
        parseSchema();
        parseArguments();
        return unexpectedArguments.size() == 0;
    }

    private boolean parseSchema() throws ParseException {
        for(String element : schema.split(&quot;,&quot;)) {
            parseSchemaElement(element);
        }
        return true;
    }

    private void parseSchemaElement(String element) throws ParseException {
        if(element.length() == 1) {
            parseBooleanSchemaElement(element);
        }
    }

    private void parseBooleanSchemaElement(char elementId) {
        char c = element.charAt(0);
        if(Character.isLetter(c)) {
            booleanArgs.put(c, false);
        }
    }

    private boolean parseArguments() {
        for (String arg : args) {
            parseArgument(arg);
        }
        return true;
    }

    private void parseArgument(String arg) {
        if(arg.startsWith(&quot;-&quot;))
            parseElements(arg);
    }

    private void parseElements(String arg) {
        for(ing i = 1; i &lt; arg.length(); i++)
            parseElement(arg.charAt(i));
    }

    private void parseElement(char argChar) {
        if(isBoolean(argChar)) {
            numberOfArguments++;
            setBooleanArg(argChar, true);
        } else
            unexpectedArguments.add(argChar);
    }

    private void setBooleanArg(char argChar, boolean value) {
        booleanArgs.put(argChar, value);
    }

    private boolean isBoolean(char argChar) {
        return booleanArgs.containKey(argChar);
    }

    public int cardinality() {
        return numberOfArguments;
    }

    public String usage() {
        if(schema.length)( &gt; 0)
            return &quot;-[&quot;+schema+&quot;]&quot;;
        else
            return &quot;&quot;;
    }

    public String errorMessage() {
        if(unexpectedArguments.size() &gt; 0) {
            return unexpectedArgumentMessage();
        } else
            return &quot;&quot;;
    }

    private String unexpectedArgumentMessage() {
        StringBuffer message = new StringBuffer(&quot;Argument(s) -&quot;);
        for (char c : unexpectedArguments) {
            message.append(c);
        }
        message.append(&quot; unexpected.&quot;);

        return message.toString();
    }

    public boolean getBoolean(char arg) {
        return booleanArgs.get(arg);
    }

}</code></pre>
<p>위 코드는 크게 세 가지 기능을 제공한다.</p>
<ul>
<li>인수 유형에 해당하는 HashMap을 선택하기 위해 스키마 요소의 구문 분석</li>
<li>명령행 인수에서 인수 유형을 분석해 진짜 유형으로 변환</li>
<li>getXXX 메서드를 구현해 호출자에게 진짜 유형을 반환</li>
</ul>
<p>인수 유형은 다양하지만 모두 유사한 메서드를 제공하므로 한 클래스에 담는것이 적합하다. 그래서 ArgumentMarshaler를 생성하였다.</p>
<h3 id="점진적-개선">점진적 개선</h3>
<ul>
<li><p>기존 코드에 ArgumentMarshaler 클래스 골격 추가</p>
<pre><code class="language-java">private class ArgumentMarshaler {
  private boolean booleanVlaue = false;

  public void setBoolean(boolean value) {
      booleanVlaue = value;
  }

  public boolean getBoolean() {return booleanValue;}
}
</code></pre>
</li>
</ul>
<p>private class BooleanArgumentMarshaler extends ArgumentMarshaler {
}</p>
<p>private class StringArgumentMarshaler extends ArgumentMarshaler {
}</p>
<p>private class IntegerArgumentMarshaler extends ArgumentMarshaler {
}</p>
<pre><code>* 구체적으로 인수를 저장하는 HashMap에서 Boolean -&gt; ArgumentMarshaler로 인수 유형 변경
```java
private Map&lt;Character, ArgumentMarshaler&gt; booleanArgs = new HashMap&lt;Character, ArgumentMarshaler&gt;();</code></pre><ul>
<li>깨지는 코드 수정<pre><code class="language-java">...
  private void parseBooleanSchemaElement(char elementId) {
      booleanArgs.put(elementId, new BooleanArgumentMarshaler());
  }
..
  private void setBooleanArg(char argChar, boolean value) {
      booleanArgs.get(argChar).setBoolean(value);
  }
...
  private boolean getBoolean(char arg) {
      return falseIfNumm(booleanArgs.get(arg).getBoolean());
  }</code></pre>
</li>
<li>테스트 케이스 가동 및 오류 수정<pre><code class="language-java">  public boolean getBoolean(char arg) {
      Args.ArgumentMarshaler am = booleanArgs.get(arg);
      return am != null &amp;&amp; am.getBoolean();
  }</code></pre>
</li>
<li>같은 방법으로 String, int 등 인수 유형 추가 (모든 논리를 ArgumentMarshaler로 이동)</li>
<li>파생 클래스 생성으로 기능 분산</li>
</ul>
<h4 id="추상-메서드-화">추상 메서드 화</h4>
<ul>
<li><p>setBoolean 함수 BooleanArgumentMarshaler로 옮긴 후 정상 호출 확인</p>
</li>
<li><p>ArgumentMarshaler 클래스에 추상 메서드 set 생성</p>
<pre><code class="language-java">private abstract class ArgumentMarshaler {
  protected boolean booleanValue = false;
  private String stringValue;
  private int integerValue;

  public void setBoolean(boolean value) {
      booleanValue = value;
  }

  public boolean getBoolean() {
      return booleanValue;
  }

  public abstract void set(String s);
}</code></pre>
<blockquote>
<p><em>추상 set 함수는 String 인수를 받아들이나 BooleanArgumentMarshaler는 인수를 사용하지 않는다. 여기서 인수를 정의한 이유는 StringArgumentMarshaler와 IntegerArgumentMarshaler에서 필요하기 때문이다.</em></p>
</blockquote>
</li>
<li><p>BooleanArgumentMarshaler 클래스에 set 메서드 구현</p>
<pre><code class="language-java">private class BooleanArgumentMarshaler extends ArgumentMarshaler {
  public void set(String s) {
      booleanValue = true;
  }
}</code></pre>
</li>
<li><p>setBoolean 호출을 set 호출로 변경</p>
<pre><code class="language-java">private void setBooleanArg(char argChar, boolean value) {
  booleanArgs.get(argChar).set(&quot;true&quot;);
}</code></pre>
</li>
<li><p>ArgumentMarshaler에서 setBoolean 메서드 제거</p>
</li>
<li><p>get 메서드 BooleanArgumentMarshaler로 이동</p>
<pre><code class="language-java">public boolean getBoolean(char arg) {
  Args.ArgumentMarshaler am = booleanArgs.get(arg);
  return am != null &amp;&amp; (Boolean)am.get();
}</code></pre>
</li>
<li><p>위 코드 컴파일을 위해 ArgumentMarshaler에 get 함수 추가</p>
<pre><code class="language-java">private abstract class ArgumentMarshaler {
  ...
  public Object get() {
      return null;
  }
}</code></pre>
</li>
<li><p>테스트 실패로 ArgumentMarshaler에 get 추상메서드로 변경 및 BooleanArgumentMarshaler에 get 구현</p>
<pre><code class="language-java">private abstract class ArgumentMarshaler {
  protected boolean booleanValue = false;
  ...

  public abstract Object get();
}
</code></pre>
</li>
</ul>
<p>private class BooleanArgumentMarshaler extends ArgumentMarshaler {
    public void set(String s) {
        booleanValue = true;
    }</p>
<pre><code>public Object get() {
    return booleanValue;
}</code></pre><p>}</p>
<p>```</p>
<ul>
<li>ArgumentMarshaler에서 getBoolean 함수 제거</li>
<li>ArgumentMarshaler의 booleanValue를 BooleanArgumentMarshaler로 내리고 private 변수로 선언</li>
<li>같은 방법으로 string, int 실행</li>
</ul>
<br>

<h3 id="결론">결론</h3>
<p>그저 돌아가는 코드만으로는 부족하다. 돌아가는 코드가 심하게 망가지는 사례는 흔하다. 단순히 돌아가는 코드에 만족하지 말고 처음부터 코드를 최대한 깔끔하고 단순하게 정리하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[3. JWT 발급, 인증/인가 로직 구현]]></title>
            <link>https://velog.io/@pride-marimo/3.-JWT-%EB%B0%9C%EA%B8%89-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@pride-marimo/3.-JWT-%EB%B0%9C%EA%B8%89-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Sat, 26 Apr 2025 06:20:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 시리즈에서는 JPA 예시 코드를 기반으로, 실제 운영 환경에서 각기 다른 도메인끼리의 통신을 가정한 RESTful API로 디벨롭한다. 
<span style="color:gray;"><em>* 기본 웹 프로젝트 세팅 완료된 상태에서 시작, 테스트는 POSTMAN 활용</em></span></p>
</blockquote>
<h4 id="디벨롭-단계">디벨롭 단계</h4>
<ul>
<li><strong>Interceptor 생성 및 적용</strong></li>
<li><strong>API Key 검증 로직 구현</strong></li>
<li><span style='background-color: #fff5b1'><strong>JWT 발급, 인증/인가 로직 구현</strong></span><ul>
<li>서버용 JWT 발급 로직 구현</li>
<li>서버 인터셉터 내 검증 구현<ul>
<li>JWT의 형식 검증</li>
<li>JWT의 서명(Signature) 검증</li>
<li>클레임(예: iss, exp, scope) 검증.</li>
</ul>
</li>
<li>클라이언트 용 인증 로직 구현</li>
</ul>
</li>
<li><strong>OAuth 2.0 기반 인증/인가 로직 구현</strong><ul>
<li>클라이언트 용 Access Token 발급 로직 구현</li>
<li>서버 인터셉터 내 검증 구현<ul>
<li>Access Token의 형식 검증</li>
<li>인증 서버에 검증 요청(옵션, 원격 검증)</li>
<li>Access Token의 클레임(예: scope, exp) 검증</li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>
이전에 작성한 api key 검증 로직의 일부를 수정하여 JWT를 활용하는 로직을 구현한다. 이 파트에서는 spring-security를 활용한다.

<h3 id="서버-측-코드-수정">서버 측 코드 수정</h3>
<h4 id="gradle-의존성-추가">Gradle 의존성 추가</h4>
<pre><code class="language-jsx">dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;
    implementation &#39;io.jsonwebtoken:jjwt-api:0.11.5&#39;    
    runtimeOnly   &#39;io.jsonwebtoken:jjwt-impl:0.11.5&#39;  
    runtimeOnly   &#39;io.jsonwebtoken:jjwt-jackson:0.11.5&#39;
}</code></pre>
<h4 id="jwtutil-생성">JwtUtil 생성</h4>
<pre><code class="language-java">@Component
public class JwtUtil {
    @Value(&quot;${jwt.secret}&quot;)
    private String secretKey;
    @Value(&quot;${jwt.expiration-ms}&quot;)
    private long expirationMs;

    public String generateToken(UserDetails user) {
        Map&lt;String, Object&gt; claims = new HashMap&lt;&gt;();
        claims.put(&quot;roles&quot;, user.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority).toList());

        return Jwts.builder()
            .setIssuer(&quot;my-company&quot;)   // issuer 클레임 설정, iss(발급자) 설정
            .setSubject(user.getUsername())
            .claim(&quot;roles&quot;, user.getAuthorities().stream()
                    .map(GrantedAuthority::getAuthority).toList())
            .claim(&quot;scope&quot;, &quot;read write&quot;)    // scope 클레임 설정, scope(권한 범위) 설정
            .setIssuedAt(now)
            .setExpiration(new Date(now.getTime() + expirationMs))
            .signWith(Keys.hmacShaKeyFor(secretKey.getBytes()),
                      SignatureAlgorithm.HS256)
            .compact();
    }

    public String extractUsername(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secretKey.getBytes())
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean validateToken(String token, UserDetails user) {
        Claims claims = Jwts.parserBuilder()
            .setSigningKey(secretKey.getBytes())
            .build()
            .parseClaimsJws(token)
            .getBody();

        boolean subjectOk   = claims.getSubject().equals(user.getUsername());
        boolean notExpired  = claims.getExpiration().after(new Date());
        boolean issuerOk    = &quot;my-company&quot;.equals(claims.getIssuer());          // iss(발급자) 검증
        boolean scopeOk     = Arrays.stream(claims.get(&quot;scope&quot;, String.class)
                                         .split(&quot; &quot;))
                                  .collect(Collectors.toSet())
                                  .containsAll(/* requiredScopes */);         // scope(권한 범위) 검증

        return subjectOk &amp;&amp; notExpired &amp;&amp; issuerOk &amp;&amp; scopeOk;
}
</code></pre>
<h4 id="userdetailsservice-구현-클래스-생성">UserDetailsService 구현 클래스 생성</h4>
<pre><code class="language-java">@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final MemberRepository memberRepository; // JPA 리포 사용

    public CustomUserDetailsService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) {
        Member member = memberRepository.findByUsername(username)
            .orElseThrow(() -&gt; new UsernameNotFoundException(&quot;User not found: &quot; + username));
        return org.springframework.security.core.userdetails.User.builder()
            .username(member.getUsername())
            .password(member.getPassword())
            .roles(member.getRoles().toArray(new String[0]))
            .build();
    }
}
</code></pre>
<h4 id="인터셉터-prehandle-수정">인터셉터 preHandle 수정</h4>
<pre><code class="language-java">@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    private final JwtUtil jwtUtil;         
    private final UserDetailsService userDetailsService;

    public AuthenticationInterceptor(JwtUtil jwtUtil,
                                     UserDetailsService userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String header = request.getHeader(&quot;Authorization&quot;);
        if (header == null || !header.startsWith(&quot;Bearer &quot;)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write(&quot;Unauthorized&quot;);
            return false;
        }
        String token = header.substring(7);
        String username = jwtUtil.extractUsername(token);
        if (username != null &amp;&amp; SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails user = userDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateToken(token, user)) {
                UsernamePasswordAuthenticationToken auth =
                  new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(auth);
                return true;
            }
        }
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.getWriter().write(&quot;Invalid or expired token&quot;);
        return false;
    }

    ...
}</code></pre>
<br/>

<h3 id="클라이언트-측-코드-수정">클라이언트 측 코드 수정</h3>
<pre><code class="language-java">public class ApiClient {
    private final RestTemplate rest;
    private String jwt; 

    public ApiClient(RestTemplateBuilder b) {
        this.rest = b.build();
    }

    public void login(String user, String pass) {
        AuthRequest req = new AuthRequest(user, pass);
        AuthResponse resp = rest.postForObject(&quot;/api/auth/login&quot;, req, AuthResponse.class);
        this.jwt = resp.getToken();
    }

    public &lt;T&gt; T get(String url, Class&lt;T&gt; cls) {
        HttpHeaders h = new HttpHeaders();
        h.setBearerAuth(jwt);
        HttpEntity&lt;?&gt; e = new HttpEntity&lt;&gt;(h);
        return rest.exchange(url, GET, e, cls).getBody();
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[13장. 동시성]]></title>
            <link>https://velog.io/@pride-marimo/13%EC%9E%A5.-%EB%8F%99%EC%8B%9C%EC%84%B1</link>
            <guid>https://velog.io/@pride-marimo/13%EC%9E%A5.-%EB%8F%99%EC%8B%9C%EC%84%B1</guid>
            <pubDate>Sun, 30 Mar 2025 06:05:56 GMT</pubDate>
            <description><![CDATA[<p>동시성과 깔끔한 코드는 양립하기 아주 어렵다. 스레드를 하나만 실행하는 코드는 짜기 쉬우나, 시스템이 부하를 받기도 쉽다.
이 장에서는 여러 스레드를 동시에 돌리는 이유와 어려움, 이러한 어려움에 대처하고 깨끗한 코드를 작성하는 방법, 동시성을 테스트하는 방법과 문제점을 논한다.</p>
<br/>

<h3 id="동시성이-필요한-이유">동시성이 필요한 이유</h3>
<p><span style='background-color: #fff5b1'>동시성은 <strong>무엇과 언제를 분리</strong>하는, 결합을 없애는 전략이다.</span> </p>
<p>다음은 반드시 동시성이 필요한 상황이다.</p>
<ul>
<li>구조적 관점: 프로그램은 거대한 루프 하나가 아닌, 작은 협력 프로그램 여럿으로 보이게 되어 시스템을 이해하기 쉽고, 문제를 분리하기도 쉽다.</li>
<li>응답시간과 작업처리량 개선의 경우 동시성 구현 필요 
_<span style="color:gray">ex) 웹 정보 수집기 -&gt; 한번에 한 사이트만 방문하는 경우 </span>_</li>
<li>정보를 대량으로 분석하는 시스템</li>
</ul>
<h4 id="미신과-오해">미신과 오해</h4>
<ul>
<li><strong>동시성은 항상 성능을 높여준다. (X)</strong>
동시성은 대기시간이 아주 길어 여러 스레드가 프로세서를 공유할 수 있거나, 여러 프로세서가 동시에 처리할 독립적인 계산이 충분히 많은 경우에만 성능이 높아진다.</li>
<li><strong>동시성을 구현해도 설계는 변하지 않는다. (X)</strong>
단일 스레드 시스템과 다중 스레드 시스템은 설계가 판이하게 다르다. 일반적으로 무엇과 언제를 분리하면 시스템 구조가 크게 달라진다.</li>
<li><strong>웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다. (X)</strong>
컨테이너가 어떻게 동작하는지, 어떻게 동시수정, 데드락 등과 같은 문제를 피할 수 있는지를 알아야만 한다.</li>
<li><strong>동시성은 다소 부하를 유발한다. (O)</strong>
성능 측면에서 부하가 걸리며, 코드도 더 짜야한다.</li>
<li><strong>동시성은 복잡하다. (O)</strong></li>
<li><strong>일반적으로 동시성 버그는 재현하기 어렵다. (O)</strong>
따라서 진짜 결함으로 간주되지 않고 일회성 문제로 여겨 무시하기 쉽다.</li>
<li><strong>동시성을 구현하려면 흔히 근본적인 설계 전략을 재고해야 한다. (O)</strong></li>
</ul>
<br/>

<h3 id="난관">난관</h3>
<p>동시성을 구현하기 어려운 이유는 무엇일까? 다음 예시에서 알아보자.</p>
<pre><code class="language-java">public class X {
    private int lastIdUsed;

    public int getNextId() {
        return ++lastIdUsed;
    }
}</code></pre>
<p>위 코드에서 인스턴스 X를 생성하고 lastIdUsed필드를 42로 설정한 다음, 두 스레드가 해당 인스턴스를 공유한다. 이제 두 스레드가 getNextId();를 호출하는 경우 결과는 다음 셋 중 하나이다.</p>
<ul>
<li>한 스레드는 43을 받는다. 다른 스레드는 44를 받는다. lastIdUsed는 44가 된다.</li>
<li>한 스레드는 44을 받는다. 다른 스레드는 43를 받는다. lastIdUsed는 44가 된다.</li>
<li>한 스레드는 43을 받는다. 다른 스레드는 43를 받는다. lastIdUsed는 43가 된다.
두 스레드가 같은 변수를 동시에 참조하는 경우 자바 코드 한 줄을 거쳐가는 경로는 수없이 많은데, 그 중 일부 경로가 세 번째와 같은 잘못된 결과를 내놓을 수 있다.</li>
</ul>
<br/>

<h3 id="동시성-방어-원칙">동시성 방어 원칙</h3>
<p>동시성 코드가 일으키는 문제로부터 시스템을 방어하는 원칙과 기술을 소개한다.</p>
<h4 id="단일-책임-원칙-_span-stylecolorgraysingle-responsibility-principle-srpspan_">단일 책임 원칙 _<span style="color:gray">Single Responsibility Principle, SRP</span>_</h4>
<p><span style='background-color: #fff5b1'><strong>주어진 메서드/클래스/컴포넌트를 변경할 이유는 하나여야 한다.</strong></span>
동시성 복잡성 하나만으로도 따로 분리할 이유가 충분하다. 동시성 관련 코드는 다른 코드와 분리해야 한다.</p>
<blockquote>
<p><strong>동시성 구현 시 고려할 점</strong></p>
</blockquote>
<ul>
<li>동시성 코드는 독자적인 개발, 변경, 조율 주기가 있다.</li>
<li>동시성 코드에는 독자적인 난관이 있다. 다른 코드에서 겪는 난관과 다르며 훨씬 어렵다.</li>
<li>잘못 구현한 동시성 코드는 별의별 방식으로 실패한다. 주변에 있는 다른 코드가 발목을 잡지 않더라도 동시성 하나만으로도 충분히 어렵다.
<span style='background-color: #fff5b1'><strong>동시성 코드는 다른 코드와 분리하라</strong></span></li>
</ul>
<h4 id="따름-정리-자료-범위를-제한하라">따름 정리: 자료 범위를 제한하라</h4>
<p>앞선 예시처럼, 객체 하나를 공유한 후 동일 필드를 수정하던 두 스레드가 서로 간섭하므로 예상치 못한 결과를 내놓는다. 이러한 문제의 해결 방안으로 <span style='background-color: #fff5b1'><strong>공유 객체를 사용하는 코드 내 임계영역을 synchronized 키워드로 보호</strong>하라고 권장한다.**</span></p>
<blockquote>
<p><strong>공유 자료를 수정하는 위치가 많은 경우 발생할 수 있는 가능성</strong></p>
</blockquote>
<ul>
<li>보호할 임계영역을 빼먹는다. 그래서 공유자료를 수정하는 모든 코드를 망가뜨린다.</li>
<li>모든 임계영역을 올바르게 보호했는지 확인하느라 똑같은 노력과 수고를 반복한다.</li>
<li>그렇지않아도 찾아내기 어려운 버그가 더욱 찾기 어려워진다.
<span style='background-color: #fff5b1'><strong>자료를 캡슐화 하라. 공유 자료를 최대한 줄여라.</strong></span></li>
</ul>
<h4 id="따름-정리-자료-사본을-사용하라">따름 정리: 자료 사본을 사용하라</h4>
<p>공유 자료를 줄이려면 처음부터 공유하지 않는 방법이 제일 좋다. 객체를 복사해 읽기 전용으로 사용하거나 각 스레드가 객체를 복사해 사용한 후 한 스레드가 해당 사본에서 결과를 가져오는 방법도 가능하다. </p>
<h4 id="따름-정리-스레드는-가능한-독립적으로-구현하라">따름 정리: 스레드는 가능한 독립적으로 구현하라</h4>
<p>다른 스레드와 자료를 공유하지 않는다. 각 스레드는 클라이언트 욫청 하나를 처리한다. 모든 정보는 비공유 출처에서 가져오며 로컬 변수에 저장한다. 그러면 다른 스레드와 동기화 할 필요가 없어, 독립적으로 돌아간다.
<span style='background-color: #fff5b1'><strong>독자적인 스레드로, 가능하면 다른 프로세서에서, 돌려도 괜찮도록 자료를 독립적인 단위로 분할하라</strong></span></p>
<br/>

<h3 id="라이브러리를-이해하라">라이브러리를 이해하라</h3>
<ul>
<li>스레드 환경에 안전한 컬렉션을 사용한다. 자바 5부터 제공한다.</li>
<li>서로 무관한 작업을 수행할 때는 executor 프레임워크를 사용한다.</li>
<li>가능하다면 스레드가 차단되지 않는 방법을 사용한다.</li>
<li>일부 클래스 라이브러리는 스레드에 안전하지 못하다.<h4 id="스레드-환경에-안전한-컬렉션">스레드 환경에 안전한 컬렉션</h4>
<code>java.util.concurrent</code> 패키지가 제공하는 클래스는 다중 스레드 환경에서 사용해도 안전하며 성능도 좋다. 실제로 ConcurrentHashMap은 거의 모든 상황에서 HashMap보다 빠르다. 동시 읽기/쓰기를 지원하며, 보통 다중 스레드 환경에서 문제가 생기는 자주 사용하는 복합 연산을 다중 스레드 상에서 안전하게 만든 메서드로 제공한다. 
<span style='background-color: #fff5b1'>*<em>언어가 제공하는 클래스를 검토하라. 자바에서는 <code>java.util.concurrent</code>, <code>java.util.concurrent.atomic</code>, <code>java.util.concurrent.locks</code> 를 익혀라 *</em></span></li>
</ul>
<br/>

<h3 id="실행-모델을-이해하라">실행 모델을 이해하라</h3>
<p>다중 스레드 애플리케이션을 분류하는 방식은 여러 가지이다.</p>
<table>
<thead>
<tr>
<th align="left">용어명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left">한정된 자원<br>(Bound Resource)</td>
<td>다중 스레드 환경에서 사용하는 자원으로, 크기난 숫자가 제한적이다. 데이터베이스 연결, 길이가 일정한 읽기/쓰기 버프 등이 예다.</td>
</tr>
<tr>
<td align="left">상호 배제<br>(Mutual Exclusion)</td>
<td>한 번에 한 스레드만 공유 자료나 공유 자원을 사용할 수 있는 경우</td>
</tr>
<tr>
<td align="left">기아<br>(Starvation)</td>
<td>한 스레드나 여러 스레드가 굉장히 오랫동안 혹은 영원히 자원을 기다린다. 예를 들어, 항상 짧은 스레드에게 우선순위를 준다면, 짧은 스레드가 지속적으로 이어질 경우, 긴 스레드가 기아 상태에 빠진다.</td>
</tr>
<tr>
<td align="left">데드락<br>(Deadlock)</td>
<td>여러 스레드가 서로 끝나기를 기다린다. 모든 스레드가 각기 필요한 자원을 다른 ㅅ그레드가 점유하는 바람에 어느 쪽도 더이상 진행하지 못한다.</td>
</tr>
<tr>
<td align="left">라이브락<br>(Livelock)</td>
<td>락을 거는 단계에서 각 스레드가 서로를 방해한다. 스레드는 계속해서 진행하려 하지만, 공명(resonance)으로 인해 굉장히 오랫동안 혹은 영원히 진행하지 못한다.</td>
</tr>
<tr>
<td align="left">다음 기본 알고리즘과 각 해법을 이해하라.</td>
<td></td>
</tr>
<tr>
<td align="left">#### 생산자-소비자</td>
<td></td>
</tr>
<tr>
<td align="left">하나 이상 생산자 스레드가 정보를 생성해 버퍼나 대기열에 넣는다. 하나 이상 소비자 스레드가 대기열에서 정보를 가져와 사용한다. 생산자 스레드와 소비자 스레드가 사용하는 대기열은 한정된 자원이다. 생산자 스레드는 대기열에 빈 공간이 있어야 정보를 채우므로, 빈 공간이 생길 때까지 기다린다. 소비자 스레드는 대기열에 정보가 있어야 가져오므로, 정보가 채워질 때 까지 기다린다. 서로 시그널을 보내며 대기열을 사용하는데, 잘못하면 생산자와 소비자 스레드 둘 다 진행 가능함에도 불구하고 동시에 서로에게서 시그널을 기다릴 가능성이 존재한다.</td>
<td></td>
</tr>
<tr>
<td align="left">#### 읽기-쓰기</td>
<td></td>
</tr>
<tr>
<td align="left">읽기 스레드를 위한 주된 정보원으로 공유 자원을 사용하지만, 쓰기 스레드가 이 공유자원을 이따금 갱신하는 경우, 처리율이 문제의 핵심이 된다. 처리율을 강조하면 기아 현상이 생기거나 오래된 정보가 쌓이고, 갱신을 허용하면 처리율에 영향을 미친다. 양쪽 균형을 잡으면서 동시 갱신 문제를 피하는 해법이 필요하다.</td>
<td></td>
</tr>
<tr>
<td align="left">#### 식사하는 철학자들</td>
<td></td>
</tr>
<tr>
<td align="left">둥근 식탁에 철학자 한 무리가 둘러앉았다. 각 철학자의 왼쪽에는 포크, 식탁 가운데는 스파게티 한 접시가 있다. 철학자들은 배가 고프지 않으면 생각하며 시간을 보내고, 배가 고프면 양손에 포크를 쥐고 먹는다. 양손에 포크를 쥐지 않으면 먹지 못한다. 좌측 또는 우측 철학자가 사용중인 경우 포크가 나올 때 까지 기다려야 하며, 먹고 나면 포크를 내려놓고 배가 고플 때까지 다시 생각에 잠긴다.</td>
<td></td>
</tr>
<tr>
<td align="left">여기서 철학자를 스레드로, 포크를 자원으로 바꿔 생각하면 된다. 주의해서 설계하지 않으면 데드락, 라이브락, 처리율 저하, 효율성 저하 등을 겪는다.</td>
<td></td>
</tr>
</tbody></table>
<br/>

<h3 id="동기화하는-메서드-사이에-존재하는-의존성을-이해하라">동기화하는 메서드 사이에 존재하는 의존성을 이해하라</h3>
<p>공유 객체 하나에는 메서드 하나만 사용하라. 
공유 객체 하나에 여러 메서드가 필요한 경우, 다음 세 가지 방법을 고려한다.</p>
<ul>
<li><strong>클라이언트에서 잠금</strong> : 클라이언트에서 첫 번째 메서드를 호출하기 전 서버를 잠근다. 마지막 메서드를 호출할 때까지 잠금을 유지한다.</li>
<li><strong>서버에서 잠금</strong> : 서버에 <code>서버를 잠그고 모든 메서드를 호출한 후 잠금을 해제하는</code> 메서드를 구현한다. 클라이언트는 이 메서드를 호출한다.</li>
<li><strong>연결서버</strong> : 잠금을 수행하는 중간 단계를 생성한다. &#39;서버에서 잠금&#39;방식과 유사하지만 원래 서버는 변경하지 않는다.</li>
</ul>
<br/>

<h3 id="동기화하는-부분을-작게-만들어라">동기화하는 부분을 작게 만들어라</h3>
<p>동기화하는 부분을 최대한 작게 만들어라.
자바에서 synchronized 키워드를 사용하면 락을 설정한다. 같은 락으로 감싼 모든 코드 영역은 한 번에 한 스레드만 실행이 가능하다. 락은 스레드를 지연시키고 부하를 가중시키므로 여기저기서 synchronized를 남발하는 코드는 바람직하지 않다. 반면, 임계영역은 반드시 보호해야 한다. 따라서, 코드를 짤 때는 임계영역 수를 최대한 줄여야 한다.</p>
<br/>

<h3 id="올바른-종료-코드는-구현하기-어렵다">올바른 종료 코드는 구현하기 어렵다</h3>
<p>영구적으로 돌아가는 시스템을 구현하는 방법과 잠시 둘다 깔끔하게 종료하는 시스템을 구현하는 방법은 다르다. 깔끔하게 종료하는 코드는 올바로 구현하기 어렵다. 가장 흔히 발생하는 문제가 데드락이다. 즉, 스레드가 절대 오지 않을 시그널을 기다린다.
<span style='background-color: #fff5b1'><strong>종료 코드를 개발 초기부터 고민하고 동작하게 초기부터 구현하라.</strong> 생각보다 어려우므로 이미 나온 알고리즘을 검토하라.</span></p>
<br/>

<h3 id="스레드-코드-테스트하기">스레드 코드 테스트하기</h3>
<p><span style='background-color: #fff5b1'>문제를 노출하는 테스트 케이스를 작성하라.</span> 프로그램 설정과 시스템 설정과 부하를 바꿔가며 자주 돌려라. 테스트가 실패하면 원인을 추적하라. 다시 돌렸더니 통과하더라는 이유로 그냥 넘어가면 절대로 안된다.
고려할 사항이 아주 많다는 뜻이다. 아래 몇 가지 구체적인 지침을 제시한다.</p>
<h4 id="말이-안되는-실패는-잠정적인-스레드-문제로-취급하라">말이 안되는 실패는 잠정적인 스레드 문제로 취급하라.</h4>
<p><span style='background-color: #fff5b1'><strong>시스템 실패를 &#39;일회성&#39;이라 치부하지 마라</strong></span>
다중 스레드 코드는 때때로 말이 안되는 오류를 일으킨다. 스레드 코드에 잠입한 버그는 실패를 재현하기가 아주 어려워 일회성 문제로 치부하고 무시한다. 일회성 문제를 계속 무시한다면 잘못된 코드 위에 코드가 계속 쌓인다.</p>
<h4 id="다중-스레드를-고려하지-순차-코드부터-제대로-돌게-만들자">다중 스레드를 고려하지 순차 코드부터 제대로 돌게 만들자.</h4>
<p>스레드 환경 밖에서 생기는 버그와 스레드 환경에서 생기는 버그를 동시에 디버깅 하지 마라. <span style='background-color: #fff5b1'><strong>먼저 스레드 환경 밖에서 코드를 올바로 돌려라.</strong></span></p>
<h4 id="다중-스레드를-쓰는-코드-부분을-다양한-환경에-쉽게-끼워-넣을-수-있도록-스레드-코드를-구현하라">다중 스레드를 쓰는 코드 부분을 다양한 환경에 쉽게 끼워 넣을 수 있도록 스레드 코드를 구현하라.</h4>
<p>다중 스레드를 쓰는 코드를 다양한 설정으로 실행하기 쉽게 구현하라.</p>
<ul>
<li>한 스레드로 실행하거나, 여러 스레드로 실행하거나, 실행 중 스레드 수를 바꿔본다.</li>
<li>스레드 코드를 실제 환경이나 테스트 환경에서 돌려본다.</li>
<li>테스트 코드를 빨리, 천천히, 다양한 속도로 돌려본다. </li>
<li>반복 테스트가 가능하도록 테스트 케이스를 작성한다.
<span style='background-color: #fff5b1'><strong>다양한 설정에서 실행할 목적으로 다른 환경에 쉽게 끼워 넣을 수 있게 코드를 구현하라</strong></span></li>
</ul>
<h4 id="다중-스레드를-쓰는-코드-부분을-상황에-맞춰-조정할-수-있게-작성하라">다중 스레드를 쓰는 코드 부분을 상황에 맞춰 조정할 수 있게 작성하라.</h4>
<p>스레드 개수를 조율하기 쉽게 코드를 구현한다. 프로그램이 돌아가는 도중에 스레드 개수를 변경하는 방법도 고려한다. 프로그램 처리율과 효율에 따라 스스로 스레드 개수를 조율하는 코드도 고민한다.</p>
<h4 id="프로세서-수보다-많은-스레드를-돌려보라">프로세서 수보다 많은 스레드를 돌려보라.</h4>
<p>시스템이 스레드를 스와핑할때도 문제가 발생한다. 스와핑을 일으키려면 프로세서 수보다 많은 스레드를 돌린다. 스와핑이 잦을수록 임계영역을 빼먹은 코드나 데드락을 일으키는 코드를 찾기 쉬워진다.</p>
<h4 id="다른-플랫폼에서-돌려보라">다른 플랫폼에서 돌려보라.</h4>
<p>다중 스레드 코드는 플랫폼에 따라 다르게 돌아간다. 따라서 코드가 돌아갈 가능성이 있는 플랫폼 전부에서 테스트를 수행해야 마땅하다.
<span style='background-color: #fff5b1'><strong>처음부터 그리고 자주 모든 목표 플랫폼에서 코드를 돌려라.</strong></span></p>
<h4 id="코드에-보조-코드를-넣어-돌려라-강제로-실패를-일으키게-해보라">코드에 보조 코드를 넣어 돌려라. 강제로 실패를 일으키게 해보라.</h4>
<p>흔히 스레드 코드는 오류를 찾기가 쉽지 않다. 간단한 테스트로는 버그가 드러나지 않는다.
스레드 버그가 산발적, 우발적, 재현이 어려운 이유는 코드가 실행되는 수천가지 경로 중 아주 소수만 실패하기 때문이다. 즉, 실패하는 경로가 실행될 확률은 극도로 저조하다.
<span style='background-color: #fff5b1'>이렇게 드물게 발생하는 오류를 좀 더 자주 일으키기 위해 <strong>보조 코드를 추가해 코드가 실행되는 순서를 바꿔준다.</strong></span>
각 메서드는 스레드가 실행되는 순서에 영향을 미친다. 가능한 초반, 가능한 자주 실패하는 편이 좋다. 
코드에 보조코드를 추가하는 방법은 두 가지이다.</p>
<ul>
<li><p><strong>직접 구현하기</strong>
코드에 직접 wait(), sleep(), yield(), priority() 함수를 추가한다. 특별히 까다로운 코드를 테스트할 때 적합하다. 다만 이 방법에는 다음과 같은 문제가 있다.</p>
<ul>
<li>보조 코드를 삽입할 적정 위치 직접 찾아야 함</li>
<li>어떤 함수를 어디서 호출해야 적당한 지 어떻게 알까?</li>
<li>배포 환경에 보조 코드를 그대로 남겨두면 프로그램 성능 하향</li>
<li>무작위적. 오류가 드러나지 않을 확률이 더 높다.</li>
</ul>
</li>
<li><p><strong>자동화</strong>
보조 코드를 자동으로 추가하려면 AOF, CGLIB, ASM 등과 같은 도구를 사용한다.
<span style='background-color: #fff5b1'><strong>흔들기 기법을 사용해 오류를 찾아내라.</strong></span> 코드를 흔드는 이유는 스레드를 매번 다른 순서로 실행하기 위해서다. 좋은 테스트 케이스와 흔들기 기법은 오류가 드러날 확률을 크게 높여준다.</p>
</li>
</ul>
<br/>

<h3 id="결론">결론</h3>
<p>다중 스레드 코드는 올바로 구현하기 어렵다. 다중 스레드 코드를 작성한다면 각별히 깨끗하게 코드를 짜야한다.</p>
<ul>
<li>SRP를 준수한다. 스레드 코드는 최대한 집약되고 작아야 한다.</li>
<li>동시성 오류를 일으키는 잠정적인 원인을 철저히 이해한다.</li>
<li>사용하는 라이브러리와 기본 알고리즘을 이해한다.</li>
<li>보호할 코드 영역을 찾아내는 방법과 특정 코드 영역을 잠그는 방법을 이해한다.</li>
<li>스레드 코드는 많은 플랫폼에서 많은 설정으로 반복해서 계속 테스트해야 한다.</li>
<li>스레드 코드는 출시 전까지 최대한 오랫동안 돌려봐야 한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[12장. 창발성]]></title>
            <link>https://velog.io/@pride-marimo/12%EC%9E%A5.-%EC%B0%BD%EB%B0%9C%EC%84%B1</link>
            <guid>https://velog.io/@pride-marimo/12%EC%9E%A5.-%EC%B0%BD%EB%B0%9C%EC%84%B1</guid>
            <pubDate>Sun, 16 Mar 2025 06:23:42 GMT</pubDate>
            <description><![CDATA[<p>이 장에서는 창발적 설계로 깔끔한 코드를 구현하는 것에 대해 알아본다.</p>
<blockquote>
<h4 id="창발성이란">창발성이란?</h4>
</blockquote>
<ul>
<li>작은 요소들이 모였을 때, 개별 요소에서는 볼 수 없는 새로운 특성이나 행동이 나타나는 현상</li>
<li>복잡한 시스템에서 자발적으로 새로운 질서나 패턴이 생기는 것</li>
<li>프로그램 개발에서는 <span style='background-color: #fff5b1'><strong>여러 단순한 모듈, 함수, 알고리즘들이 상호작용하면서, 설계자가 의도하지 않았거나 예상하지 못한 복잡한 동작이나 시스템 특성이 자연스럽게 나타나는 것</strong></span></li>
</ul>
<p>착실하게 따르기만 하면 우수한 설계가 나오는 간단한 규칙 네 가지가 있다. 다음은 해당 규칙을 중요도 순으로 나열하였다.</p>
<ul>
<li>모든 테스트를 실행한다.</li>
<li>중복을 없앤다.</li>
<li>프로그래머 의도를 표현한다.</li>
<li>클래스와 메서드 수를 최소로 줄인다.</li>
</ul>
<br/>

<h3 id="1-모든-테스트를-실행하라">1. 모든 테스트를 실행하라</h3>
<p>테스트 케이스를 만들고 계속 테스트하라는 규칙을 따르면 시스템은 낮은 결합도와 높은 응집력이라는, 객체지향방법론이 지향하는 목표를 저절로 달성한다. <span style='background-color: #fff5b1'><strong>테스트 케이스를 작성하면 설계 품질이 높아진다.</strong></span></p>
<h4 id="테스트-가능한-시스템">테스트 가능한 시스템</h4>
<ul>
<li>테스트를 철저히 거쳐 모든 테스트 케이스를 항상 통과하는 시스템</li>
<li>테스트 가능한 시스템 만들기<ul>
<li>작은 크기, 한 가지 목적만 수행하는 클래스 생성<ul>
<li>SRP(단일책임원칙) 준수하는 클래스는 테스트가 쉽다.</li>
</ul>
</li>
<li>테스트 케이스가 많을수록 개발자는 테스트가 쉽도록 코드 작성<ul>
<li>DIP(의존관계역전원칙) 적용</li>
<li>의존성 주입, 인터페이스, 추상화 등의 도구를 사용해 결합도 낮춤</li>
<li>설계 품질 향상</li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>

<h3 id="24-리팩터링">2~4. 리팩터링</h3>
<p>테스트 케이스 작성 후 코드와 클래스를 정리한다. 구체적으로, 코드를 점진적으로 리팩터링한다. 테스트 케이스가 있으니 코드를 정리하며 시스템이 깨질 걱정을 할 필요는 없다.</p>
<h4 id="리팩터링-단계">리팩터링 단계</h4>
<ul>
<li>소프트웨어 설계 품질 높이는 기법 적용 <ul>
<li>응집도 높이기</li>
<li>결합도 낮추기</li>
<li>관심사 분리하기</li>
<li>시스템 관심사 모듈로 나누기</li>
<li>함수와 클래스 크기 줄이기</li>
<li>더 나은 이름 선택하기 등 </li>
</ul>
</li>
<li>중복 제거</li>
<li>프로그래머 의도 표현</li>
<li>클래스, 메서드 수 최소로 줄이기</li>
</ul>
<h3 id="2-중복을-없애라">2. 중복을 없애라</h3>
<p>비슷한 코드는 더 비슷하게 고쳐주면 리팩터링이 쉬워진다.
아래 예시에서는 두 메서드에서 일부 코드가 동일하다.</p>
<pre><code class="language-java">public void scaleToOneDimension(
    float desiredDimension, float imageDimension) {
    if (Math.abs(desiredDimension - imageDimension) &lt; errorThreshold)
        return;
    float scalingFactor = desiredDimension / imageDimension;
    scalingFactor = (float) (Math.floor(scalingFactor * 100) * 0.01f);

    RenderedOp newImage = ImageUtilities.getScaledImage(
        image, scalingFactor, scalingFactor);
    image.dispose();
    System.gc();
    image = newImage;
}

public synchronized void rotate(int degrees) {
    RenderedOp newImage = ImageUtilities.getRotatedImage(
        image, degrees);
    image.dispose();
    System.gc();
    image = newImage;
}</code></pre>
<p>위 코드에서 동일한 코드를 정리해 중복을 제거한다.</p>
<pre><code class="language-java">public void scaleToOneDimension(
    float desiredDimension, float imageDimension) {
    if (Math.abs(desiredDimension - imageDimension) &lt; errorThreshold)
        return;
    float scalingFactor = desiredDimension / imageDimension;
    scalingFactor = (float) (Math.floor(scalingFactor * 100) * 0.01f);
    replaceImage(ImageUtilities.getScaledImage(image, scalingFactor, scalingFactor));
}

public synchronized void rotate(int degrees) {
    replaceImage(ImageUtilities.getScaledImage(image, degrees));
}

private void relplaceImage(RenderedOp newImage) {
    image.dispose();
    System.gc();
    image = newImage;
}</code></pre>
<p>중복 제거 후에는 클래스가 SRP를 위반한다. 그러므로 새로 만든 relplaceImage메서드를 다른 클래스로 옮긴다. 그러면 새 메서드의 가시성이 높아지고, 추가적인 추상화를 통해 다른 맥락에서 재사용 역시 가능하다. 이러한 <span style='background-color: #fff5b1'><strong>소규모 재사용</strong>은 시스템 복잡도를 극적으로 줄여준다.</span> 소규모 재사용을 제대로 익히면 대규모 재사용이 가능하다.</p>
<br/>

<h3 id="3-표현하라">3. 표현하라</h3>
<p>코드는 개발자의 의도를 분명히 표현해야 한다. 개발자가 코드를 명백하게 짤수록 다른 사람이 그 코드를 이해하기 쉬워지며, 결함이 줄어들고 유지보수 비용이 줄어든다.</p>
<ul>
<li><strong>좋은 이름 선택</strong> : 이름과 기능을 일치하도록 이름을 정한다.</li>
<li><strong>함수와 클래스 크기를 가능한 줄이기</strong> : 작은 클래스와 작은 함수는 이름 짓기도 쉽고, 구현하기도 쉽고, 이해하기도 쉽다.</li>
<li><strong>표준 명칭 사용</strong> : 클래스가 command나 visitor와 같은 표준 패턴을 사용해 구현된다면 클래스 이름에 패턴 이름을 넣어준다. 그러면 다른 개발자가 클래스 설계 의도를 이해하기 쉬워진다.</li>
<li><strong>단위 테스트 케이스 꼼꼼히 작성</strong> : 테스트 케이스는 예제로 보여주는 문서이다. 잘 만든 테스트 케이스를 읽어보면 클래스 기능이 한눈에 들어온다.</li>
</ul>
<br/>

<h3 id="4-클래스와-메서드-수를-최소로-줄여라">4. 클래스와 메서드 수를 최소로 줄여라</h3>
<p>중복 제거, 의도 표현, SRP 준수라는 기본 개념도 극단으로 치달으면 득보다 실이 많다. 
함수와 클래스 크기는 작게 유지하면서, 시스템 크기도 작게 유지하는 것이 궁극적인 목표이다. <span style='background-color: #fff5b1'>그러므로 클래스와 함수 수를 줄이는 작업도 중요하지만, <strong>테스트 케이스를 만들고 중복을 제거하고 의도를 표현하는 작업이 더 중요</strong>하다.</span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[11장. 시스템]]></title>
            <link>https://velog.io/@pride-marimo/11%EC%9E%A5.-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@pride-marimo/11%EC%9E%A5.-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Sat, 15 Feb 2025 08:37:49 GMT</pubDate>
            <description><![CDATA[<p>이 장에서는 시스템 수준에서도 깨끗한 코드를 유지하는 방법에 대해 알아보자.
<br/></p>
<h3 id="시스템-제작과-시스템-사용의-분리">시스템 제작과 시스템 사용의 분리</h3>
<blockquote>
<p><span style='background-color: #fff5b1'><strong>소프트웨어 시스템은</strong></span> 애플리케이션 객체를 제작하고 의존성을 서로 연결하는 <span style='background-color: #fff5b1'><strong>준비 과정과</strong></span>, 준비 과정 이후에 이어지는 <span style='background-color: #fff5b1'><strong>런타임 로직을 분리</strong>해야 한다.</span></p>
</blockquote>
<h4 id="초기화-지연계산-지연">초기화 지연(계산 지연)</h4>
<p>준비 과정 코드와 런타임 로직이 뒤섞인 기법이다. 전형적인 코드는 다음과 같</p>
<pre><code class="language-java">public Service getService() {
    if (service == null)
        service = new MyServiceImpl(...);    //모든 상황에 적합한 기본값인지 확인 필요
    return service;
}</code></pre>
<ul>
<li><strong>장점</strong><ul>
<li>실제로 필요시 까지 객체 미생성 -&gt; 불필요한 부하 X -&gt; 애플리케이션 시작 시간 단축</li>
<li>널포인터 반환X</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>getService메서드가 MyServiceImpl과 생성자 인수에 명시적으로 의존</li>
<li>런타임 로직에서 MyServiceImpl 객체를 미사용하더라도 의존성 해결 전까지 컴파일 불가</li>
<li>MyServiceImpl가 무거운 경우 테스트 시 단일 책임 원칙 미달성</li>
<li>MyServiceImpl이 모든 상황에 적합한 객체인지 판별 불가</li>
</ul>
</li>
</ul>
<p>체계적이고 탄탄한 시스템을 위해서는 모듈성을 깨서는 안된다. 설정 논리는 일반 실행 논리와 분리해야 모듈성이 높아지며, 주요 의존성을 해결하기 위한 일관적인 방식도 사용해야 한다.</p>
<br/>

<h4 id="main-분리"><em>Main 분리</em></h4>
<p>시스템의 생성과 사용을 분리하는 방법. <span style='background-color: #fff5b1'><strong>생성 관련 코드는 모두 main이나 main이 호출하는 모듈로</strong></span> 옮기면, main 호출 시 객체가 자동적으로 생성된다. 따라서 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다. 애플리케이션(나머지 시스템)은 그저 객체를 사용하기만 하면 된다.</p>
<h4 id="팩토리"><em>팩토리</em></h4>
<p><span style='background-color: #fff5b1'><strong>ABSTRACT FACTORY</strong></span>를 활용하면 객체 생성 시점은 애플리케이션이 결정하지만, 객체를 생성하는 코드는 애플리케이션이 알지 못한다. 이 경우도 모든 의존성이 main에서 애플리케이션으로 향하지만, 애플리케이션은 객체가 생성되는 시점을 완벽히 통제한다. 필요한 경우 특정 애플리케이션에서만 사용하는 생성자 인수도 넘길 수 있다.</p>
<h4 id="의존성-주입"><em>의존성 주입</em></h4>
<p>의존성 주입은 제어 역전 기법을 의존성 관리에 적용한 매커니즘이다.</p>
<ul>
<li>제어 역전: 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘긴다. 새로운 객체는 넘겨받은 책임만 수행하므로 단일 책임 원칙을 지킨다.</li>
</ul>
<p>의존성 관리 맥락에서 객체는 의존성 자체를 인스턴스로 만드는 책임은 지지 않는 대신, 이 책임을 다른 전담 매커니즘에 넘기는 방식으로 제어를 역전한다.
진정한 의존성 주입은 여기서 더 발전시켜, <span style='background-color: #fff5b1'>클래스가 의존성을 해결하려 시도하지 않는 대신, <strong>설정자 메서드나 생성자 인수를 제공</strong>한다.</span></p>
<br/>

<h3 id="확장">확장</h3>
<p>테스트 주도 개발 <em>(TDD)</em>, 리팩토링, 깨끗한 코드는 코드 수준에서 시스템을 조정하고 확장하기 쉽게 만든다. 하지만 시스템은 단순한 아키텍처를 복잡한 아키텍처로 조금씩 키우기는 어렵다. 다만, 소프트웨어 시스템은 물리적인 시스템과 달리, 관심사 별로 적절히 분리해 관리한다면 점진적으로 발전시킬 수 있다.</p>
<h4 id="횡단cross-cutting-관심사">횡단(cross-cutting) 관심사</h4>
<p>영속성과 같은 관심사는 애플리케이션의 자연스러운 객체 경계를 넘나드는 경향이 있다. 원론적으로는 모듈화, 캡슐화 된 방식으로 영속성 방식을 구상할 수 있지만, 현실적으로는 영속성 방식을 구현한 코드가 온갖 객체로 흩어진다. 이 때, 횡단 관심사를 활용하면 도메인 논리도 독자적으로 모듈화 할 수 있다.
<span style='background-color: #fff5b1'><strong>AOP</strong>는 횡단 관심사에 대처해 <strong>모듈성을 확보</strong>하는 일반적인 방법론이다.</span></p>
<ul>
<li>프로그래머가 영속적으로 저장할 객체와 속성 선언</li>
<li>영속성 책임을 영속성 프레임워크에 위임</li>
<li>AOP프레임워크가 대상 코드에 영향을 미치지 않는 상태로 동작방식 변경</li>
</ul>
<br/>

<p><strong>아래에서 자바에서 사용하는 관점 혹은 관점과 유사한 매커니즘 세 개를 알아본다.</strong></p>
<h4 id="자바-프록시"><em>자바 프록시</em></h4>
<p><span style='background-color: #fff5b1'>개별 객체나 클래스에서 메서드 호출을 감싸는 것처럼 <strong>단순한 상황에 적합</strong>하다.</span> 다만, JDK에서 제공하는 <strong>동적 프록시는 인터페이스만 지원</strong>한다. 클래스 프록시 사용을 위해서는 CGLIB, ASM, Javassist등의 바이트 코드 처리 라이브러리가 필요하다.</p>
<ul>
<li>단점<ul>
<li>코드의 양이 방대하고 복잡하여 깨끗한 코드를 사용하기 어렵다.</li>
<li>시스템 단위로 실행 지점을 명시하는 매커니즘을 제공하지 않는다.</li>
</ul>
</li>
</ul>
<br/>

<h4 id="순수-자바-aop-프레임워크"><em>순수 자바 AOP 프레임워크</em></h4>
<p>대부분의 프록시 코드는 비슷하여 도구로 자동화할 수 있다. 순수 자바 관점을 구현하는 스프링 AOP, JBoss AOP등 여러 자바 프레임워크는 내부적으로 프록시를 사용한다.</p>
<ul>
<li>프로그래머는 설정 파일이나 API를 사용해 필수적인 애플리케이션 기반 구조 구현<ul>
<li>영속성, 트랜잭션, 보안, 캐시, 장애조치 등 횡단 관심사 포함</li>
<li>대다수 실제 스프링이나 JBoss 라이브러리 관점 명시<ul>
<li><span style='background-color: #fff5b1'>이때 <strong>프레임워크가 사용자 모르게 프록시 or 바이트코드 라이브러리 활용</strong>해 이를 구현</span></li>
</ul>
</li>
<li>이러한 선언들이 요청에 따라 주요 객체를 생성하고 서로 연결하는 등 DI 컨테이너의 구체적인 동작 제어</li>
</ul>
</li>
</ul>
<br/>

<h4 id="aspectj-관점"><em>AspectJ 관점</em></h4>
<p><span style='background-color: #fff5b1'>AspectJ는 <strong>언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장</strong>으로,  관심사를 관점으로 분리하는 가장 강력한 도구이다. </span></p>
<ul>
<li>장점 : 관점을 분리하는 강력하고 풍부한 도구 집합 제공</li>
<li>단점 : 새 도구를 사용하고 새 언어 문법과 사용법을 익혀야 한다</li>
</ul>
<br/>

<h3 id="테스트-주도-시스템-아키텍처-구축">테스트 주도 시스템 아키텍처 구축</h3>
<p>최선의 시스템 구조는 각기 POJO<span style='color: gray;'><em>(Plain Old Java Object: 순수한 객체)</em></span> 또는 다른 객체로 구현되는 모듈화된 관심사 영역(도메인)으로 구성된다. 이렇게 <span style='background-color: #fff5b1'>서로 다른 영역은 해당 영역 코드에 최소한의 영향을 미치는 관점이나 유사한 도구를 사용해 통합</span>한다. 이런 구조 역시 코드와 마찬가지로 테스트 주도 기법을 적용할 수 있다.</p>
<br/>

<h3 id="의사-결정을-최적화-하라">의사 결정을 최적화 하라</h3>
<p>관심사를 모듈로 분리한 POJO 시스템은 기민함을 제공한다. 이러한 기민함 덕에 <span style='background-color: #fff5b1'>최신 정보에 기반해 최선의 시점에 최적의 결정을 내리기가 쉬워진다. 또한 결정의 복잡성도 줄어든다.</span></p>
<br/>

<h3 id="명백한-가치가-있을-때-표준을-현명하게-사용하라">명백한 가치가 있을 때 표준을 현명하게 사용하라</h3>
<p>표준을 사용하면 아이디어와 컴포넌트를 재사용하기 쉽고, 적절한 경험을 가진 사람을 구하기 쉬우며, 좋은 아이디어를 캡슐화하기 쉽고, 컴포넌트를 엮기 쉽다. 하지만 <span style='background-color: #fff5b1'>때로는 표준을 만드는 시간이 너무 오래 걸려 업계가 기다리지 못한다.</span> 어떤 표준은 원래 표준을 제정한 목적을 잊어버리기도 한다.</p>
<br/>

<h3 id="시스템은-도메인-특화-언어가-필요하다">시스템은 도메인 특화 언어가 필요하다</h3>
<p>도메인 특화 언어<span style='color: gray;'><em>(Domain-Specific Language, DSL)</em></span>를 사용하면 고차원 정책에서 저차원 세부사항에 이르기까지 <span style='background-color: #fff5b1'>모든 추상화 수준과 모든 도메인을 POJO로 표현할 수 있다.</span></p>
<br/>

<blockquote>
<ul>
<li>코드처럼 시스템 역시 깨끗해야 한다. 깨끗하지 못한 아키텍처는 버그가 숨어들기 쉬워지고 스토리를 구현하기 어려워져 도메인 논리를 흐리고 제품의 품질을 떨어뜨린다.</li>
</ul>
</blockquote>
<ul>
<li>모든 추상화 단계에서 의도는 명확히 표현해야한다. 그러려면 POJO를 작성하고, 관점 또는 관점과 유사한 매커니즘을 사용해 각 구현 관심사를 분리해야한다.</li>
<li><span style='background-color: #fff5b1'>시스템을 설계하든 개별 모듈을 설계하든, <strong>실제로 돌아가는 가장 단순한 수단을 사용</strong>해야 한다.</span></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>