معنى - git bash




كيف يمكنني التكرار عبر مجموعة من الأرقام المحددة بواسطة المتغيرات في Bash؟ (12)

نقاش

استخدام SEQ على ما يرام ، كما اقترح Jiaaro. اقترح Pax Diablo حلقة Bash لتجنب استدعاء عملية فرعية ، مع ميزة إضافية تتمثل في كونها أكثر ملائمة للذاكرة إذا كان $ END أكبر مما يجب. اكتشف Zathrus خللًا نموذجيًا في تنفيذ الحلقات ، وألمح أيضًا إلى أنه بما أن i متغير نص ، يتم إجراء التحويلات المستمرة والأرقام مع بطء مرتبط.

حساب صحيح

هذا هو نسخة محسنة من حلقة باش:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done

إذا كان الشيء الوحيد الذي نريده هو echo ، فيمكننا كتابة echo $((i++)) .

علّمني شيئًا ما: يسمح لك Bash ببناء for ((expr;expr;expr)) . بما أنني لم أقرأ صفحة الرجل بالكامل لـ Bash (مثلما فعلت مع صفحة رجل Korn shell ( ksh ) ، وكان ذلك منذ فترة طويلة) ، فقد اشتقت لذلك.

وبالتالي،

typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done

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

السؤال الأولي

أشار eschercycle إلى أن { a .. b } يعمل تدوين Bash فقط مع القيم الحرفية؛ صحيح ، وفقا لدليل Bash. يمكن للمرء التغلب على هذه العقبة مع fork() واحدة (داخلية) fork() بدون exec() (كما هو الحال مع استدعاء seq ، والتي تتطلب صورة أخرى شوكة + exec):

for i in $(eval echo "{1..$END}"); do

كل من eval و echo هي Bash builtins ، لكن fork() مطلوبة لاستبدال الأمر ( $(…) ).

كيف يمكنني التكرار عبر مجموعة من الأرقام في Bash عندما يتم إعطاء النطاق بواسطة متغير؟

أعلم أنه يمكنني القيام بذلك (يسمى "تعبير التسلسل" في documentation Bash):

 for i in {1..5}; do echo $i; done

الذي يعطي:

1
2
3
4
5

ومع ذلك ، كيف يمكنني استبدال أي من نهايات المدى مع متغير؟ هذا لا يعمل:

END=5
for i in {1..$END}; do echo $i; done

الذي يطبع:

{1..5}


أسلوب seq هو أبسط ، ولكن Bash يحتوي على تقييم حسابي مضمّن.

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

من for ((expr1;expr2;expr3)); بناء يعمل تماما مثل for (expr1;expr2;expr3) في لغة C واللغات المماثلة ، for (expr1;expr2;expr3) من حالات ((expr)) ، يعاملهم Bash كحساب.


إذا كنت ترغب في البقاء أقرب ما يمكن إلى صيغة التعبير عن range.bash ، فجرّب استخدام الدالة range من bash-tricks ' range.bash .

على سبيل المثال ، سيؤدي كل ما يلي نفس الشيء تمامًا مثل echo {1..10} :

source range.bash
one=1
ten=10

range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}

يحاول دعم بناء جملة bash الأصلية مع عدد قليل من "gotchas" بقدر الإمكان: ليس فقط المتغيرات المدعومة ، ولكن السلوك غير المرغوب في كثير من الأحيان من نطاقات غير صالحة يتم توفيرها for i in {1..a}; do echo $i; done (على سبيل المثال for i in {1..a}; do echo $i; done يتم منع for i in {1..a}; do echo $i; done ) كذلك.

ستعمل الإجابات الأخرى في معظم الحالات ، ولكن جميعها تحتوي على واحد على الأقل من العوائق التالية:

  • يستخدم العديد منهم subshells ، والتي يمكن أن تضر الأداء وربما لا تكون ممكنة على بعض الأنظمة.
  • كثير منهم يعتمدون على البرامج الخارجية. حتى seq عبارة عن ملف ثنائي يجب تثبيته ليتم استخدامه ، ويجب تحميله عن طريق bash ، ويجب أن يحتوي على البرنامج الذي تتوقعه ، حتى يعمل في هذه الحالة. في كل مكان ، أم لا ، هناك الكثير من الاعتماد عليه أكثر من مجرد لغة باش نفسها.
  • لن تعمل الحلول التي تستخدم وظائف Bash الأصلية فقط ، مثل @ ephemient ، على النطاقات الأبجدية ، مثل {a..z} ؛ سوف التوسع هدفين. كان السؤال حول نطاقات الأرقام ، على الرغم من ذلك ، وهذا هو ممحاة.
  • معظمها لا يتشابه بصريًا مع بنية النطاق الموسعة ذات النطاق {1..10} ، لذلك قد تكون البرامج التي تستخدم كلاهما أكثر صعوبة في القراءة.
  • تستخدم إجابة bobbogo بعض بناء الجملة المألوف ، ولكنها تفعل شيئًا غير متوقع إذا كان متغير $END ليس "نطاقًا" صالحًا للنطاق الآخر للنطاق. إذا كان END=a ، على سبيل المثال ، لن يحدث خطأ وسيتم {1..a} القيمة الحرفية {1..a} . هذا هو السلوك الافتراضي لباش ، وهو أمر غير متوقع في كثير من الأحيان.

إخلاء المسؤولية: أنا مؤلف الكود المرتبط.


إذا كنت تستخدم BSD / OS X ، فيمكنك استخدام jot بدلاً من seq:

for i in $(jot $END); do echo $i; done

إذا كنت في حاجة إليها بادئة مما قد ترغب فيه

 for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done

التي ستنتج

07
08
09
10
11
12

استبدل {} بـ (( )) :

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

عائدات:

0
1
2
3
4

هذا يعمل بشكل جيد في bash :

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done

هذه طريقة اخرى:

end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done

هنا لماذا لم يعمل التعبير الأصلي.

من رجل باش :

يتم تنفيذ توسع الاستطالة قبل أي توسيعات أخرى ، ويتم الحفاظ على أي أحرف خاصة للتوسعات الأخرى في النتيجة. إنه نص بدقة. لا ينطبق Bash أي تفسير نحوي على سياق التوسيع أو النص بين الأقواس.

لذا ، يتم تنفيذ توسيع الدعامة مبكرًا كعملية ماكرو نصية بحتة ، قبل توسيع المعلمة.

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

توصية

أود أن أقترح التمسك بميزات Posix 1 . وهذا يعني استخدام for i in <list>; do for i in <list>; do ، إذا كانت القائمة معروفة بالفعل ، خلاف ذلك ، استخدمها while أو seq ، كما في:

#!/bin/sh

limit=4

i=1; while [ $i -le $limit ]; do
  echo $i
  i=$(($i + 1))
done
# Or -----------------------
for i in $(seq 1 $limit); do
  echo $i
done

1. باش هو قشرة كبيرة وأنا استخدمها بشكل تفاعلي ، لكنني لا أضع إشارات bash في نصوصي. قد تحتاج البرامج النصية إلى غلاف أسرع ، وأكثر أمانًا ، ونمطًا أكثر تكاملاً. قد يحتاجون إلى تشغيل ما يتم تثبيته كـ / bin / sh ، ومن ثم هناك جميع الحجج المعتادة الموالية للمعايير. تذكر shellshock ، ويعرف أيضا باسم bashdoor؟


يعمل هذا في Bash و Korn ، ويمكن أيضًا الانتقال من أرقام أعلى إلى أرقام أقل. ربما ليس أسرع أو أجمل ولكن يعمل بشكل جيد بما فيه الكفاية. يتعامل السلبيات أيضا.

function num_range {
   # Return a range of whole numbers from beginning value to ending value.
   # >>> num_range start end
   # start: Whole number to start with.
   # end: Whole number to end with.
   typeset s e v
   s=${1}
   e=${2}
   if (( ${e} >= ${s} )); then
      v=${s}
      while (( ${v} <= ${e} )); do
         echo ${v}
         ((v=v+1))
      done
   elif (( ${e} < ${s} )); then
      v=${s}
      while (( ${v} >= ${e} )); do
         echo ${v}
         ((v=v-1))
      done
   fi
}

function test_num_range {
   num_range 1 3 | egrep "1|2|3" | assert_lc 3
   num_range 1 3 | head -1 | assert_eq 1
   num_range -1 1 | head -1 | assert_eq "-1"
   num_range 3 1 | egrep "1|2|3" | assert_lc 3
   num_range 3 1 | head -1 | assert_eq 3
   num_range 1 -1 | tail -1 | assert_eq "-1"
}

طريقة POSIX

إذا كنت تهتم بالنقل ، فاستخدم المثال من معيار POSIX :

i=2
end=5
while [ $i -le $end ]; do
    echo $i
    i=$(($i+1))
done

انتاج:

2
3
4
5

الأشياء التي ليست POSIX:

  • (( )) بدون الدولار ، على الرغم من أنه امتداد شائع كما ذكر من قبل POSIX نفسها .
  • [[ . [ يكفي هنا. راجع أيضًا: ما الفرق بين الأقواس المربعة الفردية والمزدوجة في Bash؟
  • for ((;;))
  • seq (GNU Coreutils)
  • {start..end} ، والتي لا يمكنها العمل مع المتغيرات كما هو مذكور في دليل Bash .
  • let i=i+1 : POSIX 7 2. لغة أوامر Shell لا تحتوي على الكلمة let ، وتفشل في bash --posix 4.3.42
  • قد يكون مطلوبًا الدولار عند i=$i+1 ، لكنني لست متأكدًا. POSIX 7 2.6.4 يقول التوسع الحسابي :

    إذا احتوى متغير shell x على قيمة تشكل ثابت عدد صحيح صالح ، بشكل اختياري بما في ذلك علامة زائد أو ناقص قيادية ، فإن التوسعات الحسابية "$ ((x))" و "$ (($ x))" يجب أن تعيد نفس القيمة. القيمة.

    لكن قراءته حرفياً لا يعني ضمناً أن $((x+1)) يتوسع لأن x+1 ليس متغيراً.


for i in $(seq 1 $END); do echo $i; done

تحرير: أنا أفضل seq على الطرق الأخرى لأنني يمكن أن أتذكرها في الواقع ؛)





shell