删除列表中的 uppercase/lowercase 个重复项
Removing uppercase/lowercase dupes in list
我编写了一个小脚本,该脚本应该在目录中搜索特定类型的文件,累积唯一字数 > 4 个字符,但它没有按预期工作。
- 不区分大小写不删除同一个词。
- 我不太确定如何清楚地计算每个单词的总数。
- 最后,这是一种有效的方法吗(如果真的有效?)。
脚本:
#!/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}'
我编写了一个小脚本,该脚本应该在目录中搜索特定类型的文件,累积唯一字数 > 4 个字符,但它没有按预期工作。
- 不区分大小写不删除同一个词。
- 我不太确定如何清楚地计算每个单词的总数。
- 最后,这是一种有效的方法吗(如果真的有效?)。
脚本:
#!/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}'