التكرار خلال محتوى ملف في Bash

سئل على ٥ أكتوبر ٢٠٠٩  ·  تمت مشاهدة 1.8M مرة  ·  مصدر

Peter Mortensen picture
في ٥ أكتوبر ٢٠٠٩

كيف يمكنني تكرار كل سطر في ملف نصي باستخدام Bash ؟

مع هذا البرنامج النصي:

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done

أحصل على هذا الإخراج على الشاشة:

Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'

(أريد لاحقًا أن أقوم بشيء أكثر تعقيدًا باستخدام $p أكثر من مجرد الإخراج على الشاشة.)


متغير البيئة SHELL هو (من env):

SHELL=/bin/bash

الناتج /bin/bash --version :

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

الناتج cat /proc/version :

Linux version 2.6.18.2-34-default ([email protected]) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006

يحتوي الملف peptides.txt على:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL

الإجابات

Bruno De Fraine picture
في ٥ أكتوبر ٢٠٠٩
2241

طريقة واحدة للقيام بذلك هي:

while read p; do
  echo "$p"
done <peptides.txt

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

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt

بشكل استثنائي ، إذا كان نص الحلقة قد يقرأ من الإدخال القياسي ، يمكنك فتح الملف باستخدام واصف ملف مختلف:

while read -u 10 p; do
  ...
done 10<peptides.txt

هنا ، 10 هو مجرد رقم عشوائي (يختلف عن 0 ، 1 ، 2).

Warren Young picture
في ٥ أكتوبر ٢٠٠٩
503
cat peptides.txt | while read line 
do
   # do something with $line here
done

والمتغير أحادي الخط:

cat peptides.txt | while read line; do something_with_$line_here; done

ستتخطى هذه الخيارات السطر الأخير من الملف إذا لم يكن هناك تغذية سطر لاحق.

يمكنك تجنب ذلك من خلال ما يلي:

cat peptides.txt | while read line || [[ -n $line ]];
do
   # do something with $line here
done
Stan Graves picture
في ٥ أكتوبر ٢٠٠٩
149

الخيار 1 أ: حلقة أثناء: سطر واحد في كل مرة: إعادة توجيه الإدخال

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename

الخيار 1 ب: حلقة أثناء: سطر واحد في كل مرة:
افتح الملف ، اقرأ من واصف الملف (في هذه الحالة ملف واصف # 4).

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done
mightypile picture
في ٤ أكتوبر ٢٠١٣
98

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

for word in $(cat peptides.txt); do echo $word; done

يتيح لي هذا التنسيق وضع كل ذلك في سطر أوامر واحد. قم بتغيير جزء "echo $ word" إلى ما تريد ، ويمكنك إصدار أوامر متعددة مفصولة بفواصل منقوطة. يستخدم المثال التالي محتويات الملف كوسائط في نصين آخرين ربما تكون قد كتبتهما.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done

أو إذا كنت تنوي استخدام هذا كمحرر دفق (تعلم sed) ، يمكنك تفريغ الإخراج إلى ملف آخر على النحو التالي.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt

لقد استخدمت هذه كما هو مكتوب أعلاه لأنني استخدمت ملفات نصية حيث قمت بإنشائها بكلمة واحدة في كل سطر. (انظر التعليقات) إذا كانت لديك مسافات لا تريد تقسيم كلماتك / سطورها ، فسيصبح الأمر أقبح قليلاً ، لكن الأمر نفسه لا يزال يعمل على النحو التالي:

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS

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

حظا سعيدا!

codeforester picture
في ١٤ يناير ٢٠١٧
77

بعض الأشياء الأخرى التي لم تتناولها إجابات أخرى:

القراءة من ملف محدد

# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
  # process the fields
  # if the line has less than three fields, the missing fields will be set to an empty string
  # if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt

القراءة من إخراج أمر آخر ، باستخدام عملية الاستبدال

while read -r line; do
  # process the line
done < <(command ...)

هذا الأسلوب أفضل من command ... | while read -r line; do ... لأن حلقة while هنا تعمل في الغلاف الحالي بدلاً من القشرة الفرعية كما في حالة الأخير. انظر المنشور ذي الصلة لا يتم تذكر المتغير المعدل داخل حلقة while .

القراءة من إدخال محدد فارغ ، على سبيل المثال find ... -print0

while read -r -d '' line; do
  # logic
  # use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)

قراءة ذات صلة: BashFAQ / 020 - كيف يمكنني العثور على أسماء الملفات التي تحتوي على أسطر جديدة أو مسافات أو كليهما والتعامل معها بأمان؟

القراءة من أكثر من ملف في وقت واحد

while read -u 3 -r line1 && read -u 4 -r line2; do
  # process the lines
  # note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt

بناءً على إجابة @ chepner هنا :

-u هو امتداد bash. للتوافق مع POSIX ، ستبدو كل مكالمة مثل read -r X <&3 .

قراءة ملف كامل في مصفوفة (إصدارات Bash السابقة إلى 4)

while read -r line; do
    my_array+=("$line")
done < my_file

إذا انتهى الملف بسطر غير مكتمل (سطر جديد مفقود في النهاية) ، إذن:

while read -r line || [[ $line ]]; do
    my_array+=("$line")
done < my_file

قراءة ملف كامل في مصفوفة (إصدارات Bash 4x والإصدارات الأحدث)

readarray -t my_array < my_file

أو

mapfile -t my_array < my_file

وثم

for line in "${my_array[@]}"; do
  # process the lines
done

المنشورات ذات الصلة:

Jahid picture
في ٩ يونيو ٢٠١٥
46

استخدم حلقة while ، مثل هذا:

while IFS= read -r line; do
   echo "$line"
done <file

ملاحظات:

  1. إذا لم تقم بتعيين IFS بشكل صحيح ، فستفقد المسافة البادئة.

  2. يجب عليك دائمًا استخدام الخيار -r مع القراءة.

  3. لا تقرأ الأسطر التي تحتوي على for

dawg picture
في ٣ فبراير ٢٠١٦
14

افترض أن لديك هذا الملف:

$ cat /tmp/test.txt
Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR

هناك أربعة عناصر ستغير معنى إخراج الملف الذي تقرأه العديد من حلول Bash:

  1. السطر الفارغ 4 ؛
  2. المسافات البادئة أو اللاحقة على سطرين ؛
  3. الحفاظ على معنى الأسطر الفردية (أي أن كل سطر هو سجل) ؛
  4. السطر 6 غير منتهي بسجل تجاري.

إذا كنت تريد سطر ملف نصي بسطر بما في ذلك الأسطر الفارغة وأسطر النهاية بدون CR ، فيجب عليك استخدام حلقة while ويجب أن يكون لديك اختبار بديل للسطر الأخير.

فيما يلي الطرق التي قد تغير الملف (بالمقارنة مع ما يعيده cat ):

1) تفقد السطر الأخير والمسافات البادئة والزائدة:

$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'

(إذا قمت بإجراء while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt بدلاً من ذلك ، فإنك تحتفظ بالمسافات السابقة واللاحقة ولكن لا تزال تفقد السطر الأخير إذا لم يتم إنهاؤه بـ CR)

2) سيؤدي استخدام استبدال العملية بـ cat إلى قراءة الملف بأكمله في جرعة واحدة ويفقد معنى الأسطر الفردية:

$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR'

(إذا قمت بإزالة " من $(cat /tmp/test.txt) فأنت تقرأ الملف كلمة بكلمة بدلاً من قراءة جرعة واحدة. وربما ليس المقصود أيضًا ...)


الطريقة الأقوى والأبسط لقراءة ملف سطرًا بسطر والحفاظ على جميع المسافات هي:

$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'    Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space    '
'Line 6 has no ending CR'

إذا كنت تريد تجريد المسافات البادئة والتداول ، فقم بإزالة الجزء IFS= :

$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'

(يعتبر الملف النصي بدون إنهاء \n ، رغم أنه شائع إلى حد ما ، معطلاً بموجب POSIX. إذا كان بإمكانك الاعتماد على \n فأنت لست بحاجة إلى || [[ -n $line ]] في while حلقة.)

المزيد في الأسئلة الشائعة BASH

Anjul Sharma picture
في ٨ مارس ٢٠١٦
13

إذا كنت لا تريد أن تتكسر قراءتك بحرف سطر جديد ، فاستخدم -

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"

ثم قم بتشغيل البرنامج النصي مع اسم الملف كمعلمة.

Sine picture
في ١٤ نوفمبر ٢٠١٣
4
#!/bin/bash
#
# Change the file name from "test" to desired input file 
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
    echo $x
done
Whome picture
في ٣٠ يونيو ٢٠١٥
3

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

##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
  if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
    echo ParseFPS $line
    FPS=parse
  fi
  if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
    echo ParseFPS $line
    FPS=${line##*=}
    FPS="${FPS%\"}"
    FPS="${FPS#\"}"
  fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then 
  echo ParseFPS Unknown frame rate
fi
echo Found $FPS

قم بتعريف المتغير خارج الحلقة ، وقم بتعيين القيمة واستخدامه خارج الحلقة يتطلب إجراء بناء الجملة

تطابق الحلقة مع السلاسل الفرعية ، ثم تقرأ الاسم = زوج = ، وتسقط أول اقتباس ، وتسقط آخر اقتباس ، ولدينا قيمة نظيفة لاستخدامها في مكان آخر.