Devin.KR

터미널과 작업 경로

90분 안팎

학습 목표

절대·상대 경로와 파일 구조를 확인합니다.

개념

왜 실행 위치를 고정하지 않을까요

배포 자동화는 터미널, CI 작업 폴더, 예약 실행처럼 서로 다른 위치에서 호출됩니다. workspace/logs라는 상대 경로를 그대로 쓰면 스크립트 파일의 위치가 아니라 호출한 셸의 현재 위치 아래에 폴더가 생깁니다. 잘못 생성된 폴더에도 명령은 성공할 수 있어 오류를 늦게 발견합니다. 따라서 작업자가 매번 특정 위치로 이동하기를 기대하기보다 스크립트가 자기 프로젝트의 루트를 계산하도록 만듭니다. 오늘의 목표는 어느 위치에서 호출하든 안내판 프로젝트 안에만 산출물이 생기게 하는 것입니다.

절대 경로와 상대 경로를 구분합니다

Linux에서 /로 시작하는 경로는 절대 경로입니다. workspace/app.env는 현재 위치를 기준으로 해석하는 상대 경로입니다. 점 하나는 현재 디렉터리, 점 두 개는 부모 디렉터리입니다. pwd는 현재 위치를 보여 주고 cd는 위치를 옮깁니다. ls는 목록을 확인하며 ls -a는 점으로 시작하는 파일도 보여 줍니다. 경로를 복사해 붙여 넣을 때 파일인지 폴더인지 확인합니다. cd에 파일을 넘겨 Not a directory가 나오면 그 파일의 부모 폴더로 이동해야 합니다.

프로젝트 폴더의 역할을 정합니다

scripts/에는 실행 절차를, workspace/에는 로컬 설정과 작업 산출물을 둡니다. workspace/logs는 이후 실행 기록을, workspace/data는 이후 서비스 데이터를 담을 자리입니다. mkdir -p는 필요한 부모 폴더를 함께 만들고 이미 있는 디렉터리는 유지합니다. 여러 번 실행해도 기존 데이터가 지워지지 않아야 합니다. 같은 이름의 일반 파일이 있는 경우에는 디렉터리를 만들 수 없으므로 오류를 숨기지 않습니다. 이번 레슨에서는 기존 자료를 지우거나 전체 폴더를 초기화하는 명령을 추가하지 않습니다.

Bash에서 자기 위치를 계산합니다

제공 스크립트는 scripts/ 바로 아래에 있습니다. ${BASH_SOURCE[0]}은 현재 Bash 스크립트의 경로를 제공하고 dirname은 그 경로의 부모 부분을 구합니다. 그 폴더의 한 단계 위로 이동한 결과를 pwd -P로 얻어 ROOT에 저장합니다. $(...)는 명령의 출력을 값으로 받습니다. 그 안의 cd는 명령 치환을 위한 환경에서 수행하므로 호출자의 터미널 위치를 바꾸지 않습니다. 이 실습은 심볼릭 링크로 연결한 스크립트 호출을 계약에 포함하지 않습니다. 링크 기반 릴리스 경로는 이후 배포 설계에서 별도로 다룹니다.

공백을 한 경로로 전달합니다

"$ROOT/workspace/logs"처럼 변수 확장을 큰따옴표로 감쌉니다. 경로에 공백이 있으면 따옴표 없는 확장이 여러 인자로 나뉠 수 있습니다. 작은따옴표 안에서는 $ROOT가 값으로 바뀌지 않고 글자 그대로 남습니다. 큰따옴표는 변수 값을 사용하면서 한 인자로 유지할 때 씁니다. $ROOT가 빈 값인데 폴더를 생성하는 위험을 줄이려고 루트 계산의 cd가 실패하면 실행을 중단합니다. dirname과 cd의 --는 뒤의 값을 옵션과 구분하는 표기이며 실습에서는 Bash와 일반 Linux 도구를 사용합니다.

경로 오류를 좁힙니다

No such file or directory는 대개 찾는 경로가 없다는 뜻입니다. 스크립트 자체를 못 찾았는지 스크립트 안에서 설정 파일을 못 찾았는지 오류 앞의 프로그램 이름과 경로를 읽습니다. 먼저 pwd와 ls scripts로 호출 위치를 확인하고, 다음으로 스크립트가 사용하는 ROOT 계산을 확인합니다. Permission denied는 존재하지 않는 경로와 다른 문제이므로 다음 레슨의 접근 권한 확인으로 이어집니다. 실습 검사기는 상위 임시 폴더에 workspace가 잘못 생기지 않았는지도 확인합니다.

미션으로 연결합니다

시작 코드의 mkdir 줄을 프로젝트 루트 기준으로 고친 뒤 bash check.sh를 실행합니다. 성공한 다음 workspace/data에 개인 시험 파일을 넣고 준비 스크립트를 다시 실행해 내용이 유지되는지 확인합니다. 경로에 공백이 없어서 우연히 통과하는 경우를 피하려고 검사기는 notice board라는 복사본을 만듭니다. 미션의 scripts/prepare.sh도 같은 폴더 계약을 사용하며 다음 모듈에서 Java 서비스 파일을 이 프로젝트에 추가할 수 있습니다. pwd의 옵션과 링크 해석에 대한 자세한 내용은 더 읽기로 보냅니다.

실습 환경을 준비합니다

이 트랙의 프로젝트는 작은 안내판 웹 서비스의 배포·복구 파이프라인입니다. 지금은 서비스나 클라우드 자원을 만들지 않고 실행에 필요한 작업 폴더를 준비합니다. 개인 Linux VM 또는 개발 컨테이너의 일반 사용자 계정에서 연습합니다. Windows에서는 WSL의 Linux 터미널을 열고, macOS에서는 개인 Linux VM의 터미널을 이용할 수 있습니다. 터미널은 명령을 입력하는 창이며 Bash는 그 명령을 해석하는 프로그램입니다. 다른 셸의 문법이 섞이지 않도록 제공 스크립트는 bash로 실행합니다.

다운로드와 검사 흐름을 이해합니다

시작 코드 zip을 빈 개인 폴더에 풀면 README.md, check.sh, scripts/, workspace/가 보입니다. 파일 탐색기로 폴더를 열고 터미널에서 cd 뒤에 해당 경로를 따옴표로 감싸 입력합니다. bash --version, git --version, python3 --version으로 도구가 있는지 확인합니다. command not found가 나오면 해당 Linux 배포판의 패키지 관리자로 도구를 설치한 뒤 다시 확인합니다. 설치가 끝난 다음 실습 명령에는 관리 권한을 붙이지 않습니다. 첫 bash check.sh에서 실패하는 것은 고쳐야 할 동작을 보여 주는 출발점입니다.

검사 결과를 증거로 남깁니다

check.sh는 현재 폴더를 임시 위치에 복사하여 검사합니다. 공백이 있는 경로와 다른 작업 위치를 사용하므로 내 컴퓨터의 한 위치에서만 성공하는 구현을 걸러 냅니다. PASS는 그 항목을 만족했다는 뜻이고 FAIL은 기대 동작과 실제 동작이 달랐다는 뜻입니다. 마지막 RESULT의 실패 수를 확인하고 종료 상태도 읽습니다. 도구 없음이라는 환경 오류가 나오면 코드 수정보다 도구 준비를 먼저 합니다. 통과 문구를 직접 출력하도록 검사기를 고치지 않고 scripts/의 구현과 필요한 설정을 고칩니다.

다음 작업자에게 넘깁니다

완료 기준은 화면에 성공이라는 글자가 뜨는 것만으로 판단하지 않습니다. 만들어진 파일의 위치, 권한, 실패 시 남지 않아야 할 표시 파일, Git에서 검토할 수 있는 변경을 함께 확인합니다. README에는 실행한 명령과 왜 그 수정이 필요했는지 적습니다. 계정 이름이나 절대 경로는 환경마다 달라질 수 있으므로 출력 전체가 다른 경우 의미 있는 부분을 비교합니다. 실제 비밀이나 운영 서버 설정을 예제로 쓰지 않습니다. 이번 실습은 개인 폴더에 한정되며 원격 전송이나 서비스 프로세스 실행이 필요하지 않습니다.

실습에서 적용할 구현

위 설명을 아래 구현과 연결해 읽습니다. 시작 코드의 scripts/prepare.sh를 수정한 뒤 검사합니다. ROOT는 scripts의 부모인 프로젝트 루트이며 이 코드는 해당 폴더 계약을 전제로 합니다.

#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)"
mkdir -p "$ROOT/workspace/logs" "$ROOT/workspace/data"
printf 'workspace ready\n'

따라하기

폴더와 파일 확인

모범 답안 zip을 새 폴더에 푼 루트에서 실행합니다. 시작 코드는 아래 검사에서 실패하며 실습에서 수정합니다.

cp workspace/app.example.env workspace/app.env
printf '%s\n' scripts workspace
test -f workspace/app.example.env && printf 'example exists\n'

실행 결과

scripts
workspace
example exists

다른 위치에서 실행

모범 답안 zip을 새 폴더에 푼 루트에서 실행합니다. 시작 코드는 아래 검사에서 실패하며 실습에서 수정합니다.

cp workspace/app.example.env workspace/app.env
ROOT=$(pwd -P)
(cd /tmp
bash "$ROOT/scripts/prepare.sh")
test -d "$ROOT/workspace/data" && printf 'project data exists\n'

실행 결과

workspace ready
project data exists

계약 전체 검사

모범 답안 zip을 새 폴더에 푼 루트에서 실행합니다. 시작 코드는 아래 검사에서 실패하며 실습에서 수정합니다.

cp workspace/app.example.env workspace/app.env
bash check.sh

실행 결과

PASS example config exists
PASS other cwd and space path
PASS project directories
PASS no caller workspace
PASS repeat preserves data
RESULT: 0 failure(s)

확인 문제

실습

시작 코드 zip을 개인 폴더에 풀고 README를 읽은 뒤 bash check.sh로 실패 항목을 확인합니다. scripts/prepare.sh를 고쳐 공백 경로와 다른 작업 위치에서도 프로젝트의 workspace/logs와 workspace/data만 생성합니다. 기존 데이터는 보존합니다. 검사기는 복사본에서 정상·경계 상황을 확인합니다. check.sh와 check.py는 수정하지 않습니다.

시작 코드·테스트 내려받기

실행 명령

bash check.sh

기대 결과

모든 항목 PASS, RESULT: 0 failure(s), 종료 상태 0

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 상대 경로를 사용하는 스크립트를 다른 작업 디렉터리에서 실행하면 어떤 문제가 생기며 어떻게 예방하나요?
  • 공백이 있는 경로를 Bash 명령에 전달할 때 큰따옴표가 필요한 이유는 무엇인가요?