Как удалить строки, которые появляются в файле B, из другого файла A?

160

У меня есть большой файл A (состоящий из электронных писем), одна строка для каждого письма. У меня также есть другой файл B, который содержит другой набор писем.

Какую команду я бы использовал, чтобы удалить все адреса, которые появляются в файле B, из файла A.

Итак, если файл A содержал:

A
B
C

и файл B содержал:

B    
D
E

Тогда файл A должен остаться с:

A
C

Теперь я знаю, что это вопрос, который, возможно, задавался чаще, но я нашел только одну команду в сети, которая выдала мне ошибку с неправильным разделителем.

Любая помощь приветствуется! Кто-то наверняка придумает умную однострочку, но я не эксперт по оболочкам.

slhck
источник
1
Большинство, если ответы здесь для отсортированных файлов, а самый очевидный отсутствует, что, конечно, не ваша вина, но делает другой более полезным.
tripleee

Ответы:

204

Если файлы отсортированы (они есть в вашем примере):

comm -23 file1 file2

-23 подавляет строки, которые есть в обоих файлах или только в файле 2. Если файлы не отсортированы, направьте их через sort сначала ...

Смотрите страницу руководства здесь

Архетипический Павел
источник
8
comm -23 file1 file2 > file3выводит содержимое в файл1, а не в файл2, в файл3. И тогда mv file3 file1, наконец, очистить избыточное содержимое в file1.
Spectral
2
В качестве альтернативы используйте comm -23 file1 file2 | sponge file1. Очистка не требуется.
Socowi
Ссылка на справочную
Феликс Рабе
@Socowi Что такое губка? У меня нет этого в моей системе. (macos 10.13)
Феликс Рабе,
@FelixRabe, ну это утомительно. Заменено вашей ссылкой. Спасибо
Архетип Павел
85

grep -Fvxf <lines-to-remove> <all-lines>

  • работает с несортированными файлами
  • поддерживает порядок
  • это POSIX

Пример:

cat <<EOF > A
b
1
a
0
01
b
1
EOF

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

Вывод:

b
a
01
b

Объяснение:

  • -F: использовать буквенные строки вместо BRE по умолчанию
  • -x: рассматривать только совпадения, соответствующие всей строке
  • -v: печать не соответствует
  • -f file: взять шаблоны из данного файла

Этот метод медленнее для предварительно отсортированных файлов, чем другие методы, поскольку он более общий. Если скорость имеет значение, смотрите: Быстрый способ поиска строк в одном файле, которые не находятся в другом?

Вот быстрая автоматизация bash для работы в потоке:

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub вверх по течению .

использование:

remove-lines lines-to-remove remove-from-this-file

Смотрите также: /unix/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another

Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
55

awk на помощь!

Это решение не требует отсортированных входных данных. Вы должны предоставить fileB первым.

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA

возвращается

A
C

Как это работает?

NR==FNR{a[$0];next} идиома предназначена для хранения первого файла в ассоциативном массиве в качестве ключей для последующего теста «содержит».

NR==FNR проверяет, сканируем ли мы первый файл, где глобальный счетчик строк (NR) равен текущему счетчику строк файла (FNR).

a[$0] добавляет текущую строку в ассоциативный массив в качестве ключа, обратите внимание, что это ведет себя как набор, где не будет повторяющихся значений (ключей)

!($0 in a)мы сейчас в следующем файле (ах), inэто тест содержит, здесь он проверяет, находится ли текущая строка в наборе, который мы заполнили на первом шаге из первого файла, !отменяет условие. Здесь не хватает действия, которое по умолчанию{print} обычно не пишется явно.

Обратите внимание, что теперь это можно использовать для удаления слов из черного списка.

$ awk '...' badwords allwords > goodwords

с небольшим изменением он может очистить несколько списков и создать очищенные версии.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ...
karakfa
источник
полная оценка по этому вопросу. Чтобы использовать это в командной строке в GnuWin32 в Windows, замените одиночные кусочки на двойные кавычки. работает удовольствие. большое спасибо.
два боба
Это работает, но как я смогу перенаправить вывод в файл A в виде A (с новой строкой) B
Anand Builders
Я предполагаю, что вы имеете в виду A\nC, сначала записать временный файл и перезаписать исходный файл... > tmp && mv tmp fileA
karakfa
Полные оценки в этом от меня тоже. Этот awk занимает всего 1 секунду, чтобы обработать файл с 104 000 записей: +1:
MitchellK
При использовании этого в сценариях, сначала убедитесь, что fileBон не пустой (длиной 0 байт), потому что если это так, вы получите пустой результат вместо ожидаемого содержимого fileA. (Причина: FNR==NRприменимо к тому fileAвремени.)
Питер Нови
18

Другой способ сделать то же самое (также требует отсортированного ввода):

join -v 1 fileA fileB

В Bash, если файлы предварительно не отсортированы:

join -v 1 <(sort fileA) <(sort fileB)
Приостановлено до дальнейшего уведомления.
источник
7

Вы можете сделать это, если ваши файлы не отсортированы

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a

--new-line-formatпредназначен для строк, которые находятся в файле b, но не в a, --old-..предназначен для строк, которые находятся в файле a, но не в b, --unchanged-..предназначен для строк, находящихся в обоих. %Lделает так, чтобы строка печаталась точно.

man diff

Больше подробностей

AEC
источник
1
Вы говорите, что это будет работать, если файлы не отсортированы. Какие проблемы возникают, если они отсортированы? Что делать, если они частично отсортированы?
Карлос Макасает
1
Это было в ответ на решение выше, которое предложило использовать commкоманду. commтребует сортировки файлов, поэтому, если они отсортированы, вы также можете использовать это решение. Вы можете использовать это решение независимо от того, отсортирован файл или нет
aec
7

Это уточнение хорошего ответа @ karakfa может быть заметно быстрее для очень больших файлов. Как и в случае с этим ответом, ни один файл не должен быть отсортирован, но скорость обеспечивается за счет ассоциативных массивов awk. Только файл поиска хранится в памяти.

Эта формулировка также допускает возможность использования только одного конкретного поля ($ N) во входном файле для сравнения.

# Print lines in the input unless the value in column $N
# appears in a lookup file, $LOOKUP;
# if $N is 0, then the entire line is used for comparison.

awk -v N=$N -v lookup="$LOOKUP" '
  BEGIN { while ( getline < lookup ) { dictionary[$0]=$0 } }
  !($N in dictionary) {print}'

(Другое преимущество этого подхода заключается в том, что он легко модифицирует критерий сравнения, например, обрезая начальные и конечные пробелы.)

вершина горы
источник
Это сложнее использовать в кросс-платформенном сценарии в угловом корпусе, чем в другом лайнере. Тем не менее,
снимаю шляпу
2

Вы можете использовать Python:

python -c '
lines_to_remove = set()
with open("file B", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("file A", "r") as f:
    for line in [line.strip() for line in f.readlines()]:
        if line not in lines_to_remove:
            print(line)
'
Привет пока
источник
2

Ты можешь использовать - diff fileA fileB | grep "^>" | cut -c3- > fileA

Это будет работать для файлов, которые также не отсортированы.

Дарпан
источник
-1

Для удаления общих строк между двумя файлами вы можете использовать команду grep, comm или join.

grep работает только для небольших файлов. Используйте -v вместе с -f.

grep -vf file2 file1 

Это отображает строки из файла1, которые не соответствуют ни одной строке в файле2.

comm - это служебная команда, которая работает с лексически отсортированными файлами. Он принимает два файла в качестве входных данных и создает три текстовых столбца в качестве выходных данных: только строки в первом файле; строки только во втором файле; и строки в обоих файлах. Вы можете подавить печать любого столбца, используя соответственно параметры -1, -2 или -3.

comm -1 -3 file2 file1

Это отображает строки из файла1, которые не соответствуют ни одной строке в файле2.

Наконец, есть объединение, служебная команда, которая выполняет объединение равенства для указанных файлов. Его опция -v также позволяет удалить общие строки между двумя файлами.

join -v1 -v2 file1 file2
Аакарш Гупта
источник
Все это уже было дано в других ответах. Для вашего grep нужно -F, или вы получите странные результаты, когда строки выглядят как регулярные выражения
Архетип Павел