it-swarm.asia

تنفيذ حلقات عبر محتوى ملف في Bash

كيف يمكنني تكرار كل سطر في ملف نصي باستخدام 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
1126
Peter Mortensen

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

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

1786
Bruno De Fraine
cat peptides.txt | while read line
do
   # do something with $line here
done
345
Warren Young

الخيار 1a: بينما حلقة: سطر واحد في وقت واحد: إعادة توجيه الإدخال

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

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

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

الخيار 2: للحلقة: قراءة الملف في متغير واحد وتحليل.
سيقوم بناء الجملة هذا بتحليل "الخطوط" بناءً على أي مسافة بيضاء بين الرموز. هذا لا يزال يعمل لأن خطوط ملف الإدخال المعيّنة هي رموز Word مفرد. إذا كان هناك أكثر من رمز مميز لكل سطر ، فلن تعمل هذه الطريقة. أيضا ، فإن قراءة الملف الكامل في متغير واحد ليست استراتيجية جيدة للملفات الكبيرة.

#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
    echo $line
done
130
Stan Graves

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

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

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

حظا سعيدا!

69
mightypile

بعض الأشياء الأخرى التي لا تغطيها إجابات أخرى:

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

# ':' 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 ... لأن حلقة بينما هنا تعمل في Shell الحالي بدلاً من subshell كما في حالة الأخير. راجع المنشور ذي الصلة لا يتم تذكر متغير تم تعديله داخل حلقة من الوقت .

القراءة من إدخال محدد فارغ ، على سبيل المثال 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's answer here :

-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

الوظائف ذات الصلة:

56
codeforester

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

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

ملاحظات:

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

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

  3. لا تقرأ أسطرًا بها for

42
Jahid

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

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

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

13
Anjul Sharma

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

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

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

فيما يلي الطرق التي قد تغير الملف (مقارنةً بما إرجاع 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) ، فأنت تقرأ الملف Word by Word بدلاً من غالب واحد. ربما لا يكون المقصود أيضًا ...)


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

$ 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 FAQ

12
dawg
#!/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
4
Sine

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

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

قم بتعريف المتغير خارج الحلقة ، وحدد القيمة واستخدمه خارج الحلقة يتطلب تم القيام به <<< "$ (...)" بناء الجملة. يجب تشغيل التطبيق في سياق وحدة التحكم الحالية. يقتبس حول الأمر الأسطر الجديدة من دفق الإخراج.

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

3
Whome

@ بيتر: هذا يمكن أن تعمل من أجلك-

echo "Start!";for p in $(cat ./pep); do
echo $p
done

هذا سيعود الإخراج

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
1
Alan Jebakumar