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

时间:2010-12-06 17:29:39

标签: bash

我得到了这个文本文件,调用stock.txt,文本文件的内容是:

pepsi;drinks;3
fries;snacks;6
apple;fruits;9
baron;drinks;7
orange;fruits;2
chips;snacks;8

我需要使用bash-script来输出这个输出:

Total amount for drinks: 10
Total amount for snacks: 14
Total amount for fruits: 11
Total of everything: 35

我的直觉告诉我,我需要使用sed,group,grep和其他东西 我应该从哪里开始?

5 个答案:

答案 0 :(得分:1)

Pure 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

答案 1 :(得分:0)

我会将练习分解为步骤

步骤1:一次读取一行文件

while read -r line
do
    # do something with $line
done

第2步:模式匹配(饮料,零食,水果)并做一些简单的算术。这一步要求你对每一行进行标记,然后我会留下练习让你弄明白。

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

答案 2 :(得分:0)

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

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

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

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

示例:man read显示read命令的手册页。在大多数系统上,它将在less中打开,所以你应该按q退出手册(可能很有趣,但我花了一些时间才弄明白)

答案 3 :(得分:0)

执行此操作的简单方法是使用哈希表,该表由bash 4.x直接支持,当然可以在awk和perl中找到。如果您没有哈希表,则需要循环两次:一次收集第二列的唯一值,一次为总计。

有很多方法可以做到这一点。这是一个有趣的,不使用awk,sed或perl。我在这里使用的唯一外部工具是cut,sort和uniq。你甚至可以用更多的努力来取代cut。实际上,使用grep(grep $kind stock.txt)可以更容易地编写第5-9行,但我避免使用它来展示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初始化为零,以便每当启动新组时它始终重置。

第三行开始'totaling'循环,其中对于当前kind,我们找到其出现的总和。这里我们声明我们将在循环的每次迭代中从stdin中读取变量d

在第四行实际发生总计:使用shell arithmatic我们将d中的值添加到total中的值。

第五行结束while循环,然后描述其输入。我们通过<使用shell输入重定向来指定循环的输入,从而指定read命令的输入来自文件。然后我们使用process substitution来指定文件实际上是命令的结果。

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

在第八行,我们结束内部while-read循环,并指定它的输入来自stock.txt文件,然后我们管道整个循环的输出,现在只是所有匹配的行{{ 1}},到$kind并指示它只显示第三个字段,即数字字段。在第9行,我们然后结束进程替换命令,其输出是来自cut指定的组中的行的数字的换行符。

鉴于现在知道总数并知道种类,将结果打印到屏幕上是一件简单的事情。

答案 4 :(得分:0)

以下答案是OP的。由于它是在问题本身编辑而OP还没有回来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

当我回声时,它: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