删除列表中的 uppercase/lowercase 个重复项

Removing uppercase/lowercase dupes in list

我编写了一个小脚本,该脚本应该在目录中搜索特定类型的文件,累积唯一字数 > 4 个字符,但它没有按预期工作。

  1. 不区分大小写不删除同一个词。
  2. 我不太确定如何清楚地计算每个单词的总数。
  3. 最后,这是一种有效的方法吗(如果真的有效?)。

脚本:

#!/bin/bash

file_list=()
while IFS= read file ; do
    file_list=("${file_list[@]}" "$file")
    tr -sc 'A-Za-z' '2' < "$file" | sort | uniq -c | egrep "\w{4,}" >> words.txt
done < <(find . -maxdepth 1 -type f -name "*.c")

# echo "${file_list[@]}"

cat words.txt | sort -u | sort -nr 
echo "" > words.txt

示例输出:

  38 char
  35 return
  25 static
  18 year
  18 char
  10 COLS
  10 CHAR

我如何删除上面示例中的欺骗词 char,但在所有文件中都计数?

首先,将转换为全小写作为管道的第一步。

tr A-Z a-z <"$file" | tr -sc a-z '2' | ...

其次,在整个事情的最后而不是在循环内进行排序和计数:

...
  tr A-Z a-z <"$file" | tr -sc a-z '2' 
done < <(find ...) | sort | uniq -c | egrep "\w{4,}" >words.txt

以下使用 Associative Arrays ( Bash 4 ) 将单词存储为键,并将其出现作为值:

declare -A arr
while read -r word; do
    arr[$word]=$(( ${arr[$word]} + 1 ))
done < <(find . -maxdepth 1 -type f -name '*.c' -exec grep -E '\w{4,}' {} \; | tr -s '[:space:]' \n)

是的,它可以执行得更快,但请注意:如果您将 find\; 命令终止更改为 +grep 也会产生文件名作为输出的一部分(在我们的例子中是关键)。我们不想要这种行为。因此,如果你有 GNU grep - 在 find+ 命令终止旁边添加 -h 选项。

引自man grep:

  -h, --no-filename
          Suppress the prefixing of file names on output.  This is the default when there is only one file (or only standard input) to search.

即:

find . -maxdepth 1 -type f -name '*.c' -exec grep -hE '\w{4,}' {} + | tr -s '[:space:]' \n

为了测试,我创建了以下内容:

$ cat 1.c 2.c 
char return
char    char    int
char
char    switch      return
int
CHAR switch
COLS
year
static
char
CHAR
INT
int
main
return case
long
double

我创建了一个名为sof的脚本,其中包含上面的相关代码加上一个declare -p arr来验证关联数组执行后内容:

$ ./sof
declare -A arr='([return]="3" [static]="1" [switch]="2" [int]="1" [CHAR]="2" [char]="6" [COLS]="1" [double]="1" [main]="1" [case]="1" [long]="1" [year]="1" )'

看起来不错,所以现在我们可以简单地根据您要求的输出进行打印:

$ for k in "${!arr[@]}";do v="${arr[$k]}"; printf '%s %s\n' "$v" "$k";done
1 static
3 return
2 switch
1 int
6 char
2 CHAR
1 COLS
1 main
1 double
1 case
1 long
1 year

您只需要:

awk -v RS='\s' 'length()>3{cnt[tolower([=10=])]++} END{for (word in cnt) print cnt[word], word}' *.c

以上使用 GNU awk 进行多字符 RS 和 \s,这是对其他 awks 的简单调整:

awk '{for (i=1;i<=NF;i++) if (length($i)>3) cnt[tolower($i)]++} END{for (word in cnt) print cnt[word], word}' *.c

关于您提出的问题,您当前的方法是否有效 - 不,它非常低效并且 运行 至少比我上面发布的脚本慢一个数量级。阅读 why-is-using-a-shell-loop-to-process-text-considered-bad-practice.

如果您需要对递归找到的所有文件执行此操作,那么这可能就是您所需要的:

awk -v RS='\s' 'length()>3{cnt[tolower([=12=])]++} END{for (word in cnt) print cnt[word], word}' $(find -type f -name '*.c' -print)

否则这样做:

find -type f -name '*.c' -print0 |
xargs -0 cat |
awk -v RS='\s' 'length()>3{cnt[tolower([=13=])]++} END{for (word in cnt) print cnt[word], word}'