使用 awk 并循环遍历目录中的文件

使用 awk 并循环遍历目录中的文件

我有一个 Reviews_folder,其中包含不同的文件,例如 hotel_72572.dat

每个文件包含许多评论,结构如下:

...
<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 ,它会计算reviews_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将确保如果正在查看的路径中没有文件,则会从 for 循环正常退出,而不是传递未扩展的 glob 来awk进行处理
  3. 将文件名作为$file而不是文字字符串传递file。 shell 变量需要在$名称前添加一个符号作为前缀,并且通常需要用双引号引起来。
  4. END子句中的一个小增强,用于awk在除以计数之前检查计数是否非零。

答案3

for file in "$1"将运行循环一次,并file设置为脚本第一个参数的文字值。由于"$1"被引用,其中的任何通配符都不会扩展。如果您将目录传递给脚本,您还会将目录名称传递给awk,并且它可能不太喜欢,我gawk说:

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

如果要单独对每个文件运行循环,请在适当的位置使用通配符。这里*将扩展为当前目录中的文件名,因为我们刚刚cd在那里做了一个,所以该文件名作为参数给出:

#!/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并让 shell 将文件名扩展到脚本命令行。"$@"扩展到命令行参数列表。


也就是说,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

相关内容