BASH - Сообщить, существуют ли повторяющиеся строки (y/n)

Я пишу скрипт для управления текстовым файлом.

Первое, что я хочу сделать, это проверить, существуют ли повторяющиеся записи, и если да, то спросить пользователя, хотим ли мы сохранить их или удалить.

Я знаю, как отображать повторяющиеся строки, если они существуют, но я хочу научиться просто получать ответ «да/нет» на вопрос «Существуют ли дубликаты?»

Кажется, что uniq вернет 0 либо в случае обнаружения дубликатов, либо нет, если команда выполнена без проблем.

Что это за команда, которую я могу поместить в оператор if только для того, чтобы сообщить мне, существуют ли повторяющиеся строки?

Мой файл очень простой, это просто значения в одном столбце.


person DMS    schedule 18.03.2014    source источник
comment
Если вы не против использования Vim для фильтрации текстовых файлов вручную, я рекомендую метод HighlightRepeats в stackoverflow.com/questions/1268032. Я часто использую его для фильтрации повторяющихся файлов/папок, а затем применяю команды оболочки к отфильтрованному файлу.   -  person F.X.    schedule 19.03.2014
comment
@F.X Спасибо за ваш ответ. Я хотел бы сделать это с помощью некоторых строк в моем сценарии.   -  person DMS    schedule 19.03.2014


Ответы (4)


Вы можете использовать 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

Однако [] — это не просто квадратные скобки, как в других языках программирования. [ — это синоним встроенной команды bash test, а ] — ее последний аргумент. Вам нужно прочитать help [, чтобы понять

person hek2mgl    schedule 18.03.2014
comment
Спасибо за вашу помощь. Я попробую ваш код. - person DMS; 19.03.2014

Я бы, вероятно, использовал 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 с полезным побочным эффектом, который возвращает 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
person Adrian Frühwirth    schedule 19.03.2014

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

person Jakub Kotowski    schedule 18.03.2014
comment
Спасибо jkbkot! Не могли бы вы дать мне краткое объяснение того, как работает этот код? Я новичок :) - person DMS; 19.03.2014
comment
@DMS нет проблем, добавлено объяснение. Кстати, в качестве благодарности достаточно проголосовать;) Кроме того, попробуйте принять один из ответов, чтобы сайт оставался организованным. Удачного кодирования! - person Jakub Kotowski; 19.03.2014

Вы можете сделать uniq=yes/no, используя этот однострочный awk:

awk '!seen[$0]{seen[$0]++; i++} END{print (NR>i)?"no":"yes"}' file
  • awk использует массив уникальных имен под названием seen.
  • Каждый раз, когда мы помещаем элемент в уникальный, мы увеличиваем счетчик i++.
  • Наконец, в блоке END мы сравниваем количество записей с уникальным количеством записей в этом коде: (NR>i)?
  • Если условие истинно, это означает, что есть повторяющиеся записи, и мы печатаем no, иначе печатается yes.
person anubhava    schedule 18.03.2014
comment
Спасибо за ваш ответ. Не могли бы вы объяснить мне, как работает ваша линия? - person DMS; 19.03.2014
comment
Да, конечно, добавлено объяснение. - person anubhava; 19.03.2014