最近、bashを使用してNMEA 0183チェックサムを作成して確認する必要がありましたが、必要な機能があまり見つかりませんでした。
NMEA 0183ステートメントは、$で始まり*で終わり、$と*の間のすべてのバイトの16進数XORの2文字です。例:
$INGGA,230501.547,2118.97946,N,15752.60495,W,2,08,1.1,5.17,M,,,0,0001*02
このユーティリティは文字列を16進数に変換してXORします。これは、すでに存在するチェックサムをチェックしたり、生成されているNMEAステートメントの終わりにチェックサムを生成したりするために使用できます(提供された文字列から$と* ..を削除します)。
#!/bin/bash
# =========================================================
# Reads a NMEA 0183 sentence and calculates the proper
# XOR checksum for the end.
# Will accept a string with or without a checksum on
# the end or $ on the front and calculate what the checksum
# should be.
# Sentence can be read as an argument but must be single quoted
# or preceded by a \ or the shell will try to interpret the
# talker as a variable and the result will be incorrect.
# Examples:
# xor '$INHDT,207.7,T*27'
# xor \$INHDT,207.7,T*27
# xor INHDT,207.7,T
# If run with no arguments, will prompt user for data. No
# quotes or backslash is needed then.
# Depends: xxd sed
# ===T.Young 09/2016=======================================
set -o pipefail
set -o errexit
set -o nounset
# Functions
# =========
depcheck() { # Checks that necessary external commands are present
# and executable
local DEPENDS="sed xxd"
for PROG in $DEPENDS; do
[[ -x "$(command -v $PROG)" ]] || {
echo "$PROG MISSING! Exiting."
exit 0
}
done
}
x_or() { # Here is where the magic happens
# The next two lines strip out $ characters, or an
# * and anything after it (checksum)
HEAD="${SENTENCE%\**}"
TAIL="${HEAD//\$}"
# Convert ASCII string into hex and read into an array.
# Each element in the array gets preceded by "0x"
HEXVAL="$(xxd -pu <<< ${TAIL})"
HEXARRAY=($(printf '%s' "${HEXVAL%0a}" | sed -e 's/../0x& /g'))
# Loop through the array and do the xor, initially start $XOR at 0
for (( x=0; x<"${#HEXARRAY[@]}"; x++ )); do
XOR=0x$(printf '%02x' "$(( ${XOR:-0} ^ ${HEXARRAY[$x]} ))")
done
# Strip off the 0x from the result
CLEAN=${XOR#0x}
printf '%s\n' "${CLEAN^^}"
}
main() {
case "${1:-}" in
"") # No input specified, read from stdin
depcheck
read -r SENTENCE
x_or
;;
*) # Input was provided, use that
depcheck
SENTENCE="$1"
x_or
;;
esac
}
# Main
# ====
main "$@"
シェルスクリプトを書くとき、私はいつも外部プログラム、さらにはsedやxxdのような一般的なプログラムの使用を取り除く方法を見つけようとします。シェル組み込み機能のみを使用して上記の作業を行う方法を知っている人がいる場合は、教えてください。
更新:Satoの方法を考慮した新機能があります。これにより、上記の外部プログラム呼び出しと関連するdepcheck機能を完全に削除できます。
x_or() { # Create a hex XOR checksum of all the bytes
# Clean the line of $ character and anything before it
TAIL="${SENTENCE##*$}"
HEAD=${TAIL%\**}
LEN=${#HEAD}
# Loop through the string and do the xor
# initially start $XOR at 0
XOR=0
for (( x=0; x<$LEN; x++ )); do
(( XOR^=$(printf '%d' "'${HEAD:$x:1}'") ))
done
printf '%02X\n' "${XOR}"
}
"LC_CTYPE=C" を使用してこの関数を呼び出します。ここでできることはもっとありますが、これはとてもきれいです。
答え1
個人的に私はそうするでしょう:
#! /usr/bin/env bash
log() {
{
printf '%s: ' "${0##*/}"
printf "$@"
printf '\n'
} >&2
}
cksum() {
tot=${#1}
let len=tot-4
let res=0
while [ $len -gt 0 ]; do
let res^=$( LC_CTYPE=C printf '%d' "'${1:$len:1}'" )
let len--
done
let ptr=tot-2
if [ x"$( printf '%s' "${1:$ptr}" | tr a-f A-F )" != x"$( printf '%02X' $res )" ]; then
log '%s: invalid checksum (found %02X)' "$1" $res
fi
}
check () {
if expr "$2" : '\$.*\*[0-9a-fA-F][0-9a-fA-F]$' >/dev/null; then
cksum "$2"
else
log 'invalid input on line %d: %s' "$1" "$2"
fi
}
let cnt=0
if [ $# -ne 0 ]; then
while [ $# -gt 0 ]; do
let cnt++
check $cnt "$1"
shift
done
else
while read -r str; do
let cnt++
check $cnt "$str"
done
fi
shebangラインは主張していますbash
が、まだksh93r
andで動作するはずですzsh
。これに依存せず、xxd
従うべきスクリプトスタイルの例であると主張することもありません。 :)