كيف يمكنني تكرار كل سطر في ملف نصي باستخدام 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
طريقة واحدة للقيام بذلك هي:
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).
cat peptides.txt | while read line
do
# do something with $line here
done
الخيار 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
هذه ليست أفضل من الإجابات الأخرى ، ولكنها طريقة أخرى لإنجاز المهمة في ملف بدون مسافات (انظر التعليقات). أجد أنني غالباً ما أحتاج إلى حرف واحد للبحث في قوائم الملفات النصية دون الحاجة إلى استخدام ملفات نصية منفصلة.
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 بالانقسام على خطوط جديدة فقط ، وليس مسافات ، ثم يعيد البيئة إلى ما كانت عليه سابقًا. في هذه المرحلة ، قد ترغب في وضعه في نص شل بدلاً من الضغط عليه في سطر واحد.
حظا سعيدا!
بعض الأشياء الأخرى التي لا تغطيها إجابات أخرى:
# ':' 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
.
while read -r line; do
my_array+=("$line")
done < my_file
إذا انتهى الملف بسطر غير مكتمل (السطر الجديد مفقود في النهاية) ، فقم بما يلي:
while read -r line || [[ $line ]]; do
my_array+=("$line")
done < my_file
readarray -t my_array < my_file
أو
mapfile -t my_array < my_file
وثم
for line in "${my_array[@]}"; do
# process the lines
done
الوظائف ذات الصلة:
استخدم حلقة من الوقت ، مثل هذا:
while IFS= read -r line; do
echo "$line"
done <file
ملاحظات:
إذا لم تقم بتعيين IFS
بشكل صحيح ، فسوف تفقد المسافة البادئة.
إذا كنت لا ترغب في كسر قراءتك بحرف السطر الجديد ، فاستخدم -
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
echo "$line"
done < "$1"
ثم قم بتشغيل البرنامج النصي مع اسم الملف كمعلمة.
افترض أن لديك هذا الملف:
$ 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:
إذا كنت تريد سطر الملف النصي سطراً بما في ذلك الأسطر الفارغة وخطوط الإنهاء دون 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
#!/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
إليكم مثال عن حياتي الحقيقية: كيف أقوم بتدوين خطوط لإخراج برنامج آخر ، والتحقق من وجود سلاسل ، وإسقاط علامات الاقتباس المزدوجة من المتغير ، واستخدام هذا المتغير خارج الحلقة. أعتقد أن كثيرين يطرحون هذه الأسئلة عاجلاً أم آجلاً.
##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 ، يسقط أول اقتباس ، يسقط آخر اقتباس ، لدينا قيمة نظيفة لاستخدامها في مكان آخر.
@ بيتر: هذا يمكن أن تعمل من أجلك-
echo "Start!";for p in $(cat ./pep); do
echo $p
done
هذا سيعود الإخراج
Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL