【问题标题】:Removing uppercase/lowercase dupes in list删除列表中的大写/小写重复
【发布时间】:2016-04-23 19:08:26
【问题描述】:

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

  1. 无论大小写,它都不会删除相同的单词。
  2. 我不知道如何计算每个单词的总数。
  3. 最后,这是一种有效的方法(如果它真的有效?)。

脚本:

#!/bin/bash

file_list=()
while IFS= read file ; do
    file_list=("${file_list[@]}" "$file")
    tr -sc 'A-Za-z' '\012' < "$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,但要在所有文件中计算它?

【问题讨论】:

  • “单词”是如何定义的?它是由空格/制表符/换行符分隔的字符串还是更多?
  • @RanyAlbegWein:a-zA-Z(四个字符 +),由空格、换行符、制表符分隔,我认为涵盖了它......这就是我想要的至少。谢谢

标签: linux bash macos grep wc


【解决方案1】:

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

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

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

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

【讨论】:

  • 第二部没有?它应该具有显示所有文件总数的预期结果。
  • 我不太确定如何让 find 部分像你的那样工作,所以缺少什么。我使用了你答案的第一部分,但我的其余代码如下:gist.github.com/anonymous/a2c44d1fd67f70bf84ccb36fb5c3d651
  • 发现不应该改变。括号之间的部分应与您的原始代码完全相同。
【解决方案2】:

你只需要:

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

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

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

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

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

awk -v RS='\\s' 'length()>3{cnt[tolower($0)]++} 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($0)]++} END{for (word in cnt) print cnt[word], word}'

【讨论】:

    【解决方案3】:

    以下使用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
    

    【讨论】:

    • 可能不起作用的一件事是 bash 版本过于具体;我希望这适用于 3.2+ 版本 - 谢谢
    • @NedSchneebly 它需要 Bash 4+。但将来可能对某人有用。
    猜你喜欢
    • 2018-05-31
    • 1970-01-01
    • 2019-10-17
    • 1970-01-01
    • 1970-01-01
    • 2019-06-24
    • 2023-02-02
    • 2021-09-14
    • 2021-04-25
    相关资源
    最近更新 更多