如何修改 bash 脚本,分别找出每个输入文本文件中最长的两个单词?
技术老齐
原脚本已经把初始化放在外层文件循环内,并在每个文件处理完后输出结果,符合分别统计每个文件的要求。新增的第二组变量也放在同一位置初始化即可。
如何更新前两名
用 max_word、max_len 保存第一名,用 max_word2、max_len2 保存第二名。每读到一个单词,按顺序判断:
- 如果
len > max_len,先把原第一名移到第二名,再将当前单词设为第一名。 - 否则,如果
len > max_len2,只更新第二名。
使用 if ... elif 可以避免把当前单词同时填入两个位置。更新第一名前,必须先保存原第一名。
完整代码
这段代码直接读取文本文件,按空格、制表符和换行分隔单词,也能避免原来 for j in $(strings $i) 中的文件名展开问题。
#!/usr/bin/env bash
for i; do
# 只处理可读的普通文件
[[ -f $i && -r $i ]] || continue
# 每处理一个文件,都重新初始化
max_word=
max_len=0
max_word2=
max_len2=0
while IFS= read -r line || [[ -n $line ]]; do
IFS=$' \t' read -r -a words <<< "$line"
for j in "${words[@]}"; do
len=${#j}
if [[ $len -gt $max_len ]]; then
# 原第一名移到第二名
max_word2=$max_word
max_len2=$max_len
max_word=$j
max_len=$len
elif [[ $len -gt $max_len2 ]]; then
max_word2=$j
max_len2=$len
fi
done
done < "$i"
# 当前文件处理完毕后立即输出
printf "%s : '%s' (%d characters), '%s' (%d characters)\n" \
"$i" "$max_word" "$max_len" "$max_word2" "$max_len2"
done
保存为 longest-two.sh 后,通过 Bash 传入多个文件:
bash longest-two.sh a.txt b.txt
假设 a.txt 包含:
apple banana watermelon
b.txt 包含:
cat horse
输出为:
a.txt : 'watermelon' (10 characters), 'banana' (6 characters)
b.txt : 'horse' (5 characters), 'cat' (3 characters)
for i; do 等价于 for i in "$@"; do,会依次处理每个文件参数。四个统计变量在每次外层循环中重置,因此前一个文件的结果不会带入下一个文件。
并列、重复和读取方式
这里的“最长的两个”指按长度排序的前两个单词,允许长度相同。例如,apple peach cat 会得到 apple 和 peach。比较时使用严格大于号 -gt,长度相同时优先保留先出现的单词。
重复出现的单词会分别参与统计,所以 banana banana cat 的两个结果都是 banana。如果需要两个不同的单词,就要额外排除重复值;如果需要两个不同的长度,则要调整并列判断。
空文件会输出两个 '' (0 characters);只有一个单词时,第二个位置为 '' (0 characters),表示没有第二个单词。
读取方式也会影响统计结果:
strings用于提取可打印字符串,默认通常会过滤较短的字符串。统计普通文本中的单词时,直接读取文件更合适。for j in $(...)会对命令输出进行单词拆分和文件名展开,文本中的*、?等内容可能被展开成当前目录中的文件名。示例使用数组,并通过"${words[@]}"保留每个单词的原值。- 示例仅按空白分词,不去除标点,因此
hello,的逗号也计入长度。${#j}的字符计数受当前 locale 影响;处理多字节文本时,应使用与文件编码匹配的 locale。
备注:内容仅供参考。
