使用Bash命令操作数据文本文件?

5
我被给了一个名为 stock.txt 的文本文件,文件的内容如下:
pepsi;drinks;3
fries;snacks;6
apple;fruits;9
baron;drinks;7
orange;fruits;2
chips;snacks;8

我需要使用bash脚本来生成以下输出:
Total amount for drinks: 10
Total amount for snacks: 14
Total amount for fruits: 11
Total of everything: 35

我的直觉告诉我我需要使用sed、group、grep和其他一些命令。
我应该从哪里开始?


嗨,Rafe,这就是我担心的事情..考试..哈哈..因此我需要诚实,并自己找到解决方案,但我需要你的指导,我所拥有的课程笔记非常有限,缺乏例子。 - bashington02
1
你使用的工具有限制吗?awk 可以很容易地完成这个任务... - David Gelhar
嗨大卫,没有限制,但请先使用简单的那个,因为我不想展示给我的讲师看我的代码有多好,相反,我想先学会如何使用。我想我忘了提到awk。 - bashington02
这确实是awk的呼声,但它(几乎)不是bash :) 坚持使用sed,拆分你的行,并使用expr来进行累积。 - KevinDTimm
我已经从这个问题中删除了答案,并在下面发布了它的维基。 - Kyll
显示剩余6条评论
5个回答

1
我会将这个练习分解成步骤。
步骤1:逐行读取文件。
while read -r line
do
    # do something with $line
done

步骤2:模式匹配(饮料、零食、水果)并进行一些简单的算术运算。这一步需要对每行进行标记化处理,我会留下一个练习让你自己解决。

if [[ "$line" =~ "drinks" ]]
then
    echo "matched drinks"
    .
    .
    .
fi 

谢谢Amir,我像你说的那样使用了while和if语句,现在它可以工作了。但是我更好奇如何使用sed/grep/awk来实现这一点。有什么指南可以给我吗? - bashington02

1

纯Bash。一种很好的关联数组应用:

declare -A category                  # associative array
IFS=';'
while read name cate price ; do
  ((category[$cate]+=price))
done < stock.txt

sum=0
for cate in ${!category[@]}; do       # loop over the indices
  printf "Total amount of %s: %d\n" $cate ${category[$cate]}
  ((sum+=${category[$cate]}))
done

printf "Total amount of everything: %d\n" $sum

0

使用哈希表是解决此问题的简单方法,bash 4.x 直接支持它,当然也可以在 awk 和 perl 中找到。如果您没有哈希表,则需要循环两次:一次收集第二列的唯一值,一次求和。

有许多方法可以做到这一点。这里有一个有趣的方法,不使用 awk、sed 或 perl。我在这里使用的唯一外部实用程序是 cut、sort 和 uniq。你甚至可以用更多的努力替换 cut。事实上,第 5-9 行甚至可以更容易地用 grep 写成 (grep $kind stock.txt),但我避免了这样做,以展示 bash 的强大功能。

for kind in $(cut -d\; -f 2 stock.txt | sort | uniq) ; do
    total=0
    while read d ; do
        total=$(( total+d ))
    done < <(
        while read line ; do 
            [[ $line =~ $kind ]] && echo $line
        done < stock.txt | cut -d\; -f3
    )

    echo "Total amount for $kind: $total" 
done

在这里我们失去了原始输出的严格排序。一个练习是找到一种不这样做的方法。

讨论: 第一行描述了一个使用简单管道和cut的子shell。我们从stock.txt文件中读取第三个字段,使用;分隔字段,这里写成\;以便shell不会解释它。结果是来自stock.txt的值的以换行符分隔的列表。这被传输到sort,然后uniq。这执行了我们的“分组”步骤,因为管道将输出第二列中的按字母顺序排列的项目列表,但无论输入文件中出现多少次,每个项目只列出一次。

第一行还有一个典型的for循环:对于子shell产生的每个项,我们循环一次,在变量kind中存储该项的值。这是分组步骤的另一半,确保每个“Total”输出行只出现一次。

在第二行中,total初始化为零,以便在开始新组时总是重置。

第三行开始了“总计”循环,在当前的kind中,我们找到其出现次数的总和。在这里,我们声明每次循环将从stdin读取变量d

第四行实际上进行了总计:使用shell算术d中的值加到total中的值。

第五行结束了while循环,然后描述了它的输入。我们使用shell输入重定向通过<指定循环的输入,因此也指定了read命令的输入来自文件。然后,我们使用进程替换指定该文件实际上将是一个命令的结果。

在第六行,将开始提供 while-read 循环的命令。它本身是另一个 while-read 循环,这次读入变量 line。在第七行,通过 条件结构 执行测试。这里我们使用 [[=~ 运算符进行测试,它是一种模式匹配运算符。我们正在测试是否 $line 与当前的 $kind 匹配。

在第八行,我们结束内部 while-read 循环,并指定其输入来自 stock.txt 文件,然后将整个循环的输出(现在只是所有匹配 $kind 的行)传输到 cut 并指示它仅显示第三个字段,即数字字段。在第九行,我们结束了进程替换命令,其输出是由 kind 指定的组中的行的换行分隔数字列表。

考虑到现在已知总数和种类,将结果打印到屏幕上就是一件简单的事情。


0

这里有一个关于在bash中处理逗号分隔文件的简短描述:

http://www.cyberciti.biz/faq/unix-linux-bash-read-comma-separated-cvsfile/

你可以做类似的事情。只需将IFS从逗号更改为分号。

哦,还有一个关于学习bash的提示:man是你的朋友。使用此命令查看所有(或大多数)命令和实用程序的手册页。

示例:man read 显示read命令的手册页。在大多数系统上,它将在less中打开,因此您应该通过按q退出手册(可能很有趣,但我花了一段时间才想出来)


谢谢提供的链接。我正在阅读它=)但是awk看起来非常有吸引力..似乎很容易解决我的问题。http://lowfatlinux.com/linux-awk.html#DATA。但正如KeinDTimm所说,我应该先使用sed/grep..无论如何,我会逐一探索的。 - bashington02
awk确实比这个酷多了。好吧,我能给你的最好建议是:两者都做吧:D - Goran Jovic
嗨Goran,我成功地使用while和简单的if语句完成了它。 我还没有涉及sed / grep和.... AWK..您有使用这三个命令的提示吗? - bashington02
很好。您在这个问题中提供了一些关于awk的好建议:https://dev59.com/em865IYBdhLWcg3wM7u0 ...当然,还有man awk - Goran Jovic
“help” 对于 bash 内置命令如 declareset 等也是一个好帮手。 - sorpigal

0
下面的答案是原帖作者的。由于它在问题本身中被编辑并且原帖作者已经6年没有回来了,我将从问题中删除答案并在这里发布它作为维基页面。

我的答案是,为了得到总价,我使用以下代码:

...
PRICE=0
IFS=";"     # new field separator, the end of line   
while read name cate price
do
let PRICE=PRICE+$price
done < stock.txt
echo $PRICE

当我使用echo命令时,输出的是35,这是正确的。现在我将继续使用awk命令来获取子类别的结果。

整体解决方案:

谢谢大家,我已经成功地完成了。以下是我的代码:

#!/bin/bash
INPUT=stock.txt
PRICE=0
DRINKS=0
SNACKS=0
FRUITS=0
old_IFS=$IFS      # save the field separator   
IFS=";"     # new field separator, the end of line   
while read name cate price
do
    if [ $cate = "drinks" ]; then   
        let DRINKS=DRINKS+$price
fi

if [ $cate = "snacks" ]; then
        let SNACKS=SNACKS+$price
fi

if [ $cate = "fruits" ]; then
        let FRUITS=FRUITS+$price
fi

# Total
let PRICE=PRICE+$price
done < $INPUT

echo -e "Drinks: " $DRINKS
echo -e "Snacks: " $SNACKS
echo -e "Fruits: " $FRUITS
echo -e "Price " $PRICE 
IFS=$old_IFS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接