Удаление заглавных / строчных парочек в списке

Я собрал небольшой скрипт, который должен искать файлы определенного типа в каталоге, накапливая уникальное количество слов> 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 в приведенном выше примере, но получить его счет во всех файлах?

3 ответа

Решение

Во-первых, преобразуйте все строчные буквы в качестве первого шага в вашем конвейере.

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

Все, что тебе нужно:

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

В связи с тем, что ваш вопрос задает, ваш нынешний подход эффективен - нет, он очень неэффективен и будет работать по крайней мере на порядок медленнее, чем сценарий, который я опубликовал выше. Прочитайте, почему использование цикла оболочки для обработки текста считается плохой практикой.

Если вам нужно сделать это для всех файлов, найденных рекурсивно, то это может быть все, что вам нужно:

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}'

Далее используются ассоциативные массивы ( 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"s \; прекращение команды до +, grep выдаст также имя файла как часть вывода (который в нашем случае является ключевым). Мы не хотим такого поведения. Таким образом, если у вас есть GNU grep - добавить -h вариант рядом find"s + прекращение команды.

Цитируется из 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
Другие вопросы по тегам