我有一个 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
- 因为你已经
cd
-ing 到"$1"
你不需要for file in "$1"
,但只需循环所需的文件扩展名for file in *.dat
- 该条件
test -f "$file" || continue
将确保如果正在查看的路径中没有文件,则会从 for 循环正常退出,而不是传递未扩展的 glob 来awk
进行处理 - 将文件名作为
$file
而不是文字字符串传递file
。 shell 变量需要在$
名称前添加一个符号作为前缀,并且通常需要用双引号引起来。 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