awkを使用してディレクトリ内のファイルを繰り返します。

awkを使用してディレクトリ内のファイルを繰り返します。

hotel_72572.datなどの他のファイルを含むReviews_folderがあります。

各ファイルには、次のように構成された多くのコメントが含まれています。

...
<Overall>4
...

私の目標は、averagereviews.shスクリプトを使用して、各ファイル(ホテル)のすべてのレビューの平均総数を計算することです。以下を実行すると、./averagereviews.sh path_to_reviews_folder 次のような結果が出力されます。

hotel_11212.dat 3.51
hotel_2121.dat 2.62
hotel_31212.dat 2.43
...

私のスクリプトは次のとおりです

#!/bin/bash
cd "$1" || exit 1
for file in "$1"; do
awk 'count+=sub(/<Overall>/, ""){sum+=$0}END{print sum/count}' file;
done

問題は、ファイルをディレクトリとして認識せず、hotel_*.datを入れると、各ファイルではなくreview_folder内のすべての既存のファイルの平均を計算することです。

答え1

シングルawkスクリプト(forループおよびマルチawkコールなし):

入力ファイルの例:

$ head reviews_folder/hotel_*.dat
==> reviews_folder/hotel_111.dat <==
<Overall>1
<Overall>4
<Overall>3

==> reviews_folder/hotel_222.dat <==
<Overall>11
<Overall>5
<Overall>7

==> reviews_folder/hotel_333.dat <==
<Overall>7
<Overall>4
<Overall>10

awk -F'>' 'fn && FILENAME != fn{ 
              sub(".*/", "", fn);
              print fn, sprintf("%.2f", sum/n); sum = 0
          }
          { sum += $2; n = FNR; fn = FILENAME }
          END{ 
              sub(".*/", "", fn);
              print fn, sprintf("%.2f", sum/n)
          }' reviews_folder/hotel_*.dat

出力:

hotel_111.dat 2.67
hotel_222.dat 7.67
hotel_333.dat 7.00

答え2

スクリプトをいくつか改善する

#!/bin/bash
cd "$1" || { printf 'unable to navigate to target\n' >&2; exit 1 ; }
for file in *.dat; do
    test -f "$file" || continue
    awk 'count+=sub(/<Overall>/, ""){sum+=$0}END{print (count)?(sum/count):0}}' "$file"
done
  1. 不要なファイル拡張子をcd-ingしたので、ループを繰り返します。"$1"for file in "$1"for file in *.dat
  2. この条件は、見ているパスにファイルがない場合に処理のためにtest -f "$file" || continue拡張されていないglobを渡すのではなく、forループが正常に終了することを保証します。awk
  3. $fileリテラル文字列の代わりにファイル名をasに渡しますfile。シェル変数は$名前の前に記号を付ける必要があり、通常は二重引用符で囲む必要があります。
  4. ENDawk分割する前に数がゼロでないことを確認するための節の小さな改善です。

答え3

for file in "$1"ループを一度実行し、fileスクリプトの最初の引数のリテラル値に設定されます。その中のワイルドカード文字は"$1"引用により拡張されません。ディレクトリをスクリプトに渡すと、ディレクトリ名も渡されますがawk、これはあまり気に入らない可能性がありますgawk

gawk: warning: command line argument `/tmp/test/' is a directory: skipped

各ファイルに対して個別にループを実行するには、適切な場所にワイルドカードを使用します。これは*現在のディレクトリのファイル名に拡張され、ちょうどcdそこに1つを作成したので、引数として提供されます。

#!/bin/sh
cd "$1" || exit 1 
for file in * ; do
    awk '...' "$file"
done

または、ファイル名のリストをスクリプトの引数として渡してから、次の手順を繰り返します。

#!/bin/sh
for file in "$@" ; do
    awk '...' "$file"
done

実際には、これを行い、myscript /some/path/hotel*.datシェルにファイル名をスクリプトコマンドラインに拡張させることができます。"$@"コマンドライン引数のリストに展開されます。


つまり、awk台本にも少し問題があります。作成したとおり、最初の規則の条件はですcount+=sub(/<Overall>/, "")。これはcount、追加がゼロでない限り、sub()今回返される内容に関係なく適用されます。つまり、ルールが複数回表示される{sum+=$0}たびにルールが実行されます。<Overall>加算せずに合計されますcount

次のようなものが必要な場合もあります。

awk '/^<Overall>/ {sub(/<Overall>/, ""); count += 1; sum += $0} END {print sum/count}' "$file"

ファイル名を表示するには、次のようにしますecho

#!/bin/sh
cd "$1" || exit 1 
for file in * ; do
    printf "%s " "$file"
    awk '/^<Overall>/ {sub(/<Overall>/, ""); count += 1; sum += $0} END {print sum/count}' "$file"
done

答え4

各ファイルに対して次のコマンドを使用すると、平均を取得できます。テストを経てうまく機能しました

入力する

<Overall>1
<Overall>4
<Overall>3

i=`awk '{print NR}' hotel_111.dat| tail -1 `

awk -F ">" -v i="$i" 'BEGIN{sum=0} {sum=sum+$2} END{print FILENAME;print  sum/i}' hotel_111.dat  | sed "N;s/\n/ /g"

出力

hotel_111.dat 2.66667

関連情報