Ubuntu 22.04 Server TurtleBot3 Burger를 tmux로 한 번에 실행하기 #1

1. 구성 목표

Ubuntu 22.04 Server와 ROS 2 Humble을 설치한 TurtleBot3 Burger를 SSH로 운용할 때는 일반적으로 여러 터미널이 필요합니다.

이 글에서는 TurtleBot3에 SSH로 접속한 후 하나의 스크립트만 실행하여 다음 네 개의 tmux window를 자동으로 구성합니다.

  1. bringup: TurtleBot3 본체, OpenCR, 모터, LiDAR 실행
  2. camera: Pi Camera 2 영상 노드 실행
  3. audio: 오디오 출력 노드 실행
  4. debug: 노드, 토픽, LiDAR 주기 및 수동 디버깅 환경

사용하는 주요 환경은 다음과 같습니다.

운영체제: Ubuntu 22.04 Server
ROS 버전: ROS 2 Humble
로봇: TurtleBot3 Burger
LiDAR: LDS-03
카메라: Raspberry Pi Camera 2
DDS: Cyclone DDS
ROS_DOMAIN_ID: 200
원격 접속: SSH

2. tmux란 무엇인가

tmux는 하나의 터미널 안에서 여러 터미널 작업 공간을 운영할 수 있게 해주는 터미널 멀티플렉서입니다.

TurtleBot3와 같이 SSH로 원격 운용하는 시스템에서는 다음 장점이 있습니다.

  1. 하나의 SSH 연결에서 여러 ROS 2 노드를 실행할 수 있습니다.
  2. SSH 연결이 끊어져도 tmux 안의 ROS 2 노드는 계속 실행됩니다.
  3. 다시 SSH로 접속하여 기존 화면과 로그를 그대로 확인할 수 있습니다.
  4. 노드별로 window를 분리하여 로그를 관리할 수 있습니다.
  5. 한 window를 여러 pane으로 나누어 디버깅 정보를 동시에 볼 수 있습니다.

tmux의 구조는 다음과 같습니다.

Session
 ├── Window 1
 │    ├── Pane 1
 │    └── Pane 2
 ├── Window 2
 └── Window 3
  1. Session은 전체 작업 묶음입니다.
  2. Window는 일반 터미널 프로그램의 탭과 비슷합니다.
  3. Pane은 하나의 window를 분할한 작은 터미널입니다.

이번에는 turtlebot3이라는 session 안에 네 개의 window를 만듭니다.

3. tmux 설치

TurtleBot3에 SSH로 접속합니다.

ssh sjyong@192.168.200.28

패키지 정보를 갱신합니다.

sudo apt update

tmux를 설치합니다.

sudo apt install -y tmux

설치된 버전을 확인합니다.

tmux -V

다음과 같이 버전이 출력되면 정상입니다.

tmux 3.2a

버전 번호는 설치된 Ubuntu 패키지에 따라 다를 수 있습니다.

4. tmux 간단 사용법

테스트용 tmux session을 생성합니다.

tmux new-session -s test

tmux 내부 명령은 기본적으로 Ctrl+b를 누른 후 다음 키를 누르는 방식입니다.

  1. Ctrl+b, d: 현재 session에서 빠져나오기
  2. Ctrl+b, c: 새 window 생성
  3. Ctrl+b, n: 다음 window로 이동
  4. Ctrl+b, p: 이전 window로 이동
  5. Ctrl+b, 숫자: 해당 번호의 window로 이동
  6. Ctrl+b, w: 전체 window 목록 표시
  7. Ctrl+b, %: 현재 pane을 좌우로 분할
  8. Ctrl+b, ": 현재 pane을 상하로 분할
  9. Ctrl+b, 방향키: pane 이동
  10. Ctrl+b, [: 스크롤 모드 진입
  11. 스크롤 모드에서 q: 스크롤 모드 종료

현재 tmux에서 빠져나올 때는 다음 키를 순서대로 누릅니다.

Ctrl+b
d

실행 중인 tmux session을 확인합니다.

tmux ls

기존 session에 다시 들어갑니다.

tmux attach-session -t test

테스트 session을 종료합니다.

tmux kill-session -t test

5. ROS 2 실행 환경 확인

TurtleBot3에서 사용하는 작업공간은 다음과 같습니다.

source /opt/ros/humble/setup.bash
source ~/turtlebot3_ws/install/setup.bash
source ~/rgb_led_ws/install/setup.bash

환경 변수는 다음과 같습니다.

export ROS_DOMAIN_ID=200
export LDS_MODEL=LDS-03
export TURTLEBOT3_MODEL=burger
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp

rgb_led_ws의 환경은 오디오 노드와 해당 작업공간에 포함된 패키지를 찾기 위해 반드시 ROS 2 명령 실행 전에 적용되어야 합니다.

작업공간의 올바른 source 순서는 다음과 같습니다.

ROS 2 Humble 기본 환경
→ TurtleBot3 작업공간
→ RGB LED 및 오디오 작업공간
→ ROS 2 노드 실행

따라서 각 tmux window에서 ROS 2 명령을 실행하기 전에 다음 세 개의 setup 파일을 모두 source하도록 구성합니다.

6. Cyclone DDS 환경 변수 이름 주의

Cyclone DDS 설정에 사용하는 올바른 환경 변수 이름은 다음과 같습니다.

CYCLONEDDS_URI

다음 이름은 잘못된 이름입니다.

CYCLONE_DDS_URI

중간에 밑줄이 들어간 CYCLONE_DDS_URI가 아니라, 밑줄 없이 연결된 CYCLONEDDS_URI를 사용해야 합니다.

네트워크 인터페이스 이름도 실제 TurtleBot3의 인터페이스 이름을 사용해야 합니다.

인터페이스를 확인합니다.

ip address

기본 통신에 사용되는 인터페이스를 확인합니다.

ip route show default

예를 들어 다음과 같이 출력될 수 있습니다.

default via 192.168.0.1 dev wlan0 proto dhcp

이 경우 네트워크 인터페이스 이름은 wlan0입니다.

Ubuntu 환경에 따라 다음과 같은 다른 이름이 사용될 수도 있습니다.

wlan0
wlp2s0
wlx001122334455
eth0
enp3s0

아래에서 작성할 스크립트는 기본 라우트에 사용되는 인터페이스를 자동으로 감지합니다.

자동 감지가 잘못된 경우 실행 전에 직접 지정할 수 있습니다.

export ROS_NETWORK_INTERFACE=wlan0

7. TurtleBot3 tmux 스크립트 생성

홈 디렉터리에 스크립트를 저장할 디렉터리를 만듭니다.

mkdir -p ~/turtlebot3_tmux

스크립트 파일을 생성합니다.

nano ~/turtlebot3_tmux/turtlebot3_tmux.sh

다음 전체 내용을 입력합니다.

#!/usr/bin/env bash
set -Eeuo pipefail

SESSION_NAME="turtlebot3"

ROS_SETUP="/opt/ros/humble/setup.bash"
TB3_SETUP="${HOME}/turtlebot3_ws/install/setup.bash"
RGB_LED_SETUP="${HOME}/rgb_led_ws/install/setup.bash"

CYCLONEDDS_FILE="/tmp/${SESSION_NAME}_cyclonedds_${USER}.xml"

fail()
{
  echo "[ERROR] $*" >&2
  exit 1
}

check_file()
{
  [[ -f "$1" ]] || fail "setup 파일을 찾을 수 없습니다: $1"
}

detect_network_interface()
{
  if [[ -n "${ROS_NETWORK_INTERFACE:-}" ]]; then
    printf '%s\n' "${ROS_NETWORK_INTERFACE}"
    return 0
  fi

  local interface

  interface="$(
    ip route show default 2>/dev/null |
      awk '/default/ {
        for (i = 1; i <= NF; i++) {
          if ($i == "dev" && (i + 1) <= NF) {
            print $(i + 1)
            exit
          }
        }
      }'
  )"

  [[ -n "${interface}" ]] ||
    fail "기본 네트워크 인터페이스를 찾을 수 없습니다. ROS_NETWORK_INTERFACE를 직접 지정하십시오."

  printf '%s\n' "${interface}"
}

write_cyclonedds_config()
{
  local interface="$1"

  cat > "${CYCLONEDDS_FILE}" <<XML
<CycloneDDS>
  <Domain id="any">
    <General>
      <NetworkInterfaceAddress>${interface}</NetworkInterfaceAddress>
      <AllowMulticast>default</AllowMulticast>
    </General>
  </Domain>
</CycloneDDS>
XML
}

ros_shell()
{
  local ros_command="$1"
  local payload

  printf -v payload \
    'source %q && source %q && source %q && export ROS_DOMAIN_ID=200 LDS_MODEL=LDS-03 TURTLEBOT3_MODEL=burger RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ROS_NETWORK_INTERFACE=%q CYCLONEDDS_URI=%q && %s' \
    "${ROS_SETUP}" \
    "${TB3_SETUP}" \
    "${RGB_LED_SETUP}" \
    "${ROS_NETWORK_INTERFACE}" \
    "file://${CYCLONEDDS_FILE}" \
    "${ros_command}"

  printf '/bin/bash -lc %q' "${payload}"
}

attach_session()
{
  if [[ -n "${TMUX:-}" ]]; then
    tmux switch-client -t "${SESSION_NAME}"
  else
    exec tmux attach-session -t "${SESSION_NAME}"
  fi
}

create_session()
{
  check_file "${ROS_SETUP}"
  check_file "${TB3_SETUP}"
  check_file "${RGB_LED_SETUP}"

  export ROS_NETWORK_INTERFACE
  ROS_NETWORK_INTERFACE="$(detect_network_interface)"

  write_cyclonedds_config "${ROS_NETWORK_INTERFACE}"

  tmux new-session -d \
    -s "${SESSION_NAME}" \
    -n bringup \
    "$(ros_shell 'exec ros2 launch turtlebot3_bringup robot.launch.py')"

  tmux set-option -t "${SESSION_NAME}" history-limit 50000
  tmux set-option -t "${SESSION_NAME}" mouse on

  tmux new-window \
    -t "${SESSION_NAME}" \
    -n camera \
    "$(ros_shell 'exec ros2 launch turtlebot3_bringup camera.launch.py format:=BGR888 width:=320 height:=240')"

  tmux new-window \
    -t "${SESSION_NAME}" \
    -n audio \
    "$(ros_shell 'exec ros2 run robot_audio_output audio_output_node')"

  tmux new-window \
    -t "${SESSION_NAME}" \
    -n debug \
    "$(ros_shell "watch -n 2 -t 'ros2 node list 2>/dev/null'")"

  tmux split-window \
    -t "${SESSION_NAME}:debug" \
    "$(ros_shell "watch -n 2 -t 'ros2 topic list 2>/dev/null'")"

  tmux split-window \
    -t "${SESSION_NAME}:debug" \
    "$(ros_shell 'ros2 topic hz /scan; exec bash -i')"

  tmux split-window \
    -t "${SESSION_NAME}:debug" \
    "$(ros_shell "printf '\nTurtleBot3 debug shell\nROS_DOMAIN_ID=%s\nTURTLEBOT3_MODEL=%s\nLDS_MODEL=%s\nRMW_IMPLEMENTATION=%s\nROS_NETWORK_INTERFACE=%s\nCYCLONEDDS_URI=%s\n\n' \"\${ROS_DOMAIN_ID}\" \"\${TURTLEBOT3_MODEL}\" \"\${LDS_MODEL}\" \"\${RMW_IMPLEMENTATION}\" \"\${ROS_NETWORK_INTERFACE}\" \"\${CYCLONEDDS_URI}\"; exec bash -i")"

  for window_name in bringup camera audio debug; do
    tmux set-window-option \
      -t "${SESSION_NAME}:${window_name}" \
      remain-on-exit on
  done

  tmux select-layout -t "${SESSION_NAME}:debug" tiled
  tmux select-window -t "${SESSION_NAME}:bringup"

  echo "[OK] tmux session: ${SESSION_NAME}"
  echo "[OK] network interface: ${ROS_NETWORK_INTERFACE}"
  echo "[OK] Cyclone DDS config: ${CYCLONEDDS_FILE}"
}

show_status()
{
  if tmux has-session -t "${SESSION_NAME}" 2>/dev/null; then
    tmux list-windows \
      -t "${SESSION_NAME}" \
      -F '#{window_index}:#{window_name} panes=#{window_panes} active=#{window_active}'
  else
    echo "실행 중인 ${SESSION_NAME} 세션이 없습니다."
  fi
}

COMMAND="${1:-start}"

command -v tmux >/dev/null 2>&1 ||
  fail "tmux가 설치되어 있지 않습니다: sudo apt install -y tmux"

case "${COMMAND}" in
  start)
    if ! tmux has-session -t "${SESSION_NAME}" 2>/dev/null; then
      create_session
    fi

    attach_session
    ;;

  attach)
    tmux has-session -t "${SESSION_NAME}" 2>/dev/null ||
      fail "실행 중인 ${SESSION_NAME} 세션이 없습니다."

    attach_session
    ;;

  stop)
    if tmux has-session -t "${SESSION_NAME}" 2>/dev/null; then
      tmux kill-session -t "${SESSION_NAME}"
      echo "${SESSION_NAME} 세션을 종료했습니다."
    else
      echo "실행 중인 ${SESSION_NAME} 세션이 없습니다."
    fi
    ;;

  restart)
    tmux kill-session -t "${SESSION_NAME}" 2>/dev/null || true

    create_session
    attach_session
    ;;

  status)
    show_status
    ;;

  *)
    echo "사용법: $0 {start|attach|stop|restart|status}" >&2
    exit 2
    ;;
esac

nano에서 저장합니다.

Ctrl+O
Enter
Ctrl+X

8. 실행 권한 설정

스크립트에 실행 권한을 추가합니다.

chmod +x ~/turtlebot3_tmux/turtlebot3_tmux.sh

Bash 문법을 검사합니다.

bash -n ~/turtlebot3_tmux/turtlebot3_tmux.sh

아무 메시지도 출력되지 않으면 기본 Bash 문법에는 문제가 없는 것입니다.

스크립트 상태 명령을 실행합니다.

~/turtlebot3_tmux/turtlebot3_tmux.sh status

아직 실행 전이라면 다음과 같이 출력됩니다.

실행 중인 turtlebot3 세션이 없습니다.

9. 소스 설명

1) Bash 인터프리터 지정

#!/usr/bin/env bash

이 줄을 shebang이라고 합니다.

스크립트를 실행할 때 어떤 프로그램으로 해석할 것인지를 운영체제에 알려줍니다.

/usr/bin/env bash

는 현재 환경의 PATH에서 Bash 실행 파일을 찾아서 사용한다는 뜻입니다.

다음과 같이 직접 경로를 지정할 수도 있습니다.

#!/bin/bash

그러나 /usr/bin/env bash 방식은 Bash 설치 위치가 시스템마다 다를 가능성을 고려한 형태입니다.

이 스크립트에서는 다음과 같은 Bash 전용 문법을 사용하므로 Bash로 실행해야 합니다.

[[ ... ]]
local
printf -v
${변수:-기본값}

따라서 다음과 같이 sh로 실행하면 안 됩니다.

sh turtlebot3_tmux.sh

다음과 같이 직접 실행하거나 Bash로 실행해야 합니다.

./turtlebot3_tmux.sh

또는 다음과 같이 실행합니다.

bash turtlebot3_tmux.sh

2) Bash 엄격 실행 설정

set -Eeuo pipefail

이 설정은 스크립트에서 오류를 가능한 한 빨리 발견하기 위해 사용합니다.

각 옵션은 다음 의미입니다.

set -E

함수나 명령 치환 내부에서 발생한 오류에도 ERR trap이 상속되도록 합니다.

현재 코드에는 trap ERR이 없지만, 나중에 다음과 같은 오류 추적 기능을 추가할 때 유용합니다.

trap 'echo "오류 발생: ${LINENO}번 줄"' ERR

set -e

명령이 실패하면 스크립트를 중단합니다.

예를 들어 tmux session 생성이 실패하거나 ROS 2 setup 파일을 읽지 못하면 이후 명령을 계속 실행하지 않습니다.

set -u

정의되지 않은 변수를 사용하면 오류로 처리합니다.

다음 코드에서 ROBOT_NAME이 정의되지 않았다면 스크립트가 종료됩니다.

echo "${ROBOT_NAME}"

정의되지 않을 가능성이 있는 변수는 다음처럼 작성해야 합니다.

echo "${ROBOT_NAME:-burger}"

set -o pipefail

파이프라인 중간에 있는 명령이 실패한 경우에도 전체 파이프라인을 실패로 처리합니다.

예를 들어 다음 명령에서는 ip route와 awk가 파이프로 연결되어 있습니다.

ip route show default | awk '...'

기본 Bash 동작에서는 마지막 명령인 awk의 종료 상태만 확인할 수 있습니다.

pipefail을 사용하면 앞쪽의 ip route가 실패한 경우도 감지할 수 있습니다.

3) tmux session 이름 지정

SESSION_NAME="turtlebot3"

생성할 tmux session의 이름을 지정합니다.

실제로 생성되는 session 이름은 다음과 같습니다.

turtlebot3

이 session은 다음 명령으로 확인할 수 있습니다.

tmux ls

출력 예시는 다음과 같습니다.

turtlebot3: 4 windows

session 이름을 변수로 관리하면 코드 전체에서 동일한 이름을 반복해서 직접 작성할 필요가 없습니다.

예를 들어 다음 코드는:

tmux new-session -s "${SESSION_NAME}"

실제로는 다음과 같은 의미입니다.

tmux new-session -s turtlebot3

나중에 session 이름을 변경하려면 다음 한 줄만 수정하면 됩니다.

SESSION_NAME="tb3_robot"

4) ROS 2 setup 파일 경로 설정

ROS_SETUP="/opt/ros/humble/setup.bash"
TB3_SETUP="${HOME}/turtlebot3_ws/install/setup.bash"
RGB_LED_SETUP="${HOME}/rgb_led_ws/install/setup.bash"

각 ROS 2 환경 설정 파일의 경로를 변수로 저장합니다.

ROS 2 Humble 기본 환경
ROS_SETUP="/opt/ros/humble/setup.bash"

ROS 2 Humble이 설치된 기본 환경입니다.

다음 파일을 source하면 ros2 명령과 Humble에 설치된 기본 패키지를 사용할 수 있습니다.

source /opt/ros/humble/setup.bash
TurtleBot3 작업공간
TB3_SETUP="${HOME}/turtlebot3_ws/install/setup.bash"

직접 컴파일한 TurtleBot3 패키지를 사용하기 위한 작업공간입니다.

${HOME}은 현재 사용자의 홈 디렉터리를 의미합니다.

사용자 이름이 ubuntu라면 다음 경로가 됩니다.

/home/ubuntu/turtlebot3_ws/install/setup.bash
RGB LED 및 오디오 작업공간
RGB_LED_SETUP="${HOME}/rgb_led_ws/install/setup.bash"

robot_audio_output 패키지와 RGB LED 관련 패키지가 포함된 작업공간입니다.

이 환경을 source하지 않으면 다음 명령에서 패키지를 찾지 못할 수 있습니다.

ros2 run robot_audio_output audio_output_node
source 순서

스크립트가 사용하는 source 순서는 다음과 같습니다.

ROS 2 Humble 기본 환경
→ TurtleBot3 작업공간
→ RGB LED 및 오디오 작업공간

실제 명령으로 표현하면 다음과 같습니다.

source /opt/ros/humble/setup.bash
source ~/turtlebot3_ws/install/setup.bash
source ~/rgb_led_ws/install/setup.bash

뒤에서 source한 작업공간이 앞쪽 환경 위에 overlay되는 구조입니다.

5) Cyclone DDS 설정 파일 경로

CYCLONEDDS_FILE="/tmp/${SESSION_NAME}_cyclonedds_${USER}.xml"

Cyclone DDS가 사용할 XML 설정 파일 경로를 생성합니다.

예를 들어 다음 조건이라면:

SESSION_NAME=turtlebot3
USER=sjyong

파일 경로는 다음과 같습니다.

/tmp/turtlebot3_cyclonedds_sjyong.xml

/tmp 디렉터리는 임시 파일을 저장하는 표준 디렉터리입니다.

스크립트를 실행할 때마다 이 파일의 내용이 새로 작성됩니다.

Cyclone DDS는 나중에 다음 환경 변수로 이 파일을 사용합니다.

CYCLONEDDS_URI=file:///tmp/turtlebot3_cyclonedds_sjyong.xml

여기서 사용하는 환경 변수 이름은 다음과 같습니다.

CYCLONEDDS_URI

CYCLONE_DDS_URI가 아니라 CYCLONEDDS_URI입니다.

6) 오류 처리 함수

fail()
{
  echo "[ERROR] $*" >&2
  exit 1
}

fail() 함수는 오류 메시지를 출력하고 스크립트를 종료합니다.

함수 선언
fail()
{

Bash에서 함수를 정의하는 문법입니다.

다음 두 방식은 같은 의미입니다.

fail()
{
  ...
}
function fail
{
  ...
}
전달된 인자 출력
echo "[ERROR] $*" >&2

$*는 함수에 전달된 모든 인자를 하나의 문자열처럼 나타냅니다.

다음과 같이 호출하면:

fail "setup 파일이 없습니다."

다음과 같이 출력됩니다.

[ERROR] setup 파일이 없습니다.
표준 오류 출력
>&2

메시지를 표준 출력이 아니라 표준 오류로 보냅니다.

리눅스의 주요 출력 번호는 다음과 같습니다.

0: 표준 입력
1: 표준 출력
2: 표준 오류

오류 메시지를 표준 오류로 보내면 일반 출력과 오류 출력을 분리할 수 있습니다.

예를 들어 다음과 같이 실행할 수 있습니다.

./turtlebot3_tmux.sh 2> error.log

이 경우 오류 메시지만 error.log에 저장됩니다.

비정상 종료
exit 1

스크립트를 종료 코드 1로 종료합니다.

일반적으로 종료 코드의 의미는 다음과 같습니다.

0: 정상 종료
0 이외의 값: 오류 종료

7) 파일 존재 여부 확인 함수

check_file()
{
  [[ -f "$1" ]] || fail "setup 파일을 찾을 수 없습니다: $1"
}

이 함수는 전달받은 파일이 실제로 존재하는지 확인합니다.

첫 번째 함수 인자
$1

함수에 전달된 첫 번째 인자입니다.

다음과 같이 호출하면:

check_file "${ROS_SETUP}"

$1에는 다음 값이 들어갑니다.

/opt/ros/humble/setup.bash
일반 파일 검사
[[ -f "$1" ]]

-f는 대상 경로가 일반 파일인지 확인합니다.

파일이 존재하면 참을 반환하고, 존재하지 않으면 거짓을 반환합니다.

|| 연산자
명령1 || 명령2

앞 명령이 실패할 때만 뒤 명령을 실행합니다.

따라서 다음 코드는:

[[ -f "$1" ]] || fail "setup 파일을 찾을 수 없습니다: $1"

다음 의미입니다.

파일이 존재하면 계속 진행
파일이 없으면 fail 함수 호출

일반적인 if 문으로 바꾸면 다음과 같습니다.

if [[ ! -f "$1" ]]; then
  fail "setup 파일을 찾을 수 없습니다: $1"
fi

8) 네트워크 인터페이스 자동 감지 함수

detect_network_interface()
{

이 함수는 ROS 2 Cyclone DDS 통신에 사용할 네트워크 인터페이스를 결정합니다.

우선 사용자가 직접 지정한 값이 있는지 확인하고, 없으면 기본 라우트에서 자동으로 감지합니다.

사용자가 지정한 네트워크 인터페이스 확인
if [[ -n "${ROS_NETWORK_INTERFACE:-}" ]]; then
  printf '%s\n' "${ROS_NETWORK_INTERFACE}"
  return 0
fi
미정의 변수 안전 처리
"${ROS_NETWORK_INTERFACE:-}"

스크립트 상단에 set -u가 있으므로 정의되지 않은 변수를 그대로 읽으면 오류가 발생합니다.

다음 코드는 위험합니다.

"${ROS_NETWORK_INTERFACE}"

변수가 존재하지 않으면 set -u에 의해 스크립트가 종료될 수 있습니다.

다음처럼 :-를 사용하면 변수가 없을 때 빈 문자열을 사용합니다.

"${ROS_NETWORK_INTERFACE:-}"
문자열 길이 검사
[[ -n "${ROS_NETWORK_INTERFACE:-}" ]]

-n은 문자열 길이가 0보다 큰지 검사합니다.

즉, 사용자가 다음과 같이 미리 지정했다면:

export ROS_NETWORK_INTERFACE=wlan0

자동 감지하지 않고 wlan0을 그대로 사용합니다.

인터페이스 이름 출력
printf '%s\n' "${ROS_NETWORK_INTERFACE}"

함수의 결과를 표준 출력으로 보냅니다.

뒤에서 이 함수는 다음과 같이 사용됩니다.

ROS_NETWORK_INTERFACE="$(detect_network_interface)"

따라서 함수가 출력한 문자열이 변수에 저장됩니다.

함수 정상 종료
return 0

함수를 성공 상태로 종료합니다.

이 줄이 실행되면 아래쪽의 자동 감지 코드는 실행되지 않습니다.

지역 변수 선언
local interface

interface 변수를 현재 함수 내부에서만 사용할 수 있도록 선언합니다.

local을 사용하지 않으면 함수에서 만든 변수가 스크립트 전체에 영향을 줄 수 있습니다.

다음과 같이 사용하는 것이 안전합니다.

detect_network_interface()
{
  local interface
}

함수 실행이 끝나면 지역 변수의 역할도 끝납니다.

기본 네트워크 인터페이스 검색

interface="$(
  ip route show default 2>/dev/null |
    awk '/default/ {
      for (i = 1; i <= NF; i++) {
        if ($i == "dev" && (i + 1) <= NF) {
          print $(i + 1)
          exit
        }
      }
    }'
)"

이 부분은 기본 라우트에서 네트워크 장치 이름을 찾아 interface 변수에 저장합니다.

명령 치환
interface="$(명령)"

$(...) 안의 명령을 실행하고 출력 결과를 변수에 저장합니다.

예를 들어 다음 명령 결과가:

ip route show default

다음과 같다면:

default via 192.168.0.1 dev wlan0 proto dhcp metric 600

awk가 wlan0만 출력하고 최종적으로 다음과 같이 저장됩니다.

interface=wlan0
기본 라우트 확인
ip route show default

시스템의 기본 네트워크 경로를 표시합니다.

일반적인 출력은 다음과 같습니다.

default via 192.168.0.1 dev wlan0 proto dhcp metric 600

여기서 dev 다음에 나오는 값이 네트워크 인터페이스입니다.

dev wlan0

유선 연결이라면 다음처럼 표시될 수 있습니다.

default via 192.168.0.1 dev eth0

Ubuntu의 예측 가능한 네트워크 이름을 사용하면 다음과 같을 수도 있습니다.

default via 192.168.0.1 dev wlp2s0
오류 메시지 제거
2>/dev/null

ip route 명령에서 발생하는 표준 오류를 화면에 표시하지 않습니다.

2는 표준 오류이고 /dev/null은 데이터를 버리는 장치입니다.

awk를 이용한 인터페이스 추출

/default/ {
  for (i = 1; i <= NF; i++) {
    if ($i == "dev" && (i + 1) <= NF) {
      print $(i + 1)
      exit
    }
  }
}
default 행 선택
/default/

default라는 문자열이 포함된 행만 처리합니다.

필드 순회
for (i = 1; i <= NF; i++)

현재 행의 모든 필드를 처음부터 끝까지 확인합니다.

awk에서 NF는 현재 행의 필드 개수를 의미합니다.

다음 행을 예로 들면:

default via 192.168.0.1 dev wlan0 proto dhcp

각 필드는 다음과 같습니다.

$1 = default
$2 = via
$3 = 192.168.0.1
$4 = dev
$5 = wlan0
$6 = proto
$7 = dhcp
dev 필드 검색
if ($i == "dev" && (i + 1) <= NF)

현재 필드가 dev인지 확인합니다.

동시에 다음 필드가 실제로 존재하는지도 확인합니다.

(i + 1) <= NF

장치 이름 출력

print $(i + 1)

dev 다음 필드를 출력합니다.

dev wlan0

에서는 wlan0이 출력됩니다.

검색 종료
exit

첫 번째 인터페이스를 찾으면 awk 실행을 끝냅니다.

기본 라우트가 여러 개일 때 첫 번째 결과만 선택합니다.

자동 감지 결과 검증

[[ -n "${interface}" ]] ||
  fail "기본 네트워크 인터페이스를 찾을 수 없습니다. ROS_NETWORK_INTERFACE를 직접 지정하십시오."

자동 감지한 인터페이스가 빈 문자열인지 확인합니다.

정상적으로 감지되었다면 다음과 같은 값이 들어 있습니다.

wlan0

감지하지 못했다면 fail() 함수가 실행됩니다.

[ERROR] 기본 네트워크 인터페이스를 찾을 수 없습니다. ROS_NETWORK_INTERFACE를 직접 지정하십시오.

이 경우 스크립트 실행 전에 직접 지정할 수 있습니다.

export ROS_NETWORK_INTERFACE=wlan0

그 다음 다시 실행합니다.

./turtlebot3_tmux.sh start
감지한 인터페이스 반환
printf '%s\n' "${interface}"

감지한 네트워크 인터페이스를 출력합니다.

이 출력은 뒤에서 다음 명령에 의해 변수로 저장됩니다.

ROS_NETWORK_INTERFACE="$(detect_network_interface)"

함수 자체에 문자열 반환 기능이 없으므로, Bash에서는 일반적으로 표준 출력과 명령 치환을 사용하여 문자열 결과를 전달합니다.

9) Cyclone DDS XML 생성 함수

write_cyclonedds_config()
{
  local interface="$1"

이 함수는 전달받은 네트워크 인터페이스를 사용하여 Cyclone DDS XML 파일을 생성합니다.

다음처럼 호출됩니다.

write_cyclonedds_config "${ROS_NETWORK_INTERFACE}"

ROS_NETWORK_INTERFACE가 wlan0이면 함수 내부에서는 다음과 같습니다.

interface=wlan0

Leave a Comment