적갈색 받침대 위에 반투명 판이 고정돼 겹쳐 있고, 오른쪽에는 받침 없이 따로 떠 있는 작은 반투명 판이 점선으로 아래를 가리키는 추상 일러스트

새 터미널에서 local_setup.bash만 source해도 underlay 패키지가 보일까: setup.bash와 ros2 pkg list 개수로 직접 재 보기


Read in English

ROS 2 Jazzy 입문 튜토리얼 두 편은 작업공간을 불러오는 줄을 서로 다르게 적는다. Using colcon to build packages는 빌드가 끝나면 source install/setup.bash 한 줄을 실행하게 한다. Creating a workspace는 빌드한 터미널과 분리된 새 터미널을 열어 source /opt/ros/jazzy/setup.bash로 기존 ROS 2 설치(underlay)를 먼저 불러오고, 작업공간 루트에서 source install/local_setup.bash를 실행하게 한다(원문 L270-L281, L319-L327). 이 두 줄을 .bashrc로 옮기면서 앞줄을 빼고 내 작업공간 줄만 남기면 어떻게 될까. 새 터미널에서 local_setup.bash만 source해도 underlay 패키지가 보일까? 보이지 않는다. 패키지 목록을 세기도 전에 ros2: command not found가 먼저 난다.

Creating a workspace의 note에 이 답의 근거가 이미 있다. overlay의 local_setup은 overlay 패키지만 더하고, setup은 overlay가 만들어질 때의 underlay까지 불러온다는 문장이다. 아래에서는 colcon 소스 코드에서 setup.bash가 underlay를 어떻게 찾는지 먼저 읽어 예측을 세우고, 네트워크를 끈 ros:jazzy-ros-base 컨테이너에서 환경을 비운 셸 다섯 개를 띄워 ros2 pkg list 개수로 그 예측을 확인한다. 결과는 예측과 같았다. 공식 문서와 저장소는 2026년 10월 4일에 확인했고, 실물 로봇이나 시뮬레이터는 쓰지 않았다. 끝까지 읽으면 .bashrc에 한 줄을 둘지 두 줄을 둘지, underlay를 바꿨을 때 무엇을 다시 해야 하는지 고를 수 있다.

source가 바꾸는 것은 셸의 검색 경로다

colcon build로 작업공간을 한 번 빌드해 본 정도를 전제로, bash의 source부터 짚는다. source는 스크립트를 새 프로세스가 아니라 지금 셸 안에서 실행하는 명령이라서, 스크립트가 바꾼 환경변수(environment variable, 셸이 프로그램에 넘겨주는 이름=값 설정)가 셸에 그대로 남는다. ROS 2의 setup 스크립트는 이 성질을 이용한다. 실행 파일을 찾는 PATH, ROS 패키지를 찾는 AMENT_PREFIX_PATH, 파이썬 모듈을 찾는 PYTHONPATH 같은 검색 경로 목록에 자기 경로를 덧붙인다.

여기에 세 용어가 더 필요하다.

  • 작업공간(workspace): src, build, install, log 폴더를 가진 디렉터리. colcon build를 실행하는 곳이다.
  • 아래층 환경(underlay): 내 작업공간이 기대는 쪽. 빌드하는 셸에 이미 불러와 둔 ROS 2 설치이며 보통 /opt/ros/jazzy이고, 다른 작업공간이 이 자리에 올 수도 있다. 공식 튜토리얼은 “Your underlay must contain the dependencies of all the packages in your overlay”라고 적는다(원문 L27-L30).
  • 위층 작업공간(overlay): 그 위에서 내가 만든 패키지를 빌드하는 작업공간. 튜토리얼의 ~/ros2_ws가 여기에 해당한다.

내 노드가 rclpy를 쓴다면, 그 노드를 실행하는 터미널에는 내 패키지(overlay)와 rclpy(underlay)가 둘 다 보여야 한다. 로봇 노드를 띄우기 전 단계의 환경 설정이지만, 한쪽이 빠지면 내 패키지를 못 찾거나 내 패키지가 import할 라이브러리를 못 찾는다.

Creating a workspace의 note는 두 스크립트를 이렇게 구분한다(원문 L343-L347).

Sourcing the local_setup of the overlay will only add the packages available in the overlay to your environment. setup sources the overlay as well as the underlay it was created in, allowing you to utilize both workspaces.

So, sourcing your main ROS 2 installation’s setup and then the ros2_ws overlay’s local_setup, like you just did, is the same as just sourcing ros2_ws‘s setup, because that includes the environment of its underlay.

두 번째 문단의 “like you just did”는 도입에서 본 절차, 즉 underlay를 먼저 source하고 overlay의 local_setup을 source한 두 줄을 가리킨다. note는 그 두 줄이 overlay setup 한 줄과 같다고 말한다. underlay 줄을 뺀 경우는 직접 다루지 않지만, 첫 문장대로라면 overlay 패키지만 남아야 한다. 첫 문단 끝의 “it was created in”도 기억해 둘 구절이다. 무엇을 기준으로 “만들어질 때의 underlay”를 정하는지는 코드에서 확인한다.

두 가설을 패키지 개수 하나로 가르기

note의 문장을 숫자로 바꿔 보자. underlay만 source한 셸에서 보이는 패키지 수를 U, 내 overlay 패키지 수를 M이라고 한다. 단위는 둘 다 패키지 개수다.

  • 가설 1(스크립트가 underlay까지 불러온다): 새 셸에서 U+M개가 보인다.
  • 가설 2(overlay만 불러온다): M개가 보인다. 이 경우 ros2 실행 파일도 underlay 쪽에 있으므로 명령 자체를 못 찾을 수 있다.

두 예측의 중간값 (U+M+M)/2를 기준선으로 잡으면, overlay 스크립트 하나만 source한 셸의 개수가 기준선보다 큰지 작은지만 보면 된다. U를 미리 정확히 몰라도 판정할 수 있다.

설명용 숫자로 계산해 보면 이렇다. U=280, M=3이라고 가정한다(280은 계산을 따라가기 쉽게 고른 값이고 실제 설치의 패키지 수가 아니다).

항목 식 값
가설 1 예측 280 + 3 283
가설 2 예측 3 3
두 예측의 차이 283 − 3 280
기준선 (283 + 3) / 2 143

두 예측이 280만큼 벌어지므로 측정값이 143 근처에서 애매하게 걸릴 일은 없다. 이 일곱 개 식(표의 네 개와 U, U+M, 이름 충돌 경계)은 자체 산술 예제 example.py에 assert로 적어 두었고, Python 3.12.3에서 종료 코드 0으로 통과한 기록이 검산 기록 verification.json에 있다. 어디까지나 산술 검산이다. ROS나 colcon을 실행한 결과는 뒤의 컨테이너 측정뿐이다.

setup.bash에는 빌드하던 셸의 underlay 경로가 문자열로 적힌다

두 스크립트는 colcon의 bash 확장(colcon-bash)이 빌드할 때 서로 다른 템플릿으로 만든다(bash.py L25-L50).

local_setup.bash의 템플릿은 첫머리에 “This script extends the environment with all packages contained in this prefix path.”라고 적고 있다(prefix.bash.em L3-L4). prefix(설치 경로)는 패키지가 설치된 루트 폴더다. 이 스크립트는 자기 install 폴더 아래 패키지들의 package.bash를 차례로 실행할 뿐, 다른 prefix를 부르는 코드가 없다. colcon 기본 방식(isolated install)에서는 install/huntlab_probe_a처럼 패키지마다 prefix가 따로 생기므로, 이것만 source한 셸의 AMENT_PREFIX_PATH에는 overlay 패키지 경로만 들어간다. /opt/ros/jazzy/bin이 PATH에 없으니 ros2도 없어야 한다.

setup.bash는 체인(chain, 상위 prefix의 환경을 먼저 불러온 뒤 자기 prefix를 얹는 순서 연결) 템플릿으로 만들어진다. 빌드하는 순간 colcon은 get_chained_prefix_path()로 상위 prefix 목록을 만든다(colcon-core prefix_path L60-L95). 재료는 빌드하는 셸의 환경변수 두 개다. colcon-core는 COLCON_PREFIX_PATH의 경로를 쓰고(colcon.py L23-L35), colcon-ros는 AMENT_PREFIX_PATH의 경로 중 local_setup.* 파일이 있는 곳을 더한다(ament.py L26-L66). 그러니 /opt/ros/jazzy를 source한 셸에서 빌드하면 /opt/ros/jazzy가 이 목록에 들어가야 한다.

템플릿은 그 목록을 COLCON_CURRENT_PREFIX="/opt/ros/jazzy" 같은 문자열 그대로 파일에 써 넣고, 상위 prefix의 local_setup.bash를 먼저 source한 뒤 마지막에 자기 폴더의 local_setup.bash를 source한다(prefix_chain.bash.em L19-L32). colcon 개발자 문서도 같은 구조를 설명한다. setup.<ext>는 “first invokes the local_setup.<ext> files from all parent prefix paths and then invokes the sibling local_setup.<ext> file”이다(colcon Environment setup).

두 스크립트의 source 순서 비교: install/setup.bash는 상위 underlay의 local_setup.bash를 먼저 실행한 뒤 자기 패키지로 내려가 PATH와 AMENT_PREFIX_PATH에 underlay·overlay가 모두 들어가지만, install/local_setup.bash는 패키지별 package.bash만 실행해 overlay 경로만 남고 ros2 명령이 없다

note의 “the underlay it was created in”이 가리키는 것이 바로 이 문자열이다. setup.bash는 source되는 순간 지금 셸에 무엇이 불려 있는지 보지 않고, 빌드하던 셸에서 보였던 경로를 그대로 다시 부른다.

코드만 읽고 나면 예측이 세 가지로 좁혀진다. 생성된 setup.bash에는 /opt/ros/jazzy를 가리키는 COLCON_CURRENT_PREFIX= 줄이 있고 local_setup.bash에는 없을 것, setup.bash 한 줄만 source한 빈 셸은 기준선을 넘을 것, local_setup.bash 한 줄만 source한 빈 셸에서는 ros2를 찾지 못할 것.

같은 질문은 2018년 5월 29일 ROS Answers에 “What is the difference between local_setup.bash and setup.bash”라는 제목으로 올라왔고, 지금은 Robotics Stack Exchange의 87173번 질문으로 옮겨져 있다(2026년 10월 4일 기준 조회 2,467회). 질문자는 새 터미널에서 overlay의 local_setup.bash만 source하고 ament build를 실행하자 ament: command not found를 만났고, setup.bash를 source하면 빌드가 됐다고 적었다. 채택된 답은 setup이 “sources the local_setup.<ext> script for all workspaces which were sourced in the environment when this workspace was built”라고 설명했고, 위 코드와 맞는다. 다른 답 하나는 ament 빌드 도구 시절 문서(ament 색인에서 상위 경로를 읽는다)를 인용하는데, 현재 colcon 구현은 빌드 시점의 환경변수에서 경로를 고른다. 오래된 답을 읽을 때 구분해 둘 차이다.

Jazzy 컨테이너의 깨끗한 셸 다섯 개에서 잰 값

실험 스크립트는 underlay(/opt/ros/jazzy)를 source한 상태에서 시작해 /tmp/ws에 이름이 underlay와 겹치지 않는 최소 ament_python 패키지 3개(huntlab_probe_a, _b, _c, 실행 의존성 rclpy)를 만들고 colcon build로 빌드한다. 그다음 env -i(상속받은 환경변수를 모두 지우고 명령을 실행하는 도구)로 아무것도 source하지 않은 bash를 다섯 개 띄운다. 각 셸은 source 줄만 다르다. 셸마다 command -v ros2로 명령이 있는지 먼저 보고, 있으면 ros2 pkg list의 줄 수를 센다. ros2가 없는 셸에서도 개수를 남기려고, AMENT_PREFIX_PATH의 각 경로에서 ament 색인(ament index, 패키지마다 빈 파일 하나를 두어 “이 패키지가 설치돼 있다”고 알리는 폴더) 파일 이름을 직접 세는 보조 계수도 함께 기록했다.

스크립트를 다듬는 동안 고친 점이 하나 있다. 첫 버전은 M을 local_setup.bash 셸에서 센 값으로 잡았는데, 그러면 판정할 대상에서 판정 기준을 뽑는 셈이 된다. 그래서 M은 overlay install 폴더의 색인 파일 수로 따로 세도록 바꿨다. 시험 실행은 고치기 전후 모두 종료 코드 0으로 끝났고, 아래 숫자는 하네스가 정식으로 다섯 번 돌린 결과에서만 가져왔다.

측정 환경은 이미지 ros:jazzy-ros-base(고정 다이제스트 ros@sha256:066420e07f60aa18262f2479981def87ebcfcec42eefb0c0c57c4a46098348ca), ROS 2 jazzy, Python 3.12.3, rclpy 7.1.12-1noble.20260902.053513, 기본 RMW(ROS 2가 통신 미들웨어를 바꿔 끼우는 계층, ROS Middleware) rmw_fastrtps_cpp, colcon 패키지 python3-colcon-core 0.21.3+upstream-1·python3-colcon-bash 0.5.0-100·python3-colcon-ros 0.5.0+upstream-1이다. 네트워크 없음, 1 CPU·1 GiB, 5회 반복. 노드끼리 통신하는 실험이 아니므로 RMW는 결과에 관여하지 않는다. 빌드는 매번 4.11~4.15초, 실행 한 번 전체는 6.38~6.4초 걸렸다.

U = 194, M = 3, 이름이 겹친 패키지 0개였으므로 가설 1 예측은 197, 가설 2 예측은 3, 기준선은 (194 + 3 + 3) / 2 = 100.0이다.

새 셸 source한 줄 예측 실측 (5회 모두 같음)
T0 없음 ros2 없음, 색인 0 ros2 없음(종료 코드 127), 색인 0
T1 /opt/ros/jazzy/setup.bash U개 194개, probe 0개
T2 /tmp/ws/install/setup.bash U+M개 197개, probe 3개 모두
T3 /tmp/ws/install/local_setup.bash ros2 없음, 색인 M개 ros2 없음(종료 코드 127), 색인 3개, rclpy 없음
T4 underlay setup.bash → overlay local_setup.bash T2와 같은 집합 197개, 패키지 이름 집합이 T2와 같음

T1·T2·T4에서는 ros2 pkg list 줄 수와 색인 직접 계수가 모두 같았다. 다섯 셸 모두 실험 전에 적어 둔 예측과 같은 쪽으로 나왔고, 다섯 번 반복하는 동안 개수는 한 번도 달라지지 않았다.

1회차 원본 출력 중 판정에 쓰인 네 줄을 그대로 옮긴다. 첫 줄은 빌드가 만든 두 스크립트에서 COLCON_CURRENT_PREFIX=로 시작하는 줄만 뽑은 것이다.

{"kind": "generated_scripts", "setup_bash_prefix_assignments": ["COLCON_CURRENT_PREFIX=\"/opt/ros/jazzy\"", "COLCON_CURRENT_PREFIX=\"$(builtin cd \"`dirname \"${BASH_SOURCE[0]}\"`\" > /dev/null && pwd)\""], "local_setup_bash_prefix_assignments": [], "setup_bash_mentions_underlay": true, "local_setup_bash_mentions_underlay": false}
{"kind": "terminal", "terminal": "T2_overlay_setup", "sourced": ["/tmp/ws/install/setup.bash"], "source_exit_codes": [0], "ros2_path": "/opt/ros/jazzy/bin/ros2", "ros2_pkg_list_exit": 0, "ros2_pkg_list_lines": 197, "ros2_pkg_list_stderr_head": "", "index_count": 197, "probes_in_index": 3, "probes_in_pkg_list": 3, "rclpy_in_index": true, "ament_prefix_entries": 4, "underlay_in_ament_prefix_path": true, "colcon_prefix_path": "/tmp/ws/install", "shell_seconds": 0.47}
{"kind": "terminal", "terminal": "T3_overlay_local_setup", "sourced": ["/tmp/ws/install/local_setup.bash"], "source_exit_codes": [0], "ros2_path": null, "ros2_pkg_list_exit": 127, "ros2_pkg_list_lines": null, "ros2_pkg_list_stderr_head": "bash: line 5: ros2: command not found", "index_count": 3, "probes_in_index": 3, "probes_in_pkg_list": null, "rclpy_in_index": false, "ament_prefix_entries": 3, "underlay_in_ament_prefix_path": false, "colcon_prefix_path": "/tmp/ws/install", "shell_seconds": 0.05}
{"kind": "verdict", "U": 194, "M": 3, "name_overlap": 0, "baseline": 100.0, "hypothesis_underlay_included": 197, "hypothesis_overlay_only": 3, "T2": {"terminal": "T2_overlay_setup", "ros2_available": true, "count_used": 197, "count_source": "ros2_pkg_list", "above_baseline": true, "verdict": "underlay_and_overlay_visible"}, "T3": {"terminal": "T3_overlay_local_setup", "ros2_available": false, "count_used": 3, "count_source": "ament_index", "above_baseline": false, "verdict": "overlay_only"}, "T4_equals_T2": true, "pkg_list_matches_index": {"T1_underlay_setup": true, "T2_overlay_setup": true, "T4_underlay_setup_then_overlay_local_setup": true}, "total_seconds": 5.63}

재현 스크립트와 5회 실행의 원본 출력은 실험 공개 폴더에 있다. 스크립트 본문은 experiment.py, 실행별 출력은 results.json에서 바로 열 수 있다.

해석은 측정과 분리해서 읽어야 한다. generated_scripts 줄은 코드에서 세운 첫 예측과 같다. setup.bash에는 /opt/ros/jazzy를 가리키는 줄과 자기 디렉터리를 계산하는 줄 두 개가 있고, local_setup.bash에는 그런 줄이 하나도 없다. setup.bash 한 줄만 source한 T2는 기준선을 크게 넘었고 probe 3개도 모두 보였으니 가설 1, 곧 “underlay까지 불러온다” 쪽이다. local_setup.bash 한 줄만 source한 T3는 개수를 세기 전에 ros2를 찾지 못했고, 색인에도 내 패키지 3개만 있었다. 가설 2 쪽이다. T3의 "rclpy_in_index": false도 눈여겨볼 값이다. probe 패키지는 rclpy에 의존하도록 만들었는데, 이 셸에서는 그 의존성이 보이지 않는다. 노드를 실행해 보지는 않았지만, 내 노드가 import할 라이브러리가 검색 경로에 없다는 뜻이다.

측정 도구가 무엇을 세는지도 확인해 둘 필요가 있다. ros2 pkg list는 AMENT_PREFIX_PATH의 경로들만 뒤져 share/ament_index/resource_index/packages 아래 이름을 모으고, 같은 이름은 처음 찾은 경로 하나로만 기록한 뒤 정렬해 출력한다(list.py L22-L24, resources.py L96-L121, search_paths.py L21-L33). 그래서 ros2 pkg list | wc -l은 “이 셸에서 보이는 서로 다른 패키지 이름 수”이고, 실험에서 이 값이 색인 직접 계수와 일치한 것도 같은 규칙을 따랐기 때문이다.

같은 조건에서 직접 돌려 보려면 빈 폴더에서 아래를 실행한다. Docker가 설치돼 있어야 하고, 첫 실행 때 이미지를 내려받는 동안만 호스트 네트워크가 필요하다. 컨테이너 자체는 네트워크 없이 돈다.

mkdir local-setup-check && cd local-setup-check
curl -fsSLO https://raw.githubusercontent.com/sungpyo9053/blog/main/experiments/ros2-local-setup-vs-setup-bash-fresh-terminal-package-count/experiment.py
docker run --rm --network none --cpus 1 --memory 1g -v "$PWD":/work ros@sha256:066420e07f60aa18262f2479981def87ebcfcec42eefb0c0c57c4a46098348ca bash -c "source /opt/ros/jazzy/setup.bash && python3 /work/experiment.py"

정상이라면 JSON 줄 아홉 개가 나오고 마지막 verdict 줄에 "T2"는 underlay_and_overlay_visible, "T3"는 overlay_only가 찍힌다. "kind": "build" 줄의 exit_code가 0이 아니면 빌드 실패이며, 스크립트는 숫자를 만들지 않고 종료 코드 1로 끝난다.

숫자로 판정해 보는 두 문제

문제 1. 앞의 설명용 예제(U=280, M=3)에서 overlay 패키지 하나가 underlay 패키지와 이름이 같다면, setup.bash 한 줄만 source한 셸의 개수는 얼마로 예측해야 하고 판정은 바뀌는가?

답은 282이고 판정은 바뀌지 않는다. ros2 pkg list는 이름 기준으로 한 번만 세므로 겹친 1개가 빠져 280 + 3 − 1 = 282가 된다. 기준선 143보다 여전히 훨씬 크다. 이 경계 사례도 verification.json의 일곱 식 가운데 하나로 검산했다. 다만 이름이 겹칠 때 overlay 쪽 패키지가 실제로 underlay 것을 덮어쓰는지(override)는 개수로 알 수 없고, 이 실험도 재지 않았다.

문제 2. Creating a workspace 튜토리얼을 그대로 따라 /opt/ros/jazzy/setup.bash를 source한 다음 overlay의 local_setup.bash를 source한 터미널에서 개수가 기준선을 넘었다. 이 결과로 “local_setup.bash도 underlay를 불러온다”고 말할 수 있을까?

말할 수 없다. 측정 표의 T4가 바로 이 터미널이고, 개수와 이름 집합이 T2와 같았다. 그 셸에서 underlay 패키지가 들어올 수 있는 길은 먼저 source한 underlay와 방금 source한 overlay 스크립트, 두 가지다. 개수만으로는 어느 길 덕분인지 가를 수 없다. 그래서 판정은 아무것도 source하지 않은 셸(T2·T3)에서만 했고, T4는 앞에서 인용한 note의 두 번째 문단(“is the same as just sourcing ros2_ws‘s setup“)을 확인하는 대조군으로만 썼다.

.bashrc에 넣을 줄과 이 측정이 말하지 않는 것

새 터미널마다 overlay까지 쓰고 싶다면 선택지는 두 가지이고, 측정에서 두 방식은 같은 패키지 집합을 만들었다.

# 한 줄: overlay의 setup.bash가 빌드 때의 underlay까지 불러온다
source ~/ros2_ws/install/setup.bash

# 두 줄: Creating a workspace 튜토리얼의 순서 그대로, underlay 다음 overlay의 local_setup.bash
source /opt/ros/jazzy/setup.bash
source ~/ros2_ws/install/local_setup.bash

~/ros2_ws는 예시 경로이니 자기 작업공간 경로로 바꾼다. 두 줄 방식에서 첫 줄을 지우면 T3와 같은 셸이 된다. 같은 overlay에 local_setup.bash와 setup.bash를 연달아 쓰는 예제도 인터넷에 있는데, setup.bash가 마지막에 같은 폴더의 local_setup.bash를 다시 source하므로 앞줄은 결과에 보태는 것이 없다. local_setup.bash가 제 몫을 하는 경우는 원하는 underlay가 이미 셸에 준비돼 있고 그 위에 overlay만 얹고 싶을 때다.

한 줄 방식을 고를 때는 조건이 하나 붙는다. setup.bash는 빌드할 때 보였던 underlay 경로를 문자열로 들고 있으므로, underlay를 다른 배포판이나 다른 작업공간으로 바꾸면 그 새 underlay를 source한 터미널에서 overlay를 다시 빌드해야 체인이 갱신된다. 템플릿 코드에서 끌어낸 추론이고, 이번 실험에서 재지는 않았다. 공식 튜토리얼이 빌드한 터미널과 분리된 새 터미널에서 overlay를 source하라고 권하는 것도(원문 L270-L271) 빌드하는 셸의 환경이 스크립트에 기록된다는 이 구조와 함께 기억해 둘 만하다.

자기 컴퓨터에서 확인하고 싶다면 실험과 같은 방식으로 환경을 비운 셸을 열어 한 줄씩 시험할 수 있다. 아래 명령은 실험 스크립트가 쓴 env -i 조건을 손으로 옮긴 것이며, 독자 데스크톱의 ~/ros2_ws에서 이 글이 실행해 본 것은 아니다.

env -i HOME="$HOME" PATH=/usr/sbin:/usr/bin:/sbin:/bin LANG=C.UTF-8 bash --noprofile --norc
source ~/ros2_ws/install/local_setup.bash
command -v ros2 || echo "ros2 없음: underlay 줄이 따로 필요하다"

ros2가 보이는 셸에서는 ros2 pkg list | wc -l 값을 underlay만 source한 셸의 값 U와 비교해 기준선 (U+M+M)/2의 어느 쪽인지 보면 된다. 확인이 끝나면 exit로 빠져나온다.

이 측정이 말하지 않는 범위도 있다. env -i로 비운 셸은 “아무것도 source하지 않은 새 셸”이지 실제 데스크톱 터미널과 같지 않다. 평소 터미널은 ~/.bashrc나 /etc/profile이 먼저 무언가를 source했을 수 있다. bash만 확인했고 zsh의 setup.zsh, Windows 스크립트, --merge-install, 세 단 이상 쌓은 underlay 체인은 다루지 않았다. /opt/ros/jazzy 자체의 setup.bash와 local_setup.bash가 어떻게 다른지도 확인하지 않았다. 개발 환경 설정에 관한 측정이므로 로봇 동작이나 안전에 대해서는 아무것도 보여 주지 않는다.

작업공간이 하나이고 underlay가 /opt/ros/jazzy 하나라면 overlay setup.bash 한 줄로 충분하다. 두 줄 방식은 어떤 underlay를 까는지가 .bashrc에 그대로 드러나는 대신 줄 순서를 지켜야 한다. 어느 쪽이든 local_setup.bash 한 줄만 남겨 두는 설정은 피한다. 새 터미널에서 ros2: command not found를 만나면 그 줄부터 의심하면 된다.

확인한 자료 (2026-10-04)

제휴·협찬 없이 작성했다.

비슷한 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다