以 linux shell 為基礎在 windows 中批次處理

trimble 的 gnss 接收儀預設是記錄 trimble 的 T02 格式, 要用 trimble 的 windows 程式 convertToRINEX 轉成 rinex 格式, 只能開虛擬機或在另一台 windows 電腦上轉換。 但檔案很多,於是研究了一下 convert to rinex 的命令列介面, 運用 windows 10 開始內建的 ssh 功能傳檔案, 再在 linux 上產生 bat 批次檔,完成一次跨系統合作演出。

現在在管系館的 gnss 站,是 trimble 的接收儀, 預設是記錄 trimble 的 t02 格式, 要用 trimble 提供的程式 convertToRINEX 才能轉換成通用的 rinex 格式。

convert to rinex

convert to rinex 執行時可以有圖形介面, 同時也可以用參數傳入檔名,單純在命令列處理。 雖然圖形介面中,可以一次選取多個檔案, 提供基本的批次處理功能。

但 convert to rinex 的處理速度實在太慢, 一天長度周期一秒的 T02 檔案,要轉五分鐘。 而且 convert to rinex 的圖形介面設計, 是會先掃描完整個檔案,才能點下轉換鈕轉換; 一個檔案大概掃 4 分鐘,轉換 1 分鐘。

選取多個檔案批次處理時,則是全部掃描完才會開始轉換。 我不確定記憶體用量會不會隨檔案數增加, 所以不敢一口氣轉全部。 最後決定用命令列的介面,轉一次呼叫一次程式, 也避免要一次把所有檔案傳到 windows 上,怕硬碟空間不足。

使用 sftp 在虛擬機與本機間傳送檔案

trimble 的程式 convertToRINEX 只提供 exe 格式, 我試了在 wine 下執行,也裝了.NET 框架, 但執行時還是會報錯。最後就放棄在 wine 下執行了。

但還是計劃要做,就決定開 qemu 虛擬機來轉。 本來想弄個 9p 來共享虛擬機和本機的檔案, 後來發現 9p 開起來只是虛擬機裡可以連上去, 但虛擬機裡還是要有 9p 的客戶端才有用; windows 還是放棄吧。

本來在考慮 smb,但忽然想到 windows 10 內建 ssh 了, 於是可以用 sftp 來傳。 於是 batch 程式寫起來就像這樣:

scp user@10.0.2.2:data/2019-01-01.T02 .
"C:\Program Files (x86)\Trimble\convertToRINEX\convertToRinex.exe" 2019-01-01.T02 -mo GHLK -v 2.11
scp 2019-01-01.19o user@10.0.2.2:data
del 2019-01-01.*

但有個問題,我不會寫 batch 迴圈。 而且我不想一次下載全部的 t02 檔案, 所以我想要下載一個,轉檔完傳回去,刪掉後再換下一個。 可是這樣我得要從 sftp 的 ls 輸出中, 取得檔案列表,然後遍歷。 聽說 batch 的編程能力是有限制的, 不像 bash 是一門完整的程式語言, 我想這已經超出 batch 的能力範圍。

在 linux 上產生腳本

反正 shell 腳本的本質只是一連串依序執行的命令, 可編程只是用來降低複雜度的附加功能。 若是缺少編程功能,只能以更複雜的方式完成程式, 像是由 另一種程式 ,來產生目標腳本; 甚至直接在程式中呼叫 system() 達到編程需求。 總之我就跑了個 bash 迴圈, 生出了要在 windows 上執行的 batch 檔:

host=user@10.0.2.2
convert='"C:\Program Files (x86)\Trimble\convertToRINEX\convertToRinex.exe"'

for t02 in data/*.T02
do
    filename=$(basename $t02)
    basename=$(basename $filename .T02)
    cat <<BATCH

scp $host:$t02 .
$convert $filename -mo GHLK -v 2.11
scp $(printf "$basename.19%s " o g n q b c) $host:data
del $basename.*

BATCH
done

首先最重要的,ssh 要開免密碼登入, 否則執行一次 scp 就要輸入一次登入密碼, 根本不可能批次作業。 在 windows 的 ssh 除了基本的 ssh, 傳送檔案的 sftp 外,連 ssh-keygen 也內建了。 執行一次產生金鑰後,丟到 linux 的 .ssh/authorized_keys 即可。 如果不信任 windows 的話, 可以跑完腳本就把公鑰從 authorized_keys 裡刪除。

windows 的 glob 萬用字元, 似乎不會由 shell 展開,而是命令得自己展開。 cmd 的內建命令 del dir 之類的,多支援 glob, 但 ssh 就不支援,直接回找不到檔案。 (但 scp 複製遠端 linux 上的檔案時是支援 glob 的。)

傳到 linux 上並壓縮

由於本人收納習慣極差,server 的硬碟也快滿了。 而轉換出來的 rinex 檔是 ascii 格式很佔空間, 尤其我是轉出周期一秒的檔案。 所以我希望可以在上傳後自動壓縮。

rinex 壓縮有二步:rnxcmp 軟體提出了一種格式 crx, 可以最小化 rinex 中文字所記錄的資訊, 並提供了 rnx2crx crx2rnx 二個轉換程式。 rnx2crx 的輸出仍為文字檔, 所以可以再用 gzip 或其它壓縮工具壓縮一次。

在 server 上監視檔案上傳我還沒試過, 而且感覺就很麻煩,還要處理檔案複製到一半的情況。 所以我決定直接在 windows 中處理, 直接把傳送最大的 rinex 檔案 *.??o 的 scp, 換成以下的 ssh 命令:

ssh user@10.0.2.2 "rnx2crx - | gzip - > 2019-01-01.19d.gz" <2019-01-01.19o

但發現這樣重導向不知道為什麼會卡住,會卡在 gzip 那邊, 或是其它在 linux 上讀取標準輸入的程式, 可能 windows 沒有送出 eof 之類的? 後來試了管道不會卡住, 就 google 到 cmd 裡類似 cat 的命令 type , 最後就這樣用了。

type 2019-01-01.19o | ssh user@10.0.2.2 "rnx2crx - | gzip - > 2019-01-01.19d.gz"

另外還有試了在傳送文字檔時壓縮會不會好一點,但幾乎沒有差別。

type 2019-01-01.19o ^
| ssh -o Compression=yes ^
  user@10.0.2.2 "rnx2crx - | gzip - > 2019-01-01.19d.gz"

推測原因:

  1. 虛擬機裡壓縮有性能銷耗,且 linux 端還要解壓一次,都吃同一台機器的 cpu。
  2. 虛擬機的網路是假的網路,和本機間的網路速度遠超實體網路。

用 runpkr00 直接在 linux 上轉檔

後來與 r2 討論後,才知道另一支程式 runpkr00, 是古早時期 trimble 提供用來轉換 t02 格式的。 最大的優勢是提供了 linux 甚至 solaris 版本的執行檔, 雖然主要支援對向是 t02 之前的 t00 t01, 但如今仍可以運作在 t02 格式上。

我是在 unavco 的網站上找到 runpkr00 的執行檔的 , 但總之為了防止死檔,還是 自己再備份一次 unavco 提供的 rpm 包

下載 rpm 包後,可能會因為作業系統太新, 不符合 rpm 包的要求無法安裝。 但總之裝就對了,看要強制裝, 或是用其它工具解壓縮 rpm 包, 再手動複製到作業系統內。 我是用 debian,所以用 alien 把 rpm 轉成 tgz 後手動解壓縮。 裡面的執行檔是 static link, 雖然是古董了,但還是可以正常執行。 還有附手冊,雖然內容有夠少,雖然選項都有介紹, 但 unavco 網站提的眉角都沒記錄。

alien --to-tgz runpkr00*.rpm
tar -xvf runpkr00*.tgz
install ./usr/bin/runpkr00 /usr/local/bin
install --mode 644 ./usr/share/man/man1/runpkr00.1.gz \
                   /usr/local/share/man/man1

runpkr00 如同 unavco 的網站建議的, runpkr00 在轉換 t02 格式時,最好加上 -g 選項, 若是記錄檔包含 rt27 時會一併轉換,(手冊寫 type 27。)副檔名會從 dat 變成 tgd。 teqc 能處理 trimble 的 dat 及 tgd 格式, 直接 teqc 2019-01-01.tgd > 2019-01-01.19o 即可。 另外 runpkr00 在選項解析上設計不良, (也可能是因為在現在的環境執行很老的執行檔。) 參數順序或連寫都可能造成錯誤,最好照 unavco 或手冊上的建議用法。

runpkr00 -g -d 2019-01-01.T02
teqc 2019-01-01.tgd > 2019-01-01.19o

runpkr00 轉出來的 rinex 檔案, 相比新版程式 convert to rinex 少了一些觀測量。 我的 runpkr00 轉出來有 L1 L2 C1 P1 P2 S1 S2, 而 convert to rinex 有 C1 C2 C5 C7 C8 L1 L2 L5 L7 L8 P1 P2。 就 gipsy 和 csrs-ppp 的解算結果來看, convert to rinex 稍好,但沒有很明顯。 考慮 convert to rinex 的執行門檻, 使用 runpkr00 是可接受的。 但就後處理而言,執行速度好像不是重點; 花個 10 幾分鐘處理一整天的數據,應該不是不可接受。

另外 runpkr00 轉出來的 rinex 檔案, 會缺少記錄天線位移的 ANTENNA: DELTA H/E/N 欄位, 如果需要,記得在 teqc 時手動加上去。

eastd=1 northd=2 heightd=3
teqc -O.pehEN,m $eastd $northd $heightd 2019-01-01.tgd > 2019-01-01.19o

powershell 與 windows 舒適圈

這篇文章中,我繞了很大一圈,在 linux 上產生 windows 的 batch 腳本, 並且在產生的 batch 腳本中,再使用 windows 10 中的 ssh 登入回 linux 執行 linux shell 指令。 最主要的原因是我無法把 trimble 的 convert to rinex 整合進現有的 linux shell 工具鏈中。 我所需要的是,在 linux 上執行一條命令後,就幫我轉好檔。

如果是不同 unix like 作業系統中, 最最基礎也可以用 ssh 簡單連結, 把另一台電腦上的程式當自己家用。 (也許我可以試試在 linux 上以 shell 介面登入 windows?)

ssh user@another.os '
    cat > 2019-01-01.t02
    convert 2019-01-01.t02
    cat 2019-01-01.19o
    rm 2019-01-01.*
' < 2019-01-01.t02 > 2019-01-01.19o

或是如果可以讓 convert to rinex 在 linux 的 wine 中運作, 也可以有更好的效果。 但都不行,只好想辦法在 windows 中處理了。

對於一直生活在 windows 中的人,則根本沒這個問題。 由於檔案也本來就存在本機, 可以直接寫個 batch 遍歷所有檔案一一轉換; 就算 batch 的功能有限,這樣基本的功能還是做的到的。 而且 crx2rnx teqc 或其它這類 gnss 數據, 多也會提供 windows 版本,所以完全在 windows 處理, 雖然 batch 沒有 bash 那麼好用,還是足夠了。

何況現在還有 powershell,先天體質就比 bash 好, 只差在完整的生態和配合的工具。 以批次處理來說,要活在 windows 中完成這些工作, windows 和 linux 的差距已經不像以前那樣巨大了。

但 windows user 的主要問題, 是系統的設計哲學,不鼓勵使用者動手的文化。 要一個慣用 windows 圖形介面的人, 去學習命令列操作是很困難的。

另一個門檻就是 windows 上寫程式太麻煩,windows 有很多自己的鍋。 visual stdio 太肥,雖然也是有各種 ide,如 code block dev c++。 但 linux 對腳本語言的支持還是最好的, 一個 shebang 直接把所有直譯語言當執行檔用。