我有一个包含一些数字的文件
$ 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 行,并将各列粘贴在一起-
作为分隔符。 head
和tail
分别用于修剪第一列的最后一行和第二列的第一行,以实现必要的偏移。结果列表是通过管道传送到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 的答案一样。