[linux] التكرار من خلال محتوى ملف في باش


Answers

cat peptides.txt | while read line
do
   # do something with $line here
done
Question

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

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 (geeko@buildhost) (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



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

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

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

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




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

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

حظا سعيدا!




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

$ 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 ، يجب استخدام حلقة 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 -rp; do printf "%s\n" "'$p'"; done </tmp/test.txt بدلاً من ذلك ، تحافظ على المسافات البادئة while IFS= read -rp; 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 FAQ




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

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

# ':' 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 ... command ... | while read -r line; do ... command ... | while read -r line; do ... لأن حلقة while تعمل هنا في 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

قراءة ملف كامل في صفيف (إصدارات 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

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




Related