【问题标题】:RPi: Bash script while read loop fails after first lineRPi:第一行后读取循环失败的 Bash 脚本
【发布时间】:2016-02-14 00:14:13
【问题描述】:

我最近买了一个 Slice of Radio - https://www.wirelessthings.net/slice-of-radio-wireless-rf-transciever-for-the-raspberry-pi,我用 Raspbian 将它连接到我的 RPi 中的串行端口。

我有一个温度+湿度传感器,它连接到 Slice of Radio 并定期发送包含多个信息的字符串,可以从串行端口 (/dev/ttyAMA0) 在 RPi 中读取这些信息。

为了阅读并利用这些信息,我决定编写一个 Bash 脚本。此信息与 12 个字符的字符串连接传输,并且每个字符串始终以一组固定的字符(在本例中为 aSD)开头。

这个 Bash 脚本应该在无限循环中运行,以确保定期发送的所有信息都按应有的方式被捕获和处理。

为此,以下小脚本可以正常工作:

    #!/bin/bash
    IFS='aSD'
    while read -r -n 12 char1 char2 char3 char4 char5
    do
    date=`/bin/date -u +%F@%X`
    echo $date
    echo  "1= ""$char1"
    echo  "2= ""$char2"
    echo  "3= ""$char3"
    echo  "4= ""$char4"
    echo  "5= ""$char5"
    done < /dev/ttyAMA0

并输出以下内容(每行 4 是我需要的信息):

    2016-02-13@23:06:12
    1= 
    2= 
    3= 
    4= RHUM77.0-
    5= 
    2016-02-13@23:06:12
    1= 
    2= 
    3= 
    4= TEMP20.0-
    5= 
    2016-02-13@23:11:08
    1= 
    2= 
    3= 
    4= RHUM77.0-
    5= 
    2016-02-13@23:11:08
    1= 
    2= 
    3= 
    4= TEMP19.9-
    5= 
    2016-02-13@23:11:08
    1= 
    2= 
    3= 
    4= BATT3.32-
    5= 
    2016-02-13@23:11:08
    1= 
    2= 
    3= 
    4= 
    5= LEEPING-

如果我运行以下脚本(我的简化版本):

    !/bin/bash
    log_file="/home/pi/logs/Temp_$(date +%Y%m%d).log"
    log_file_copy="/home/pi/logs/Temp_$(date +%Y%m%d)_copy.log"
    sensor="/home/pi/sensor"

    IFS='aSD'
    while read -r -n 12 char1 char2 char3 value char5
    do
    date=`/bin/date -u +%F@%X`
    msgid=$( /bin/echo $value | cut -c1-4 )
    echo $msgid
    if [ "$msgid" = "TEMP" ]; then

    #Write current temperature to log file
    log=`/bin/date -u +%F@%X`
    log+=" | Current Temperature: "
    temp=$( /bin/echo $value | cut -c5-8 )
    log+=$temp
    log+=" ºC"
    /bin/echo $log>>$log_file
    elif [ "$msgid" = "RHUM" ]; then

    #If log file backup still exists, start by deleting it
    if [ -f "$log_file_copy" ]; then
            /bin/rm $log_file_copy > /dev/null 2>&1 &
    fi
    #If log file already exists, start by creating a backup
    if [ -f "$log_file" ]; then
            /bin/mv $log_file $log_file_copy > /dev/null 2>&1 &
    fi

    #Write current relative humidity to log file
    log=`/bin/date -u +%F@%X`
    log+=" | Current Relative Humidity: "
    rhum=$( /bin/echo $value | cut -c5-8 ) 
    log+=$rhum
    log+=" %"
    /bin/echo $log>>$log_file
    elif [ "$msgid" = "BATT" ]; then

    #Write current battery voltage to log file
    log=`/bin/date -u +%F@%X`
    log+=" | Current Battery Voltage: "
    volt=$( /bin/echo $value | cut -c5-8 )
    if [ "$volt" != "LOW-" ]; then
            volt="LOW"
            log+=$volt
    else
            log+=$volt
            log+=" V"
    fi
    /bin/echo $log>>$log_file
    fi

    #Store values to display in website - personal.xively.com
    time=`/bin/date -u +%FT%XZ`
    if [ -n "$temp" ]; then
    /bin/echo "$time,$temp">>/home/pi/cosm/sensor/temperature/cosm.csv
    fi
    if [ -n "$rhum" ]; then
    /bin/echo "$time,$rhum">>/home/pi/cosm/sensor/humidity/cosm.csv
    fi
    if [ -n "$volt" ]; then
    /bin/echo "$time,$volt">>/home/pi/cosm/sensor/voltage/cosm.csv
    fi
    /bin/bash /home/pi/cosm/sensor/upload-cosm.sh > /dev/null 2>&1 &
    log=`/bin/date -u +%F@%X`
    log+=" | Values sent to xively.com"
    /bin/echo $log>>$log_file

    done < /dev/ttyAMA0

这就是它处理的全部内容:

    RHUM
    RHUM
    RHUM

如果我取出最后一段代码(将值发送到 xively.com),它会显示(这是我需要的):

    RHUM
    TEMP
    RHUM
    TEMP
    RHUM
    TEMP

我花了很多时间试图找出为什么循环不遍历所有记录而只处理第一个记录。

任何人都可以阐明或提供任何可行的替代方案吗?

提前感谢您的帮助。

【问题讨论】:

  • 你能显示原始输入吗?当您说“这就是它处理的全部内容”时-您的意思是RHUM 是输出吗? “它显示了 [...] 我需要什么” - 你能具体说明你需要什么吗?
  • RHUM 是第一行的输出,因为这是我在程序中只有 ECHO 的结果。 TEMP 是第二行的输出,所以我希望它出现在 RHUM 之后。基本上,我需要它将每 12 个字符处理为一行...

标签: bash while-loop


【解决方案1】:

为了清楚起见,我编辑了脚本,并根据我根据您的第一组输出所做的输入运行它。我不认为循环跳过了记录 - 也许输入与您的预期不符。以下版本将原始数据捕获到文件中,因此您可以根据原始数据验证结果。

#!/bin/bash

PATH=/bin:/usr/bin

log_file="/home/pi/logs/Temp_$(date +%Y%m%d).log"
log_file_raw=${log_file/./_raw.}

while read -r -n 12 dozen
do
    echo -n "$dozen" >> $log_file_raw

    date=`date -u +%F@%X`
    key=${dozen:3:4}
    val=${dozen:7}
    val=${val%-}

    case $key in
    TEMP)
        log="$date | Current Temperature: $val ºC"
        csv=/home/pi/cosm/sensor/temperature/cosm.csv
    ;;
    RHUM)
        log="$date | Current Relative Humidity: $val %"
        csv=/home/pi/cosm/sensor/humidity/cosm.csv
    ;;
    BATT)
        log="$date | Current Battery Voltage: $val V"
        csv=/home/pi/cosm/sensor/voltage/cosm.csv
    ;;
    *)
        continue
    ;;
    esac

    echo "$log" >> $log_file
    echo "${date/@/T}Z,$val" >> $csv

    /bin/bash /home/pi/cosm/sensor/upload-cosm.sh > /dev/null 2>&1 &
    echo "$date | Values sent to xively.com" >> $log_file

done < /dev/ttyAMA0

exit 0

一个重要的变化是这个脚本不会改变 IFS。 IFS 的每个字符都是一个分隔符。一个很好的例子是 char5 的 LEEPING-,当输入是(大概)aSDSLEEPING-。相反,此脚本使用参数扩展的内置 bash 子字符串功能。

我省略了日志轮换,因为我想建议您长期登录到 syslog。 logger 命令将向系统日志发送消息。在大多数系统上,这些日志已经定期轮换。

【讨论】:

  • 您的建议非常有效:从 IFS 转移到使用参数扩展的内置 bash 子字符串功能确实成功了。我还有一个问题要问您:考虑到我永远不知道每次迭代中发送的字符总数(可以是 24 - RHUM 和 TEMP 或更多 - RHUM、TEMP、BATT),有没有办法确定我何时达到字符串的结尾,能够知道我对该行的处理完成了吗?
  • 猜测(我没有其中之一),如果您没有大小限制进行单次读取,它将消耗当时可用的所有字符。然后,您可以使用 for 循环对每十几个字符进行操作,例如 while read -r buf; do for ((i=0; i&lt;${#buf}; i+=12)); do echo ${buf:i:12}; done; done &lt; /dev/ttyAMA0
  • 不幸的是,在 /dev/ttyAMA0 上运行 read -r 永远不会完成(根据我从使用 bash -x 运行脚本中可以理解的内容),因此您的示例不会产生任何数据。还有其他想法吗?
  • 而不是read -r bufbuf=$(&lt;/dev/ttyAMA0) 怎么样? (我通常使用反引号,但在这里似乎无法正确引用它们。)
  • 好的,你可以试试buf=$(cat /dev/ttyAMA0)
猜你喜欢
  • 2019-01-11
  • 1970-01-01
  • 2019-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多