web-dev-qa-db-ja.com

ファイルから複数のグループ行の平均を出力するスクリプト

これは、情報を抽出しようとしているcar_sales.txtファイルの「小さな」部分です(ファイル全体には約700行あり、ここにリストされているよりも多くの車両ブランドがあります)。

first_name,last_name,price_paid,brand,year Mann,Mathers,20500.79,Chevy,2012 Doug,Samual,21000.12,Dodge,2015 Walter,Gray,17000.87,Dodge,2010 Jessica,Garnet,17350.00,MINI,2009 Paula,Raymond,45300.87,BMW,2015 Willie,Reynolds,64950.05,BMW,2015 Sam,Collins,70200.35,Lexus,2014 Katy,Martinez,29580.84,Chevy,2012 Nicole,Davis,31650.60,Chevy,2009 Brenda,Gray,12400.56,Dodge,2012 Samantha,Fernandez,27900.21,MINI,2015 Eric,Woods,68900.85,BMW,2009 George,Luke,33453.91,BMW,2011 Mildred,Takey,46820.80,Lexus,2012

「brand」列と「price_paid」列を出力し(そして、すべての車について、ブランドごとに支払われた平均価格を見つけます)、ソート(a-z)し、最初の行の「header」を削除したいと思います。これは私が探している出力です(上記の例から):

BMW,53151.4 Chevy,27244.1 Dodge,16800.5 Lexus,58510.6 MINI,22625.1

今、私はこれを2日間運がなくて理解しようとして取り組んできました(私はこれが初めてです)、私が思いつくことができる最善の方法はこれです:

sed '1d' car_sales.txt |awk -F ',' '/Chevy/{print $3}' $1|awk '{total += $1; count ++}END{print "Chevy," total/count}'

明らかに、それは私が探しているものではありません。 「1つの」Brand/Price_Paidの平均出力が必要な場合は、それでうまくいきます。探している1つの「パターン」を入力するだけで、平均価格が支払われます。

ただし、car_sales.txtファイルですべてのブランドに支払われた平均価格を取得して出力する方法を探しています。そして、私がリストした部分の5つだけよりも多くのブランドがあります(約50以上のブランド)。

私は自分が持っている3冊の本を読んでオンラインで何時間もスキャンしましたが、私の人生の間、それを理解することはできません。たぶん私は適切な場所を探していません。awkが答えになると思いましたが、それはとても巨大です。事前に助けてくれてありがとう。

それから私はそれを実現する方法を考え出したと思い、このスクリプトを書き始めました。論理的には、頭の中でうまくいくように見えました。最初の関数からの出力を2番目の関数への出力として使用することを考えました。残念ながら、これもうまくいきませんでした。私は正しい方向に進んでいると思いましたが、そうではありませんでした。

#!/bin/bash

#This will output the car "brand"
function brand {
        sed '1d' $1| cut -d ',' -f 4 |sort|uniq 
}

#The output of function "brand", will be the pattern for function "average"
function average {
    awk -F ',' '/'"$names"'/{print $3}' $1|awk '{total += $1; count ++}END{print "'$names'" "," total/count}'

}

brand $1
names=$(brand)
average $1 $names
4
Jaymes Deen

awk配列は文字列でインデックス付けされるため、1つの配列を使用してそのブランドのこれまでの合計価格を保持し、別の配列を使用してそのブランドで表示されるレコードの数を保持できます。

「brand」はフィールド4であるため、次のようにawkの配列にインデックスを付けることができます。

total_price[$4] += $3        # accumulate total price for this brand
count[$4] += 1               # increment count of records for this brand

最後に、配列へのキーをループし、平均を計算しながら出力をフォーマットします。

POSIX awkにはソート関数が含まれていないため、awkコマンドの出力を標準のUnixsortコマンドにパイプします。

これを試してください:

脚本

#!/bin/sh

#first_name,last_name,price_paid,brand,year
#print for each brand, the average price paid

awk -F, '
    NR == 1 {
        next                        # skip header
    }
    {
        price_paid[$4] += $3        # accumulate total price for this brand       
        count[$4] += 1              # increment count of records for this brand
    }
    END {
        for (brand in price_paid) {
            printf "%s,%7.2f\n", brand, price_paid[brand] / count[brand]
        }
    }
' < "${1:?filename required}" | sort

注釈/説明

  1. awkコマンドを呼び出し、フィールド区切り文字をコンマ(,)に設定し、スクリプトとして、この行の単一引用符と数行下の次の一重引用符の間のすべてを渡します。

    awk -F, '
    
  2. ヘッダーのスキップ:現在のレコード番号が1の場合、現在の行(最初の行)のすべての処理をスキップして、次の入力行を取得します。

        NR == 1 {
            next                        # skip header
        }
    
  3. ブランドごとの累積価格合計(これはすべての行で実行されます):
    配列price_paidおよびcountは、brand文字列によってインデックス付けされます。
    現在支払われている価格($3)をこのブランドのprice_paid合計に追加します。
    このブランドのレコード数を増やします。

        {
            price_paid[$4] += $3        # accumulate total price for this brand    
            count[$4] += 1              # increment count of records for this brand
        }
    
  4. 出力テーブルの印刷:すべての入力が処理されたら、キー(brand)をステップスルーしてprice_paid配列に移動し、brandごとにbrandを印刷します。そして、そのbrandprice_paidの平均:

        END {
            for (brand in price_paid) {
                printf "%s,%7.2f\n", brand, price_paid[brand] / count[brand]
            }
       }
    
  5. スクリプト引数を終了し、filenameパラメーターからの入力をリダイレクトし、awkコマンドの出力をsortコマンドにパイプします。

    ' < "${1:?filename required}" | sort
    

一重引用符(')は、awkへのスクリプト引数を終了します。
< "${1:?filename required}"は、awkの標準入力を最初のコマンドラインパラメーターで指定されたファイル名からスクリプトにリダイレクトします。パラメータがない場合、シェルは「ファイル名が必要です」を含むエラーメッセージを出力し、エラーステータスで終了します。

5
RobertL