Як видалити рядки, які з’являються у файлі B, з іншого файлу A?


160

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

Яку команду я використовував би для видалення всіх адрес, що відображаються у файлі B, з файлу А.

Отже, якщо файл A містив:

A
B
C

і файл B містив:

B    
D
E

Тоді файл A слід залишити:

A
C

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

Будь-яка допомога буде дуже вдячна! Хтось напевно придумає розумний однолінійний, але я не фахівець із снарядів.



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

Відповіді:


204

Якщо файли відсортовані (вони є у вашому прикладі):

comm -23 file1 file2

-23пригнічує рядки, що знаходяться в обох файлах, або лише у файлі 2. Якщо файли не відсортовані, передайте їх через sortпершу ...

Дивіться сторінку людини тут


8
comm -23 file1 file2 > file3виведе вміст у файл1, а не у файл2, у файл3. І тоді mv file3 file1остаточно очистить зайвий вміст у file1.
Спектрал

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: взяти шаблони з даного файлу

Цей спосіб є більш повільним для попередньо відсортованих файлів, ніж інші методи, оскільки він більш загальний. Якщо швидкість також має значення, див.: Швидкий спосіб пошуку рядків в одному файлі, які не є в іншому?

Ось швидка автоматична система автоматичного керування для поточної роботи:

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} idiom призначена для зберігання першого файлу в асоціативному масиві як ключів для подальшого тесту "містить".

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 ...

повні позначки на цьому. Щоб використовувати це в командному рядку в GnuWin32 в Windows, замініть одиничні нибли на подвійні лапки. працює частування. велике дякую.
twobob

Це працює, але як я зможу перенаправляти вихід на файлA у вигляді A (З новим рядком) B
Anand Builders

Я думаю, ви маєте на увазі A\nC, спершу напишіть у тимчасовий файл і перезаписуйте оригінальний файл... > tmp && mv tmp fileA
karakfa

Повні позначки в цьому і від мене. Цей awk займає 1 секунду, щоб обробити файл із 104 000 записів: +1:
MitchellK

Використовуючи це в сценаріях, переконайтеся, що спочатку перевірити, fileBчи не порожній (довжина 0 байтів), тому що якщо він є, ви отримаєте порожній результат замість очікуваного вмісту fileA. (Причина: FNR==NRстосуватиметься fileAтоді.)
Пітер Нові


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

для отримання детальної інформації


1
Ви кажете, що це буде працювати, якщо файли не відсортовані. Які проблеми виникають, якщо їх сортувати? Що робити, якщо вони частково відсортовані?
Карлос Макасает

1
Це було у відповідь на рішення вище, яке пропонувало використання commкоманди. commвимагає сортування файлів, тож якщо вони будуть відсортовані, ви також можете використовувати це рішення. Ви можете використовувати це рішення , незалежно від того , відсортований файл чи ні , хоча
АЕС

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}'

(Ще одна перевага цього підходу полягає в тому, що легко змінити критерій порівняння, наприклад, обрізати провідний та кінцевий пробіли.)


Це складніше використовувати в кутовому сценарії крос-платформи, ніж інший вкладиш. Однак капелюхи знімаються для виконання вистави
twobob

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 або приєднатися.

grep працює лише для невеликих файлів. Використовуйте -v разом з -f.

grep -vf file2 file1 

Тут відображаються рядки з file1, які не відповідають жодному рядку у file2.

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

comm -1 -3 file2 file1

Тут відображаються рядки з file1, які не відповідають жодному рядку у file2.

Нарешті, є з'єднання - команда утиліти, яка виконує об'єднання рівності у вказаних файлах. Опція -v також дозволяє видаляти загальні рядки між двома файлами.

join -v1 -v2 file1 file2

Все це вже було дано в інших відповідях. Вашому грепу потрібен -F, або ви отримаєте дивні результати, коли рядки будуть схожі на регулярні вирази
Архетипний Павло
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.