باش ، لينكس: ضبط الفرق بين ملفين نصي
-
22-09-2019 - |
سؤال
لدي ملفان A
-nodes_to_delete
و B
-nodes_to_keep
. يحتوي كل ملف على العديد من الخطوط ذات المعرفات الرقمية.
أريد أن أحصل على قائمة المعرفات الرقمية الموجودة nodes_to_delete
ولكن ليس في nodes_to_keep
, ، على سبيل المثال
.
القيام بذلك ضمن قاعدة بيانات postgreSQL بطيئة بشكل غير معقول. أي طريقة أنيقة للقيام بذلك في باش باستخدام أدوات Linux CLI؟
تحديث: يبدو أن هذا عمل بيثوني ، لكن الملفات كبيرة حقًا. لقد حلت بعض المشكلات المماثلة باستخدام uniq
, sort
وبعض تقنيات نظرية مجموعة. كان هذا حوالي اثنين أو ثلاثة أوامر من حجم أسرع من معادل قاعدة البيانات.
المحلول
ال بالاتصالات القيادة تفعل ذلك.
نصائح أخرى
لقد أوضح لي أحدهم كيف أفعل هذا بالضبط منذ شهرين ، وبعد ذلك لم أتمكن من العثور عليه لفترة من الوقت ... وبينما كنت أبحث عن سؤالك. ها هو :
set_union () {
sort $1 $2 | uniq
}
set_difference () {
sort $1 $2 $2 | uniq -u
}
set_symmetric_difference() {
sort $1 $2 | uniq -u
}
يستخدم comm
- سيقارن خط ملفين مصنفين سطرا.
الجواب القصير على سؤالك
سيعيد هذا الأمر خطوطًا فريدة من نوعها إلى Deletenodes ، ولكن ليس الخطوط في keepnodes.
comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
مثال الإعداد
دعنا ننشئ الملفات المسماة keepNodes
و deleteNodes
, ، واستخدامها كمدخلات غير مصورة لـ comm
أمر.
$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)
بشكل افتراضي ، يطبع تشغيل Comm بدون وسيطات 3 أعمدة مع هذا التصميم:
lines_unique_to_FILE1
lines_unique_to_FILE2
lines_which_appear_in_both
باستخدام ملفات المثال لدينا أعلاه ، قم بتشغيل Comm بدون وسيطات. لاحظ الأعمدة الثلاثة.
$ comm <(sort keepNodes) <(sort deleteNodes)
amber
ann
bob
قمع إخراج العمود
قمع العمود 1 أو 2 أو 3 مع -n ؛ لاحظ أنه عند إخفاء العمود ، يتقلص المسافة البيضاء.
$ comm -1 <(sort keepNodes) <(sort deleteNodes)
ann
bob
$ comm -2 <(sort keepNodes) <(sort deleteNodes)
amber
bob
$ comm -3 <(sort keepNodes) <(sort deleteNodes)
amber
ann
$ comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
ann
$ comm -2 -3 <(sort keepNodes) <(sort deleteNodes)
amber
$ comm -1 -2 <(sort keepNodes) <(sort deleteNodes)
bob
الفرز مهم!
إذا قمت بتنفيذ Comm دون فرز الملف أولاً ، فإنه يفشل بأمان مع رسالة حول الملف الذي لم يتم فرزه.
comm: file 1 is not in sorted order
comm
تم تصميمه خصيصًا لهذا النوع من حالات الاستخدام ، لكنه يتطلب إدخالًا مرتبة.
awk
يمكن القول أنه أداة أفضل لهذا لأنها مستقيمة إلى الأمام إلى حد ما للعثور على اختلاف محدد ، لا يتطلب ذلك sort
, ، ويوفر مرونة إضافية.
awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete
ربما ، على سبيل المثال ، ترغب فقط في العثور على الفرق في الأسطر التي تمثل أرقامًا غير سالبة:
awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r {
a[$0]
next
} $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete
ربما تحتاج إلى طريقة أفضل للقيام بذلك في Postgres ، يمكنني أن أراهن تمامًا على أنك لن تجد طريقة أسرع للقيام بذلك باستخدام ملفات مسطحة. يجب أن تكون قادرًا على القيام بانضمام داخلي بسيط وافتراض أن كلا من كولو المعرفات مفهرسة يجب أن تكون سريعة للغاية.
لذلك ، هذا يختلف قليلاً عن الإجابات الأخرى. لا أستطيع أن أقول إن برنامج التحويل البرمجي C ++ هو بالضبط "أداة Linux CLI" ، ولكن التشغيل g++ -O3 -march=native -o set_diff main.cpp
(مع الكود أدناه في main.cpp
يمكن أن تفعل الخدعة):
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
للاستخدام ، ما عليك سوى تشغيل set_diff B A
(ليس A B
, ، حيث B
هو nodes_to_keep
) وسيتم طباعة الفرق الناتج إلى stdout.
لاحظ أنني غفرت بعض أفضل الممارسات C ++ للحفاظ على الكود أكثر بساطة.
يمكن إجراء العديد من التحسينات الإضافية للسرعة (بسعر المزيد من الذاكرة). mmap
سيكون أيضًا مفيدًا بشكل خاص لمجموعات البيانات الكبيرة ، لكن ذلك سيجعل الكود أكثر مشاركة.
منذ أن ذكرت أن مجموعات البيانات كبيرة ، اعتقدت أن القراءة nodes_to_delete
قد يكون الخط في وقت واحد فكرة جيدة لتقليل استهلاك الذاكرة. النهج المتبع في الكود أعلاه غير فعال بشكل خاص إذا كان هناك الكثير من الخداع في nodes_to_delete
. أيضا ، لا يتم الحفاظ على النظام.
شيء أسهل للنسخ واللصق في bash
(أي تخطي إنشاء main.cpp
):
g++ -O3 -march=native -xc++ -o set_diff - <<EOF
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
EOF