Способ устранения внешних команд из скрипта для генерации контрольной суммы NMEA 0183 xor с помощью bash

Способ устранения внешних команд из скрипта для генерации контрольной суммы NMEA 0183 xor с помощью bash

Недавно у меня возникла необходимость сгенерировать и проверить контрольные суммы NMEA 0183 с помощью bash, но я не смог найти достаточно информации, которая помогла бы мне сделать именно то, что мне нужно.

Предложения NMEA 0183 начинаются с $ и заканчиваются * и двумя символами, которые являются шестнадцатеричным xor всех байтов между $ и *. Пример:

$INGGA,230501.547,2118.97946,N,15752.60495,W,2,08,1.1,5.17,M,,,0,0001*02

Эта утилита преобразует строку в шестнадцатеричный код и выполняет операцию 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. Если кто-то знает способ сделать вышеизложенное, используя только встроенные функции оболочки, пожалуйста, напишите.

Обновление: Вот новая функция, учитывающая метод Сато. Она позволяет полностью исключить внешние вызовы программ и связанную с ними функцию 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и zsh. Никакой зависимости от xxd. Также не претендует на то, чтобы быть примером стиля написания скриптов, которому нужно следовать. :)

Связанный контент