我一直在使用socat
通过UDP提取ASCII流并将其写入文件。以下就是这样一条线。
socat UDP-RECV:$UDP_PORT,reuseaddr - | cat >> $INSTRUMENT_$JDAY_RAW &
收到的每个流已经由发件人使用ts
(moreutils的一部分)加上时间戳,其中包括年份,Julian日,小时,分钟,秒和毫秒。如果Julian日更改,接收端的JDAY变量不会重新初始化,并且cat会使用昨天的时间戳将管道数据保存到同一个文件中。
以下是socat收到的udp流的示例。它被记录在20hz。
2015 317 06 34 43 303 winch680 000117.9 00000000 00000000.0
2015 317 06 34 43 353 winch680 000117.5 00000000 00000000.0
bash
中是否有某种方式我可以接收socat
收到的每一行,检查jday
时间戳字段,并根据该时间戳更改输出文件?
答案 0 :(得分:1)
不在cat
。你需要一个[ not bash
]脚本(例如perl / python或C程序)。
替换:
socat UDP-RECV:$UDP_PORT,reuseaddr - | cat >> $INSTRUMENT_$JDAY_RAW &
使用:
socat UDP-RECV:$UDP_PORT,reuseaddr - | myscript &
myscript
的样子:
while (1) {
get_data_from_socat_on_stdin();
if (jdaynew != jdayold) {
close_output_file();
jdayold = jdaynew;
}
if (output_file_not_open)
open_output_file(jdaynew);
write_data_to_output_file();
}
答案 1 :(得分:0)
您可以使用bash中的read
内置程序解析输入流。您可以使用$ help read
获取更多信息。它通常使用空格分隔标记。如果您提供了输出结果的两行预览,则可能更容易提供帮助。
必须在启动cat命令之前定义变量$INSTRUMENT
和$JDAY
,因为cat
将在文件开始写入之前打开文件。
如果以某种方式从每一行中提取$JDAY
和$INSTRUMENT
,您可以使用以下bash片段(假设socat读取的行看起来像<INSTRUMENT> <JDAY> <TS> yaddi yadda ...
):
function triage_per_day () {
while read INSTRUMENT JDAY TS REST; do
echo "$TS $REST" >> "${INSTRUMENT}_${JDAY}_RAW";
done
}
triage_per_day < <(socat UDP-RECV:"${UDP_PORT}",reuseaddr -)
如果你想变得更加漂亮,可以使用文件句柄来帮助bash运行得更快。只要日期相同,您就可以使用文件描述符重定向来保持输出到同一文件。这将最小化文件打开次数并关闭bash必须执行的操作。
function triage_per_day () {
local LAST_JDAY=init
exec 5>&1 # save stdout
exec 1>&2 # echos are sent to stderr until JDAY is redefined
while read INSTRUMENT JDAY TS REST; do
if [[ "$JDAY" != "$LAST_JDAY" ]]; then
# we need to change output file
# send stdout to file in append mode
exec 1>>"${INSTRUMENT}_${JDAY}_RAW"
LAST_JDAY="${JDAY}"
fi
echo "$TS $REST"
done
exec 1>&5 # restore stdout
exec 5>&- # close stdout copy
}
triage_per_day < <(socat UDP-RECV:"${UDP_PORT}",reuseaddr -)
如果你想用不同的字符标记你的行而不是空格,比如','逗号,你可以在本地修改特殊变量IFS
:
function extract_ts () {
local IFS=,; # special bash variable: internal-field-separator
# $REST will contain everything after the third token. it is a good
# practice to specify one more name than your last token of interest.
while read TOK1 TS REST; do
echo "timestamp is $TS";
done
}
如果您需要更精细地处理每一行以提取时间戳和其他字段,您可以改为执行外部程序(python / perl / cut / awk / grep等),但这比简单地坚持使用bash内置函数,如read
或echo
。如果您必须这样做,速度是一个问题,您可以考虑将脚本更改为另一种语言,以提供您所需的表达能力。如果您需要花哨的正则表达式,您可能还希望在手册中查看bash Pattern substitution
。
function extract_ts () {
# store each line in the variable $LINE
while read LINE; do
TS="$(echo "$LINE" | ...)";
echo "Timestamp is $TS";
done
}
推荐做法
另外,我应该提一下,如果你打算将它们用作文件名参数,最好用双引号括起你的bash变量(比如在答案中)。如果名称包含空格或特殊字符,则尤其如此 - 例如可以从日期或时间派生的文件名中预期。如果您的变量扩展为空(由于人为或编程错误),位置参数将会丢失,有时会出现错误的影响。
考虑:
# copy two files to the directory (bad)
$ cp file1 file2 $MYDIR
如果$MYDIR
未定义,则此命令相当于使用file1的内容覆盖file2。将此与cp file1 file2 "$MYDIR"
进行对比,因为目标""
不存在,因此会提前失败。
我在您的问题中看到的另一个问题来源是变量名称后跟下划线_
,如$INSTRUMENT
。这些应该用大括号{ }
包围。
INSTRUMENT=6
BAR=49
echo $INSTRUMENT_$BAR # prints '49', but you may have expected 6_49
因为_
是变量名中的有效字符,bash会尝试在INSTRUMENT
之后贪婪地“粘合”'_'以匹配可能的最长有效变量名称,即{{1} }。但是,此变量未定义,并扩展为空字符串,因此您将剩下其余部分$INSTRUMENT_
。此示例可以正确地重写为:
$BAR
答案 2 :(得分:0)
这是适合我的代码。 输入的udp流如下所示:
2015 317 06 34 43 303 winch680 000117.9 00000000 00000000.0
#!/bin bash
# This code creates a function which reads the fields in the
# udp stream into a table
# and uses the fields in the table to determine output.
UDP_PORT=5639
function DATAOUT () {
while read YR JDY MIN SEC MSEC INST TENS SPEED LINE; do
echo "$YR $JDY $HR $MIN $SEC $MSEC $INST $TENS $SPEED $LINE" >> "${INST}_${JDY}_RAW";
done
}
DATAOUT < <(socat udp-recv:${UDP_PORT},reuseaddr -)