it-swarm.asia

باش أداة للحصول على خط nth من ملف

هل هناك طريقة "قانونية" للقيام بذلك؟ أنا أستخدم head -n | tail -1 الذي يقوم بالخدعة ، لكنني أتساءل عما إذا كانت هناك أداة Bash تستخرج بشكل خاص خطًا (أو مجموعة من الخطوط) من ملف.

أعني بكلمة "قانوني" برنامج وظيفته الرئيسية هو القيام بذلك.

486
Vlad Vivdovitch

head وأنبوب التوجيه مع tail سيكون بطيئًا في ملف ضخم. أود أن أقترح sed مثل هذا:

sed 'NUMq;d' file

حيث NUM هو رقم السطر الذي تريد طباعته ؛ لذلك ، على سبيل المثال ، sed '10q;d' file لطباعة السطر العاشر من file.

تفسير:

سيتم إنهاء NUMq على الفور عندما يكون رقم السطر NUM.

d سيحذف السطر بدلاً من طباعته ؛ يتم منع ذلك في السطر الأخير لأن q يتسبب في تخطي بقية البرنامج النصي عند الإنهاء.

إذا كان لديك NUM في متغير ، فستحتاج إلى استخدام علامات اقتباس مزدوجة بدلاً من واحدة:

sed "${NUM}q;d" file
648
anubhava
sed -n '2p' < file.txt

سوف يطبع الخط الثاني

sed -n '2011p' < file.txt

خط 2011

sed -n '10,33p' < file.txt

السطر 10 حتى السطر 33

sed -n '1p;3p' < file.txt

الخط الأول والثالث

وما إلى ذلك وهلم جرا...

لإضافة خطوط مع sed ، يمكنك التحقق من هذا:

sed: أدخل خطًا في موضع معين

255
jm666

لديّ موقف فريد حيث يمكنني تحديد الحلول المقترحة في هذه الصفحة ، ولذا فإنني أكتب هذه الإجابة كتوحيد للحلول المقترحة مع أوقات تشغيل مضمنة لكل منها.

اقامة

لدي 3.261 غيغابايت ASCII ملف بيانات نصي مع زوج واحد من مفاتيح القيمة لكل صف. يحتوي الملف على 3،339،550،320 صفًا في المجموع ويتحدى فتح أي محرر قمت بتجربته ، بما في ذلك الانتقال إلى Vim. أحتاج إلى تعيين هذا الملف من أجل التحقيق في بعض القيم التي اكتشفتها تبدأ فقط حول الصف ~ 500،000،000.

لأن الملف به العديد من الصفوف:

  • أحتاج إلى استخراج مجموعة فرعية فقط من الصفوف لفعل أي شيء مفيد مع البيانات.
  • ستستغرق قراءة كل صف يؤدي إلى القيم التي أهتم بها وقتًا طويلاً.
  • إذا قرأ الحل الصفوف التي أهتم بها واستمر في قراءة بقية الملف ، فسيضيع الوقت في قراءة حوالي 3 مليارات صفًا غير ذي صلة ويستغرق 6 مرات أكثر من اللازم.

أفضل سيناريو هو حل يستخلص سطرًا واحدًا فقط من الملف دون قراءة أي من الصفوف الأخرى في الملف ، لكن لا يمكنني التفكير في كيفية تحقيق ذلك في Bash.

لأغراض عقلاني ، لن أحاول قراءة الخطوط البالغ عددها 500.000.000 التي أحتاجها لمشكلتي. بدلاً من ذلك ، سأحاول استخراج الصف 50،000،000 من 3،339،550،320 (مما يعني أن قراءة الملف الكامل ستستغرق 60x أطول من اللازم).

سوف أستخدم time المدمج لقياس أداء كل أمر.

خط الأساس

أولاً ، دعنا نرى كيف حل headtail:

$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0

real    1m15.321s

خط الأساس للصف 50 مليون هو 00: 01: 15.321 ، إذا ذهبت مباشرة للصف 500 مليون فمن المحتمل أن يكون حوالي 12.5 دقيقة.

يقطع

أنا مشكوك في هذا ، لكن الأمر يستحق العناء:

$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0

real    5m12.156s

استغرق هذا واحد 00: 05: 12.156 لتشغيل ، وهو أبطأ بكثير من الأساس! لست متأكدًا من قراءته للملف بالكامل أو حتى 50 مليون خط قبل إيقافه ، لكن بغض النظر عن ذلك ، لا يبدو هذا حلاً قابلاً للتطبيق للمشكلة.

AWK

لقد قمت بتشغيل الحل فقط باستخدام exit لأنني لم أنتظر حتى يتم تشغيل الملف الكامل:

$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0

real    1m16.583s

تم تشغيل هذا الرمز في 00: 01: 16.583 ، وهو أبطأ من ثانية واحدة تقريبًا ، ولكن لا يزال هذا التحسن غير أساسي في الأساس. على هذا المعدل ، إذا تم استبعاد أمر الخروج ، فربما استغرق الأمر حوالي 76 دقيقة تقريبًا لقراءة الملف بأكمله!

بيرل

قمت بتشغيل حل بيرل الحالي أيضًا:

$ time Perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0

real    1m13.146s

تم تشغيل هذا الرمز في 00: 01: 13.146 ، والذي هو ~ 2 ثانية أسرع من خط الأساس. إذا قمت بتشغيله على 500.000.000 كاملة ، فربما يستغرق الأمر حوالي 12 دقيقة.

sed

الإجابة العليا على السبورة ، ها هي نتائجي:

$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0

real    1m12.705s

تم تشغيل هذا الرمز في 00: 01: 12.705 ، وهو أسرع بثلاث ثوانٍ من الخط الأساسي ، وأسرع ~ 0.4 ثانية من Perl. إذا كنت أقوم بتشغيلها على الصفوف الكاملة البالغة 500.000.000 ، فربما استغرق الأمر 12 دقيقة تقريبًا.

mapfile

لدي bash 3.1 وبالتالي لا يمكنني اختبار حل ملف mapfile.

استنتاج

يبدو ، في معظم الأحيان ، أنه من الصعب تحسين الحل headtail. في أحسن الأحوال ، يوفر حل sed زيادة بنسبة 3٪ تقريبًا في الكفاءة.

(النسب المئوية محسوبة بالصيغة % = (runtime/baseline - 1) * 100)

الصف 50،000،000

  1. 00: 01: 12.705 (-00: 00: 02.616 = -3.47٪) sed
  2. 00: 01: 13.146 (-00: 00: 02.175 = -2.89٪) Perl
  3. 00: 01: 15.321 (+00: 00: 00.000 = + 0.00٪) head|tail
  4. 00: 01: 16.583 (+00: 00: 01.262 = + 1.68٪) awk
  5. 00: 05: 12.156 (+00: 03: 56.835 = + 314.43٪) cut

صف 500،000،000

  1. 00: 12: 07.050 (-00: 00: 26.160) sed
  2. 00: 12: 11.460 (-00: 00: 21.750) Perl
  3. 00: 12: 33.210 (+00: 00: 00.000) head|tail
  4. 00: 12: 45.830 (+00: 00: 12.620) awk
  5. 00: 52: 01.560 (+00: 40: 31.650) cut

الصف 3،338،559،320

  1. 01: 20: 54.599 (-00: 03: 05.327) sed
  2. 01: 21: 24.045 (- 00: 02: 25.227) Perl
  3. 01: 23: 49.273 (+00: 00: 00.000) head|tail
  4. 01: 25: 13.548 (+00: 02: 35.735) awk
  5. 05: 47: 23.026 (+04: 24: 26.246) cut
78
CaffeineConnoisseur

مع awk ، فهو سريع جدًا:

awk 'NR == num_line' file

عندما يكون هذا صحيحًا ، يتم تنفيذ السلوك الافتراضي لـ awk: {print $0}.


إصدارات بديلة

إذا كان حجم ملفك ضخمًا ، فمن الأفضل exit بعد قراءة السطر المطلوب. بهذه الطريقة يمكنك توفير وقت وحدة المعالجة المركزية.

awk 'NR == num_line {print; exit}' file

إذا كنت ترغب في إعطاء رقم السطر من متغير bash يمكنك استخدام:

awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file   # equivalent
44
fedorqui

واو ، كل الاحتمالات!

جرب هذا:

sed -n "${lineNum}p" $file

أو واحدة من هذه اعتمادا على إصدار Awk الخاص بك:

awk  -vlineNum=$lineNum 'NR == lineNum {print $0}' $file
awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file
awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file

( قد تضطر إلى تجربة الأمر nawk أو gawk).

هل هناك أداة تعمل فقط على طباعة هذا الخط المحدد؟ ليست واحدة من الأدوات القياسية. ومع ذلك ، فإن sed هو الأقرب والأبسط للاستخدام.

26
David W.
# print line number 52
sed '52!d' file

مخطوطات مفيدة من سطر واحد للسيد

20
Steven Penny

يتم وضع علامة على Bash لهذا السؤال ، وإليك طريقة Bash (≥4): mapfile مع خيار -s (تخطي) و -n (العد).

إذا كنت بحاجة إلى الحصول على السطر 42 من ملف file:

mapfile -s 41 -n 1 ary < file

في هذه المرحلة ، سيكون لديك صفيف ary - الحقول التي تحتوي على سطور file (بما في ذلك السطر الجديد الزائد) ، حيث تخطينا أول 41 سطرًا (-s 41) ، وتوقفت بعد قراءة سطر واحد (-n 1). هذا هو حقا الخط 42. لطباعته:

printf '%s' "${ary[0]}"

إذا كنت بحاجة إلى مجموعة من الخطوط ، فقل النطاق من 42 إلى 666 (ضمنيًا) ، ثم قل أنك لا تريد أن تفعل الرياضيات بنفسك ، ثم قم بطباعتها على stdout:

mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"

إذا كنت بحاجة إلى معالجة هذه السطور أيضًا ، فليس من المناسب حقًا تخزين السطر الجديد الزائد. في هذه الحالة ، استخدم خيار -t (تقليم):

mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"

يمكنك الحصول على وظيفة تفعل ذلك لك:

print_file_range() {
    # $1-$2 is the range of file $3 to be printed to stdout
    local ary
    mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3"
    printf '%s' "${ary[@]}"
}

لا توجد أوامر خارجية ، فقط بنى Bash!

20
gniourf_gniourf

وفقًا لاختباراتي ، من حيث الأداء وسهولة القراءة ، فإن توصيتي هي:

tail -n+N | head -1

N هو رقم السطر الذي تريده. على سبيل المثال ، ستطبع tail -n+7 input.txt | head -1 السطر السابع من الملف.

ستقوم tail -n+N بطباعة كل شيء بدءًا من السطر N ، و head -1 ستتوقف بعد سطر واحد.


البديل head -N | tail -1 ربما يكون أكثر قابلية للقراءة قليلاً. على سبيل المثال ، سيؤدي هذا إلى طباعة السطر السابع:

head -7 input.txt | tail -1

عندما يتعلق الأمر بالأداء ، لا يوجد اختلاف كبير بالنسبة للأحجام الصغيرة ، ولكن سيتفوق عليه في tail | head (من الأعلى) عندما تصبح الملفات ضخمة.

من المهم معرفة sed 'NUMq;d' ، ولكنني أزعم أن عدد الأشخاص الخارجين عن الصندوق سيكون مفهوما أكثر من حل الرأس/الذيل كما أنه أبطأ من الذيل/الرأس.

في اختباراتي ، تفوقت كل من إصدارات ذيول/الرؤوس على sed 'NUMq;d' باستمرار. وهذا يتماشى مع المعايير الأخرى التي تم نشرها. من الصعب العثور على حالة كانت فيها ذيول/رؤوس سيئة بالفعل. كما أنه ليس من المستغرب أن تكون هذه العمليات التي تتوقع أن تكون الأمثل بشدة في نظام يونيكس الحديثة.

للحصول على فكرة حول اختلافات الأداء ، هذه هي الأرقام التي أحصل عليها لملف ضخم (9.3 جيجا بايت):

  • tail -n+N | head -1: 3.7 ثانية
  • head -N | tail -1: 4.6 ثانية
  • sed Nq;d: 18.8 ثانية

قد تختلف النتائج ، لكن الأداء head | tail و tail | head يمكن مقارنته عمومًا بالمدخلات الأصغر ، ويكون sed دائمًا أبطأ بعامل كبير (حوالي 5x أو نحو ذلك).

لإعادة إنتاج المؤشر ، يمكنك تجربة ما يلي ، ولكن حذر من أنه سيؤدي إلى إنشاء ملف 9.3G في دليل العمل الحالي:

#!/bin/bash
readonly file=tmp-input.txt
readonly size=1000000000
readonly pos=500000000
readonly retries=3

seq 1 $size > $file
echo "*** head -N | tail -1 ***"
for i in $(seq 1 $retries) ; do
    time head "-$pos" $file | tail -1
done
echo "-------------------------"
echo
echo "*** tail -n+N | head -1 ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time tail -n+$pos $file | head -1
done
echo "-------------------------"
echo
echo "*** sed Nq;d ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time sed $pos'q;d' $file
done
/bin/rm $file

فيما يلي إخراج التشغيل على الجهاز (ThinkPad X1 Carbon مع SSD و 16 G من الذاكرة). أفترض في النهاية أن كل شيء سيأتي من ذاكرة التخزين المؤقت وليس من القرص:

*** head -N | tail -1 ***
500000000

real    0m9,800s
user    0m7,328s
sys     0m4,081s
500000000

real    0m4,231s
user    0m5,415s
sys     0m2,789s
500000000

real    0m4,636s
user    0m5,935s
sys     0m2,684s
-------------------------

*** tail -n+N | head -1 ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt
500000000

real    0m6,452s
user    0m3,367s
sys     0m1,498s
500000000

real    0m3,890s
user    0m2,921s
sys     0m0,952s
500000000

real    0m3,763s
user    0m3,004s
sys     0m0,760s
-------------------------

*** sed Nq;d ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt
500000000

real    0m23,675s
user    0m21,557s
sys     0m1,523s
500000000

real    0m20,328s
user    0m18,971s
sys     0m1,308s
500000000

real    0m19,835s
user    0m18,830s
sys     0m1,004s
12
Philipp Claßen

يمكنك أيضًا استخدام طباعة sed وإنهاء:

sed -n '10{p;q;}' file   # print line 10
11
bernd

يمكنك أيضًا استخدام Perl لهذا:

Perl -wnl -e '$.== NUM && print && exit;' some.file
7
Timofey Stolbov

الحل الأسرع للملفات الكبيرة دائمًا هو الذيل | head ، بشرط أن تكون المسافة بين:

  • من بداية الملف إلى خط البداية. دعنا نسميها S
  • المسافة من السطر الأخير إلى نهاية الملف. سواء كان E

من المعروف. ثم ، يمكننا استخدام هذا:

mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"

كم هو مجرد عدد الخطوط المطلوبة.

مزيد من التفاصيل في https://unix.stackexchange.com/a/216614/79743

6
user2350426

كمتابعة للإجابة المرجعية المفيدة للغاية لـ CaffeineConnoisseur ... كنت مهتمًا بمدى سرعة مقارنة طريقة "mapfile" بأخرى (حيث لم يتم اختبارها) ، لذلك جربت مقارنة السرعة السريعة والقذرة بنفسي لدي باش 4 مفيد. ألقيت في اختبار لطريقة "الذيل | الرأس" (بدلاً من الرأس | الذيل) المذكورة في أحد التعليقات على الإجابة العليا أثناء وجودي فيها ، حيث يغني الناس الثناء. ليس لدي أي شيء تقريبًا بحجم ملف testfile المستخدم ؛ أفضل ما يمكن أن أجده في غضون مهلة قصيرة هو ملف نسب 14M (خطوط طويلة مفصولة بمسافات بيضاء ، أقل بقليل من 12000 سطر).

نسخة مختصرة: يظهر ملف mapfile بشكل أسرع من طريقة القطع ، ولكنه أبطأ من أي شيء آخر ، لذا فإنني أسميها ملفقة. ذيل | يبدو الرأس ، OTOH ، أنه يمكن أن يكون الأسرع ، على الرغم من وجود ملف بهذا الحجم ، فإن الفرق ليس كبيرًا مقارنةً بـ sed.

$ time head -11000 [filename] | tail -1
[output redacted]

real    0m0.117s

$ time cut -f11000 -d$'\n' [filename]
[output redacted]

real    0m1.081s

$ time awk 'NR == 11000 {print; exit}' [filename]
[output redacted]

real    0m0.058s

$ time Perl -wnl -e '$.== 11000 && print && exit;' [filename]
[output redacted]

real    0m0.085s

$ time sed "11000q;d" [filename]
[output redacted]

real    0m0.031s

$ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]})
[output redacted]

real    0m0.309s

$ time tail -n+11000 [filename] | head -n1
[output redacted]

real    0m0.028s

أتمنى أن يساعدك هذا!

4
Jo Valentine-Cooper

جميع الإجابات المذكورة أعلاه الإجابة مباشرة على السؤال. ولكن هنا حل أقل مباشرة ولكن فكرة أكثر أهمية يحتمل ، لإثارة الفكر.

نظرًا لأن أطوال الأسطر تعسفية ، فكل بايتات الملف قبل السطر nth need to read. إذا كان لديك ملف ضخم أو كنت بحاجة إلى تكرار هذه المهمة عدة مرات ، وكانت هذه العملية تستغرق وقتًا طويلاً ، فعليك التفكير بجدية فيما إذا كنت تريد تخزين بياناتك بطريقة مختلفة في المقام الأول.

الحل الحقيقي هو امتلاك فهرس ، على سبيل المثال في بداية الملف ، مع الإشارة إلى المواضع التي تبدأ منها الخطوط. يمكنك استخدام تنسيق قاعدة بيانات ، أو مجرد إضافة جدول في بداية الملف. بدلاً من ذلك ، قم بإنشاء ملف فهرس منفصل لمرافقة ملفك النصي الكبير.

مثلا يمكنك إنشاء قائمة بمواقع الأحرف للخطوط الجديدة:

awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx

ثم اقرأ بـ tail ، والذي فعليًا seeks مباشرة إلى النقطة المناسبة في الملف!

مثلا للحصول على خط 1000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • قد لا يعمل هذا مع الأحرف ثنائية البايت/متعددة البايت ، لأن awk "يعرف الحروف" ولكن الذيل لا يعمل.
  • لم أختبر هذا مقابل ملف كبير.
  • انظر أيضا هذه الإجابة .
  • بدلا من ذلك - تقسيم ملفك إلى ملفات أصغر!
4
Sanjay Manohar

إذا حصلت على عدة أسطر بواسطة\n (سطر جديد عادة) يمكنك استخدام "قطع" كذلك:

echo "$data" | cut -f2 -d$'\n'

سوف تحصل على السطر الثاني من الملف. -f3 يمنحك السطر الثالث.

3
danger89

الكثير من الإجابات الجيدة بالفعل. أنا شخصيا أذهب مع awk. للراحة ، إذا كنت تستخدم bash ، فما عليك سوى إضافة ما يلي إلى ~/.bash_profile. وفي المرة التالية التي تقوم فيها بتسجيل الدخول (أو إذا قمت بتصدير ملف .bash_profile الخاص بك بعد هذا التحديث) ، فستتوفر لديك وظيفة nthty جديدة "nth" متاحة لنقل ملفاتك عبرها.

قم بتنفيذ هذا أو ضعه في ملف ~/.bash_profile (في حالة استخدام bash) وأعد فتح bash (أو قم بتنفيذ source ~/.bach_profile)

# print just the nth piped in line nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }

ثم ، لاستخدامها ، ببساطة الأنابيب من خلال ذلك. . منها مثلا،:

$ yes line | cat -n | nth 5 5 line

2
JJC

باستخدام ما ذكره الآخرون ، أردت أن تكون هذه وظيفة سريعة وممتعة في شل باش.

قم بإنشاء ملف: ~/.functions

أضف إليها المحتويات:

getline() { line=$1 sed $line'q;d' $2 }

ثم أضف هذا إلى ~/.bash_profile:

source ~/.functions

الآن عند فتح نافذة bash جديدة ، يمكنك فقط استدعاء الوظيفة على النحو التالي:

getline 441 myfile.txt

1
Mark Shust

لطباعة السطر nth باستخدام sed مع متغير كرقم سطر:

a=4
sed -e $a'q:d' file

هنا علامة '-e' هي لإضافة نص إلى أمر ليتم تنفيذه.

1
aliasav

لقد وضعت بعض الإجابات أعلاه في برنامج نصي للباش قصير يمكنك وضعه في ملف يسمى get.sh وارتباط /usr/local/bin/get (أو أي اسم آخر تفضله).

#!/bin/bash
if [ "${1}" == "" ]; then
    echo "error: blank line number";
    exit 1
fi
re='^[0-9]+$'
if ! [[ $1 =~ $re ]] ; then
    echo "error: line number arg not a number";
    exit 1
fi
if [ "${2}" == "" ]; then
    echo "error: blank file name";
    exit 1
fi
sed "${1}q;d" $2;
exit 0

تأكد من أنه قابل للتنفيذ مع

$ chmod +x get

ربطه لإتاحته على PATH بـ

$ ln -s get.sh /usr/local/bin/get

استمتع بمسؤولية!

P

0
polarise