Bash Shell
Shell是位于用戶與操作系統內核之間的橋梁,當用戶在終端敲入命令后,這些輸入首先會進入內核中的tty子系統,TTY子系統負責捕獲并處理終端的輸入輸出流,確保數據正確無誤的在終端和系統內核之中。Shell在此過程不僅僅是一個監聽者,還會積極地從tty子系統讀取用戶的命令輸入,并對其進行解析來識別用戶意圖(比如當你輸入ls
時,是Shell解析這個命令并調用/bin/ls
文件的來直接解析)。Bash?Shell(基礎 shell)是 Linux/Unix 系統中用戶與操作系統內核交互的基本命令行界面,它是系統啟動后為用戶提供的默認命令解釋器環境。
Bash和Shell其實是包含關系。Shell是命令行解釋器的統稱?,有Bash、Zsh、Fish、Dash等多種命令行解釋器,Bash是Shell的最流行方式,Ubuntu系統默認使用的也是Bash,當然你可以通過在終端中輸入特定命令行可以切換(一般這幾種命令行解釋器的命令是兼容的,但是存在一些語法和交互功能的差異),一般使用Bash就足夠了。
Bash Shell與終端不同點:Bash Shell是命令行解釋器程序,終端則是輸入輸出設備/界面。可以理解為終端是"顯示器+鍵盤",而Bash是運行在這個顯示器上的"智能程序"。一般的執行流程是-------你在終端輸入命令 → 終端將輸入傳給Shell → Shell解釋執行 → 結果返回終端顯示
交互式Shell與非交互式Shell?
?簡單來說,交互式Shell用戶可以?直接輸入命令?并?實時看到輸出?的 Shell,會顯示?PS1
?提示符(如?user@host:~$
),等待用戶輸入。比較常見的場景:1,直接打開終端 2,通過 SSH?登錄后手動操作(如?ssh user@192.168.1.1
?后進入 Shell3,手動運行?bash -i
(強制交互模式)
非交互式Shell,不等待用戶輸入,直接執行?單條命令?后退出,所以說一般是看不到的,不會顯示?PS1
?提示符,執行完命令后立即關閉。比較常見的場景:1,通過?ssh user@host "command"
?遠程執行命令。2,使用 Python?paramiko.exec_command("command")。3,
運行 Shell 腳本(如?bash script.sh
),如果輸入bash -i script.sh會強制交互式
登錄式Shell和非登錄式Shell
登錄式 Shell 的關鍵特征是:
-
以用戶身份初始化完整的登錄會話(加載登錄配置文件,如?
~/.bash_profile
)。 -
通常出現在系統首次登錄時(如 SSH 登錄、本地終端登錄、
su - username
)。
.sh腳本文件
我們平常為了方便而寫的腳本文件,實際上就是一個純文本文件,包含一系列?Shell 命令(如 Bash、Zsh 等支持的語法),將需要手動逐條輸入的命令,預先整體寫入文件,實現?自動化執行。比如,以下腳本文件(文件名demo.sh),Shell解析器就會一行一行的解析每一行(即每一條)內容。
#!/bin/bash
echo "Hello World"
ls -l
date
執行的.sh文件的方式有以下幾種
- bash demo.sh:顯式指定解釋器(bash),啟動一個新的子 Shell 進程來執行腳本。直接調用?
/bin/bash
?程序,將?demo.sh
?作為參數傳遞給它,忽略 Shebang 行(即首行的#!/bin/bash)。 - ./demo.sh:依賴腳本首行的 Shebang(如?
#!/bin/bash
)。與bash的區別還有重要的一點就是bash demo.sh不需要賦予文件執行權限,而./demo.sh必須賦予文件執行權限 - source demo.sh(也可以簡寫為. demo.sh):不啟動子Shell,直接在當前 Shell 進程中逐行執行腳本,并且會忽視Shebang。腳本中定義的變量、函數、別名等會影響當前 Shell。而前兩種執行方式腳本中定義的變量、函數不會影響當前 Shell 環境。一般用于加載環境配置,比如
# 加載環境配置(如 ~/.bashrc)
source ~/.bashrc# 腳本中修改當前 Shell 的工作目錄
source change_dir.sh # 腳本內容: cd /some/path
在交互式Shell和非交互式Shell說到過,bash??運行Shell腳本時會啟動一個非交互式Shell,也就是啟動一個新的子Shell來運行這個腳本,并且這個子Shell進程是非交互式的Shell,執行完畢后,控制權返回給父 Shell。而source不會啟動子Shell,所以說它會在當前的交互式Shell運行。
.bashrc文件
.bashrc
?文件是 Linux 系統中非常重要的配置文件,它與是否安裝 ROS 無關,是 Bash shell的標準配置文件(它是一個純文本文件,使用?Bash 語法?編寫),"rc" 通常代表 "run commands" (運行命令)。文件名中的點(.)表示它是隱藏文件,在Linux系統用戶主目錄下,按ctrl+h鍵可以顯示隱藏文件。
.bashrc的內容語法(完全遵循Bash語法):
- export PATH="$PATH:/my/custom/path"?變量定義。用來設置環境變量,影響所有子進程
- alias ll='ls -alF' 起別名,在終端輸入ll就相當于輸入ls -alF
- greet() { echo "Hello, $USER!"; } 函數定義
- if [ -d ~/projects ]; then
? echo "Projects directory exists"
fi? ?條件循環判斷
.bashrc如何運行?
當啟動非登錄的交互式 Bash shell 時,打開新的終端時,通過bash命令啟動新的Shell時,.bashrc文件都會自動啟動
必須需要注意的是在非交互式Shell中默認不會自動加載.bashrc,也就是說你在使用bash執行腳本時在新打開的Shell子進程中不會加載.bashrc也就不會啟動對應的環境(但是子進程會繼承父進程的環境變量)。在交互式Shell中默認會加載.bashrc(因為?~/.bash_profile
?或?~/.profile
?通常會顯式調用?.bashrc
)。登錄式Shell會加載?~/.bash_profile
?或?~/.profile,~/.bash_profile
?或?~/.profile
?通常會顯式調用?.bashrc所以.bashrc也會自動調用。
非登錄式Shell不會加載配置文件。
其實在非交互式Shell不會自動加載.bashrc,這是因為.bashrc的開頭有以下代碼。這表示:僅當Shell是交互式時,才繼續執行后續內容。非交互式Shell會直接return
退出,也就是說即使你在非交互式Shell單獨調用source ~/.bashrc,也不會加載.bashrc的配置內容。這是為了避免用戶自定義配置影響自動化任務而設置的,為了安全起見也不要試圖去修改它!!!
case $- in*i*) ;;*) return;;
esac
除此之外還有.bash_profile和.profile文件。.bashrc在非登錄的交互式Bash Shell(不需要重新認證的子會話,如圖形終端新建窗口、bash
?命令啟動等,直接繼承父 Shell 的環境變量)自動加載;.bash_profile在Bash登錄Shell(需要用戶認證的完整會話,如系統登錄、SSH 連接等,全新環境(會重新讀取配置))自動加載,也就是說.bash_profile登錄時一次性設置。.profile是在所有Shell的登錄會話都會加載,但是前提是.bash_profile不存在。也就是說只有在用戶使用Bash并且存在./bash_profile時才會跳過.profile的加載,如果用戶使用其他命令解釋器則會自動加載.profile,所以說.profile是跨 Shell 的通用環境變量。
1,網絡配置
確保所有設備(PC端,嵌入式端等等)連接在同一個局域網下,在每臺設備的終端輸入ifconfig查看IP地址,由于不同網絡下IP地址經常改變,所有推薦將每臺設備設置為靜態IP地址。
使用ping
命令測試各PC之間的連通性,如果都能ping通后就可以開始下一步了
2,環境變量配置
在所有PC上編輯~/.bashrc
文件,添加以下環境變量:
export ROS_MASTER_URI=http://<master_ip>:11311 # 主機的IP地址。ROS 默認使用 11311 端口 作為 roscore 的通信端口,這是由 ROS 的設計者設定的標準配置。
export ROS_IP=<local_ip> # 本機的IP地址
export ROS_HOSTNAME=<local_ip> # 本機的IP地址
然后執行
source ~/.bashrc
3,主機名解析
主機名解析就是將上面的ROS_MASTER_URI=http://<master_ip>:11311中的master_ip用一個名字來代替,比如master-pc=192.168.1.1,這時只需要將192.168.1.1替換為master-pc即可,類似于變量賦值。當然,你也可以直接輸入為192.168.1.1而不需要主機解析,但是這樣的話如果主機的ip地址頻繁變換,就需要在每個從機的./bashrc中重新修改主機的ip地址,從機數量少的話還可以,從機數量多的話就會很麻煩。
主機名解析的實現方式一般有以下幾種:
1,使用/etc/hosts文件:
首先,在所有參與ROS通信的計算機上編輯/etc/hosts
文件,添加所有ROS計算機的IP和主機名映射,格式如下:
192.168.1.100 master-pc
192.168.1.101 slave1-pc
192.168.1.102 slave2-pc
保存文件后測試解析:
ping master-pc
ping slave1-pc
?但是這種方式看起來是使用了主機解析,但它只是增強了可讀性,而沒有解決頻繁手動修改IP的缺點,如果 Master 的 IP 地址變了,還是需要手動修改所有從機的/etc/hosts文件的配置
?2,使用DNS:
DNS一般是用于學校或者企業,適用于本地有DNS服務器或路由器支持DNS綁定的場景
在路由器或 DNS 服務器上,將主機名(如?master-pc
)綁定到 Master 的 IP。所有從機自動通過 DNS 解析?master-pc
,無需手動改 IP。這樣IP地址變動時,只需改DNS記錄所有從機自動生效
?3,使用mDNS:
適用于小型網絡,如實驗室或者家用
確保所有機器安裝?avahi-daemon
(Linux 默認通常已安裝):
sudo apt install avahi-daemon # Ubuntu/Debian
直接使用?.local
?主機名(無需配置?/etc/hosts
):
export ROS_MASTER_URI=http://master-pc.local:11311
4,使用靜態DHCP綁定:
適用于家用/實驗室路由器支持DHCP靜態綁定的場景
在路由器后臺,將 Master 的 MAC 地址綁定到一個固定 IP(如?192.168.1.100
)。所有從機直接用這個 IP,因為 Master 的 IP?永遠不會變。
4,防火墻配置
UFW(Uncomplicated Firewall)是一款基于 iptables 的簡單易用的防火墻配置工具,廣泛應用于基于 Debian 和 Ubuntu 的 Linux 系統中,用于管理和配置網絡防火墻規則。UFW 支持允許或拒絕特定的網絡連接。即使系統中沒有安裝 UFW,也不意味著沒有防火墻阻攔網絡連接。UFW 只是一個簡化的防火墻管理工具,而在 Linux 系統中,底層的防火墻功能通常由 iptables 或 nftables 實現(iptables 是 Linux 系統中強大的防火墻配置工具,但它的命令復雜,規則編寫難度較大,對普通用戶不太友好。UFW 則提供了一種簡化的接口,使用戶可以用更直觀、簡潔的命令來管理防火墻規則,比如,啟用防火墻只需執行sudo ufw enable
,而使用 iptables 則需要編寫多條復雜的命令)。
先使用命令查看防火墻配置工具ufw是否安裝
dpkg -l | grep ufw
如果?ufw
?已經安裝,會顯示相關的軟件包信息;如果沒有任何輸出,說明?ufw
?未安裝。此時需要輸入命令進行安裝:
sudo apt update
sudo apt install ufw
安裝后查看防火墻活躍狀態
sudo ufw status
如果輸出Status: inactive,表示防火墻處于未啟用狀態,網絡端口處于相對開放狀態,外界可以直接訪問系統暴露的端口。
如果防火墻處于活躍狀態,則需要確保所有PC的防火墻允許ROS通信(默認端口11311)
sudo ufw allow 11311/tcp
就能允許外部設備通過 22 端口(通常用于 SSH 服務)訪問本地系統?
?5,測試連接
在主PC上啟動ROS Master:
roscore
在其他PC上測試連接:
rostopic list
如果連接成功,會顯示 Master 上已發布的 Topic 列表。如果 Master 剛啟動?roscore
,默認只有?/rosout
?和?/rosout_agg
$ rostopic list
/rosout
/rosout_agg
6,同步時間
需要時可以選用此功能
在ROS多機通信中,時間同步(Time Synchronization)是一個關鍵但容易被忽視的配置。指的是讓所有參與ROS通信的計算機保持高度一致的系統時間(最好誤差在毫秒級)。
通常使用NTP來實現時間同步 :
?NTP是一種網絡協議,用于同步計算機的系統時間,計算機通過NTP客戶端從時間服務器(如ntp.ubuntu.com
)獲取精確時間,局域網內通常可達毫秒級同步
?基本原理
NTP(Network Time Protocol)實現時間同步的原理是一個分層、多源校正的精密時間同步體系,其核心設計目標是在不可靠的網絡環境中實現高精度的時間同步(局域網內通常可達毫秒級,理想條件下可達亞毫秒級)。
NTP采用層級化的時間源結構,類似于金字塔:
-
Stratum 0
最頂層,直接連接原子鐘、GPS或銫鐘等高精度物理時鐘設備,不直接參與網絡通信。-
示例:實驗室原子鐘、衛星時間信號。
-
-
Stratum 1
直接與Stratum 0設備同步的NTP服務器(時間誤差通常<100μs)。-
示例:國家授時中心服務器、Google的
time.google.com
。
-
-
Stratum 2
從Stratum 1同步的服務器,誤差逐層遞增(每層增加約1ms)。-
示例:企業級NTP服務器、公共NTP池(如
pool.ntp.org
)。
-
-
Stratum 3及以下
更低層級的同步節點,適用于普通客戶端設備。
NTP通過以下四個時間戳計算時間偏差(Clock Offset)和網絡延遲(Round-Trip Delay):
假設客戶端(A)與服務器(B)交互:
-
T?:客戶端發送請求時的本地時間(A的時鐘)。
-
T?:服務器收到請求時的本地時間(B的時鐘)。
-
T?:服務器回復響應時的本地時間(B的時鐘)。
-
T?:客戶端收到響應時的本地時間(A的時鐘)。
?最終時間偏差是多次測量的統計結果(通常采用最小二乘法或Marzullo算法過濾異常值)。
中國的NTP服務架構是分層級的,國家授時中心是stratum1,其他運營商比如阿里云,騰訊云等部署了多臺次級服務器。所以你執行命令時會使自己的PC端自動連接到附近最優的服務器,由于所有的NTP服務器本身已經同步(都溯源到國家授時中心或GPS/原子鐘) ,所以連接了NTP的各個服務器就可以實現同步。
比如下面的方式一就是將其連接到了最優的服務器,但是如果你需要更高的精度,還可以指定同一NTP服務器,這時就需要在文件/etc/systemd/timesyncd.conf,添加如下內容指定特定服務器(比如阿里云)
[Time]
NTP=ntp.aliyun.com
實現方式(Ubuntu系統)
方法一:使用系統默認的timesyncd(使用簡單)
如果使用?systemd-timesyncd
(即?timedatectl
?管理的 NTP 客戶端),通常不需要額外安裝?ntp
?或?ntpdate
,因為?timesyncd
?已經是一個輕量級的 NTP 客戶端,默認集成在?systemd
?中。
# 檢查當前同步狀態
timedatectl status# 如果未啟用NTP,運行:
sudo timedatectl set-ntp on# 手動強制同步
sudo systemctl restart systemd-timesyncd
方法二:安裝完成的ntp包(更精確控制)
sudo apt install ntp
sudo systemctl restart ntp # 啟動或重啟 NTP 服務,立即應用配置并開始同步。# 查看同步狀態
ntpq -p
ntpq -p輸出示例如下
remote refid st t when poll reach delay offset jitter
============================================================================
*ntp.ubuntu.com .POOL. 16 u 25 64 1 5.123 -0.432 0.871
+time.google.com .GOOG. 1 u 12 64 7 1.234 0.567 0.123
-
*
?表示當前主同步源。 -
offset
:本地時鐘與服務器的偏差(單位:毫秒)。 -
jitter
:網絡延遲的波動程度。
驗證時間同步:
ntpdate -q 192.168.1.100 # 查詢與某臺機器的時間差
?7,共享ROS包
此功能如果需要可以選用,不做詳細解釋
方法1:同步工作空間
-
使用rsync或git同步工作空間
-
確保所有PC上的包路徑相同
方法2:NFS共享
-
設置NFS服務器共享工作空間
-
其他PC掛載該共享目錄
方法3:獨立編譯
-
在各PC上獨立維護代碼庫
-
確保版本一致
?8,啟動管理
可以使用roslaunch
在多臺機器上啟動節點:
<launch><machine name="pc1" address="192.168.1.101" user="user" password="pass" env-loader="/opt/ros/noetic/env.sh"/><machine name="pc2" address="192.168.1.102" user="user" password="pass" env-loader="/opt/ros/noetic/env.sh"/><node machine="pc1" name="node1" pkg="your_pkg" type="node1"/><node machine="pc2" name="node2" pkg="your_pkg" type="node2"/>
</launch>