Bash脚本用于从变量中包含的相应行中减去值

时间:2014-04-18 00:53:48

标签: bash awk

我认为没有人问过这个问题:我一直在研究一个小的bash脚本来提取一些数据,而我的想法是将今天的数据与昨天的数据进行比较以找到用法。

Today的文件如下:

150
100
50

Yesterday的文件如下:

145
99
20

当两个文件的内容存储在同名变量中并打印出结果时,我正在寻找能够从Today 中减去{1}}的第1行的内容。

Yesterday

从包含文件的命令行开始,它可以工作,但是从脚本中读取包含数据的变量的内容,它打印出第一个Total 5 1 30 然后找不到文件:

cat

有什么想法吗?

4 个答案:

答案 0 :(得分:2)

更新:此答案基于$Today$Yesterday引用文件的假设,如OP的命令行示例;事实证明,这些变量实际上包含数据

但是,这个答案仍然正确地解释了附加问题:单引号 {{1}中对 shell 变量的错误引用程序。

它还使用awkpaste提供了一个更简单的(事实证明:更快 - 感谢,JS웃)替代命令。


awk不会在您的$Yesterday计划中展开,因为awk程序整体都包含在引号中,这意味着任何其中的shell变量引用未被扩展。

要将shell变量值传递到awk程序,请使用awk选项:

-v varName=varValue
  • cat "$Today" | awk -v Yesterday="$Yesterday" '{n=$0; getline < Yesterday; print" " n-$0}' 定义了 -v Yesterday="$Yesterday" 变量awk,然后可以在Yesterday内使用{没有引号,没有$前缀) {1}}程序。

或者,您可以将awkpaste合并:

awk
  • paste file1 file2 | awk '{ print $1 - $2 }' 合并pastefile1中的相应行,file2可以轻松解析并执行算术运算。

答案 1 :(得分:2)

我看到此标记为,但您也可以使用bc。此处TodayYesterday是包含您的数据的文件名:

$ echo Total; paste -d- Today Yesterday | bc
Total
5
1
30
$ 

<强>更新

因为我们现在知道$Today$Yesterday是包含换行符分隔列表的bash变量,所以这是实现所需内容的另一种方法,它只使用bash内置功能。如果您拥有大量数据集,则无需生成外部流程(awkpastebc等)将提高性能。

# Create array versions of the variables
TodayArr=($Today)
YesterdayArr=($Yesterday)

# Loop over the list of indices in the Today array and do bash-based arithmetic
for i in ${!TodayArr[@]}; do
    echo $(( ${TodayArr[$i]} - ${YesterdayArr[$i]} ))
done

上面的警告是bash内置算术$(( ))只处理整数(通常是64位签名)。如果您的值包含小数,那么您必须再次使用bc或其中一个awk解决方案。 for循环看起来像这样:

# Loop over the list of indices in the Today array and do bc-based arithmetic
for i in ${!TodayArr[@]}; do
    echo "${TodayArr[$i]} - ${YesterdayArr[$i]}"
done | bc

答案 2 :(得分:1)

这是awk的一种方式:

$ awk 'NR==FNR{a[NR]=$1;next}{print $1-a[FNR]}' yesterdaysfile todaysfile
5
1
30
  • 将昨天的文件读入一个以行号NR索引的数组。
  • 读取整个文件后,使用行号FNR减去带有数组的todays文件。

NRFNR是存储行号的变量,当读取新文件时,差异为FNR会重置为1NR没有。因此明智地使用它们可以获得您寻求的结果。

性能检查:

$ seq 10000000 > f1

$ seq 10000000 > f2

$ time paste f1 f2 | awk '{ print $1 - $2 }' >/dev/null

real    0m12.894s
user    0m13.519s
sys     0m0.229s

$ time cat f1 | awk '{n=$0; getline < "f2"; print" " n-$0}' >/dev/null

real    0m14.615s
user    0m14.428s
sys     0m0.154s

$ time awk 'NR==FNR{a[NR]=$1;next}{print $1-a[FNR]}' f1 f2 >/dev/null

real    0m18.631s
user    0m17.459s
sys     0m1.094s

$ time paste -d- f1 f2 | bc >/dev/null

real    0m37.221s
user    0m32.027s
sys     0m6.535s

答案 3 :(得分:1)

鉴于$Today$Yesterday包含数据或者文件内容,而不是文件名,如果您想使用您的代码,能做到:

$ awk '{n=$1; getline < ARGV[2]; print" " n-$1}' <(echo "$Today") <(echo "$Yesterday")
 5
 1
 30
awk: warning: close of fd 62 (`/dev/fd/62') failed (Bad file descriptor)

但正如您所看到的,您将收到 gawk 的警告。正如mklement0的评论中所述,您可以通过在结尾处添加2>/dev/null来安全地忽略此警告,但只有在您确定它可以按预期工作之后。否则,您可能会遗漏一些重要的调试信息。

更惯用的方式是:

$ awk 'BEGIN  { print "Total"}
    NR == FNR { n[FNR] = $1;next} 
    NF        { print n[FNR] - $1 }' <(echo "$Today") <(echo "$Yesterday") 
Total
5
1
30