BASH - 判断是否存在重复行(是/否)

7
我正在编写一个脚本来操作文本文件。
首先我想做的是检查是否存在重复条目,如果存在,询问用户是否保留或删除它们。
我知道如何显示重复行(如果存在),但我想学习的只是得到一个"是否存在重复项"的是/否答案。
似乎uniq会返回0,无论是否发现重复项,只要命令完成而没有问题。
有没有一个命令可以放在if-语句中,告诉我重复行是否存在?
我的文件非常简单,它只包含单列的值。

如果您不反对使用 Vim 手动过滤文本文件,我建议在 https://dev59.com/P3M_5IYBdhLWcg3wslbs 中使用 HighlightRepeats 方法。我经常用它来过滤重复的文件/文件夹,然后在过滤后的文件上应用 shell 命令。 - F.X.
1
@F.X 感谢您的回复。我想在我的脚本中用几行代码来完成这个任务。 - DMS
4个回答

10

我可能会使用awk来做这件事,但出于多样性考虑,这里是一个简短的管道以完成相同的事情:

$ { sort | uniq -d | grep . -qc; } < noduplicates.txt; echo $?
1
$ { sort | uniq -d | grep . -qc; } < duplicates.txt; echo $?
0

sort + uniq -d 确保只有重复的行(不需要相邻)被打印到stdout,而grep . -c 通过模拟wc -l命令计数这些行,并具有一个实用的副作用:如果不匹配(即计数为0),则返回1。 使用-q选项可以消除输出,因此在脚本中可以静默使用。

has_duplicates()
{
  {
    sort | uniq -d | grep . -qc
  } < "$1"
}

if has_duplicates myfile.txt; then
  echo "myfile.txt has duplicate lines"
else
  echo "myfile.txt has no duplicate lines"
fi

3
您可以使用 awk 结合布尔运算符 ||
# Ask question if awk found a duplicate
awk 'a[$0]++{exit 1}' test.txt || (
    echo -n "remove duplicates? [y/n] "
    read answer
    # Remove duplicates if answer was "y" . I'm using `[` the shorthand
    # of the test command. Check `help [`
    [ "$answer" == "y" ] && uniq test.txt > test.uniq.txt
)
|| 后面的块只有在 awk 命令返回 1 并找到重复项时才会被执行。
但为了基本理解,我也会展示一个使用 if 块的例子。
awk 'a[$0]++{exit 1}' test.txt

# $? contains the return value of the last command
if [ $? != 0 ] ; then
    echo -n "remove duplicates? [y/n] "
    read answer
    # check answer
    if [ "$answer" == "y" ] ; then
        uniq test.txt > test.uniq.txt            
    fi
fi

然而,[] 不只是其他编程语言中的括号。 [test bash 内置命令的同义词,而 ] 则是最后一个参数。你需要阅读 help [ 才能理解。


谢谢你的帮助。我会尝试你的代码。 - DMS

1

您可以使用以下 awk 单行命令进行 uniq=yes/no

awk '!seen[$0]{seen[$0]++; i++} END{print (NR>i)?"no":"yes"}' file
  • awk使用一个名为seen的唯一数组。
  • 每次我们将一个元素放入唯一数组中,计数器i++会递增。
  • 最后在END块中,我们比较记录的数量与该代码中唯一记录的数量:(NR>i)?
  • 如果条件成立,那么表示存在重复记录,我们输出no,否则输出yes

1
谢谢您的回复。您能否请解释一下您的代码是如何工作的? - DMS
是的,当然。已添加解释。 - anubhava

1
一个快速的bash解决方案:
#!/bin/bash

INPUT_FILE=words

declare -A a 
while read line ; do
    [ "${a[$line]}" = 'nonempty' ] && duplicates=yes && break
    a[$line]=nonempty
done < $INPUT_FILE

[ "$duplicates" = yes ] && echo -n "Keep duplicates? [Y/n]" && read keepDuplicates

removeDuplicates() {
    sort -u $INPUT_FILE > $INPUT_FILE.tmp
    mv $INPUT_FILE.tmp $INPUT_FILE
}

[ "$keepDuplicates" != "Y" ] && removeDuplicates

该脚本从INPUT_FILE逐行读取每行,并将其存储在关联数组a中,作为键,并将字符串nonempty设置为值。在存储值之前,它首先检查它是否已经存在-如果存在,则表示它找到了一个重复项,并设置了duplicates标志,然后退出循环。
稍后它只检查标志是否设置,并询问用户是否保留重复项。如果他们回答除Y以外的任何内容,则调用removeDuplicates函数,该函数使用sort -u删除重复项。${a[$line]}评估为关联数组a的键$line的值。[ "$duplicates" = yes ]是bash内置语法的测试。如果测试成功,则在&&之后的任何内容都会得到评估。
但请注意,awk解决方案可能更快,因此如果您希望处理更大的文件,则可能需要使用它们。

谢谢jkbkot!你能给我简要解释一下这段代码是如何工作的吗?我是新手 :) - DMS
@DMS 没问题,已添加解释。顺便说一句,点赞就足以表示感谢了 ;) 还有,请尽量接受其中一个答案,以保持网站的组织性。祝编码愉快! - Jakub Kotowski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接