循环文件中的行并从当前行中减去上一行

循环文件中的行并从当前行中减去上一行

我有一个包含一些数字的文件

$ cat file.dat
0.092593
0.048631
0.027957
0.030699
0.026250
0.038156
0.011823
0.013284
0.024529
0.022498
0.013217
0.007105
0.018916
0.014079

我想创建一个新文件,其中包含当前行与上一行的差异。预期输出应该是

$ cat newfile.dat
-0.043962
-0.020674
0.002742
-0.004449
0.011906
-0.026333
0.001461
0.011245
-0.002031
-0.009281
-0.006112
0.011811
-0.004837

认为这很简单,我从这段代码开始

f="myfile.dat"    
while read line; do
    curr=$line
    prev=

    bc <<< "$line - $prev" >> newfile.dat
done < $f

但我很快意识到我不知道如何访问文件中的前一行。我想我还需要考虑到在阅读第一行时不应进行减法。任何有关如何进行的指导将不胜感激!

答案1

$ awk 'NR > 1 { print $0 - prev } { prev = $0 }' <file.dat
-0.043962
-0.020674
0.002742
-0.004449
0.011906
-0.026333
0.001461
0.011245
-0.002031
-0.009281
-0.006112
0.011811
-0.004837

在 shell 循环调用中执行此操作bc很麻烦。上面使用一个简单的awk脚本,它逐一读取文件中的值,并且对于第一行之后的任何行,它都会打印您所描述的差异。

NR > 1 { print $0 - prev }如果我们到达第二行或更远的位置(NR是迄今为止读取的记录数,默认情况下“记录”是一行),则第一个块有条件地打印本行与上一行之间的差异。

第二个块{ prev = $0 },无条件设置prev为当前行上的值。

将输出重定向到以newfile.dat将结果保存在那里:

$ awk 'NR > 1 { print $0 - prev } { prev = $0 }' <file.dat >newfile.dat

有关的:


有人提到bc循环调用的速度很慢。下面是一种使用单次调用bc进行算术运算,同时仍在 shell 循环中读取数据的方法(我实际上不建议以这种方式解决这个问题,我只是在这里向对 co 感兴趣的人展示它) -中的过程bash

#!/bin/bash

coproc bc

{
    read prev

    while read number; do
        printf '%f - %f\n' "$number" "$prev" >&"${COPROC[1]}"
        prev=$number

        read -u "${COPROC[0]}" result
        printf '%f\n' "$result"
    done
} <file.dat >newfile.dat

kill "$COPROC_PID"

中的值${COPROC[1]}是 的标准输入文件描述符,bc${COPROC[0]}是 的标准输出文件描述符bc

答案2

使用一些简单的 GNU 实用程序,并且没有 shell 循环:

paste -d- <(head -n-1 file.dat) <(tail -n+2 file.dat) | bc

这里的想法是将输入文件复制到两列中;将第二列偏移 1 行,并将各列粘贴在一起-作为分隔符。 headtail分别用于修剪第一列的最后一行和第二列的第一行,以实现必要的偏移。结果列表是通过管道传送到bc进行评估所需的算术差异列表。

在线尝试一下


或者,如果您愿意sed,您可以这样做:

sed '1{s/$/-\\/;p;d};${p;d};s/.*/&\n&-\\/' file.dat | bc

这会复制每一行并插入到-\每行的第二个版本的末尾。第一行和最后一行被不同地处理以生成必要的表达式。 sed 的输出结果如下:

a-\
b
b-\
c
c-\
d

这些也是可以评估的有效算术差异bc。不能理解bc每隔一行末尾的续行反斜杠。

在线尝试一下

答案3

如果您想尝试强制 shell 脚本工作,那么您只是缺少一些初始化:

f=myfile.dat
prev=0
while read line; do
    bc <<< "$line - $prev"
    prev=$line
done < $f > newfile.dat

...我还将重定向移到了循环之外,只是为了节省一些 I/O。

bc解决方案不打印前导零,而awk 解决方案做。

答案4

我使用数组。我用它们来做一切事情。如果没有广泛研究手册页,我不记得 awk 和 sed 是如何工作的。这是我的做法。

f=( $(< file.dat) )
for ((num=1;num<=${#f[@]};num++))
do
    echo $(bc <<< ${f[$num]}-${f[(($num-1))]})>>differences.dat
done

我是这样理解的。它具有其他一些答案的令人反感的特征:一遍又一遍地循环和调用 bc 。但是,它只读取文件一次,就像使用 sed 和 awk 的答案一样。

相关内容