Удаление заглавных / строчных парочек в списке
Я собрал небольшой скрипт, который должен искать файлы определенного типа в каталоге, накапливая уникальное количество слов> 4 символа, но он не работает должным образом.
- Это не исключает одно и то же слово независимо от регистра.
- Я не уверен, как подвести итоги каждого слова, очевидно.
- Наконец, это эффективный способ сделать это (если это действительно сработало?).
Автор сценария:
#!/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