لجعل خط أنابيب zsh يبلّغ عن الفشل من أي مرحلة، قم بتمكين خيار PIPE_FAIL قبل تشغيله. في تطبيق Terminal على Mac، أدخل setopt PIPE_FAIL؛ وفي أي برنامج نصي، ضع هذا السطر قبل خط الأنابيب. عندئذٍ، يعيد خط الأنابيب حالة الخروج الخاصة بأقصى أمر فاشل جهة اليمين، أو صفراً إذا نجحت جميع الأوامر. التقط $? مباشرة بعد خط الأنابيب إذا كان يجب ألا تستبدل الأوامر اللاحقة تلك النتيجة. يوثّق دليل خيارات zsh هذا السلوك.
لماذا قد يبدو خط الأنابيب الفاشل ناجحاً
بشكل افتراضي، يمنح zsh خط الأنابيب حالة الخروج الخاصة بأمره الأخير. لذا، إذا فشل أمر سابق ولكن نجح الأمر الأخير، فقد يبدو خط الأنابيب بأكمله ناجحاً. على سبيل المثال، يمكن لأمر cat الأخير أدناه أن ينجح حتى عندما لا يعثر grep على أي سطر مطابق:
printf '%s\n' 'ready' | grep 'missing' | cat
print -r -- $?
يعرّف دليل zsh خط الأنابيب بأنه أوامر متصلة بواسطة | وينص على أن قيمته هي عادةً قيمة الأمر الأخير. تعني الحالة صفر النجاح؛ بينما تعني الحالة غير الصفرية أن الأمر أو خط الأنابيب قد أبلغ عن فشل. يصف دليل خطوط أنابيب zsh القاعدة الافتراضية.
تمكين PIPE_FAIL لخط الأنابيب
في جلسة Terminal، اضبط الخيار قبل تشغيل الأمر:
setopt PIPE_FAIL
printf '%s\n' 'ready' | grep 'missing' | cat
print -r -- $?
أصبحت الحالة النهائية الآن غير صفرية لأن grep فشل، على الرغم من نجاح cat. يستخدم PIPE_FAIL أقصى حالة غير صفرية جهة اليمين في خط الأنابيب. يحافظ هذا على دلالة الفشل مع الاستمرار في إرجاع رمز الحالة الفعلي الذي حدده zsh. لإيقاف هذا السلوك مجدداً في الجلسة نفسها، شغّل unsetopt PIPE_FAIL.
بالنسبة لبرنامج نصي، ضع الخيار بالقرب من البداية، بعد سطر المفسر (interpreter line) وقبل أي خط أنابيب معني:
#!/bin/zsh
setopt PIPE_FAIL
printf '%s\n' 'ready' | grep 'missing' | cat
pipeline_status=$?
if (( pipeline_status != 0 )); then
print -u2 -- "Pipeline failed with status $pipeline_status"
exit "$pipeline_status"
fi
يعد التعيين الصريح للحالة أمراً مهماً: فالمتغير $? يعني حالة الأمر الأخير، لذا فإن أمر print لاحق، أو أمراً متعلقاً بالمتغيرات، أو عبارة شرطية يمكن أن يغير ما يبلّغ عنه. يتيح حفظ القيمة على الفور للبرنامج النصي الإبلاغ عنها أو الخروج بها بعد تنفيذ مهام أخرى. يعيد هذا المثال حالة فشل إلى البرنامج الذي شغّل البرنامج النصي؛ لكنه لا يمنع تشغيل أوامر خط الأنابيب الفردية بمجرد فشل مرحلة سابقة.
استخدام pipestatus عندما تحتاج إلى نتيجة كل مرحلة
يقوم PIPE_FAIL بتلخيص خط الأنابيب. إذا كنت بحاجة أيضاً إلى تحديد أي أمر أرجع أي رمز، يوفر zsh المصفوفة الخاصة pipestatus، التي تحتوي على حالة كل أمر في خط الأنابيب الأخير. اقرأها على الفور، لأن تشغيل خط أنابيب آخر يستبدل تلك القيم. يوثّق مرجع معلمات zsh هذه المصفوفة.
printf '%s\n' 'ready' | grep 'missing' | cat
print -r -- "Stage statuses: ${pipestatus[*]}"
بالنسبة لمثال الأوامر الثلاثة هذا، يُظهر الناتج صفراً لأمر printf، وقيمة غير صفرية لأمر grep، وصفراً لأمر cat. يعتمد الرمز غير الصفري الدقيق على الأمر. إذا كنت بحاجة إلى كلٍ من النتيجة الإجمالية ونتائج كل مرحلة على حدة، فاستخدم PIPE_FAIL والتقط القيمة الإجمالية لـ $? على الفور؛ وافحص pipestatus مباشرة بعد تشغيل خط أنابيب منفصل عند تشخيص المراحل، أو صمم البرنامج النصي لتسجيل المصفوفة أولاً وحساب الإجمالي المطلوب من تلك البيانات المحفوظة. تجنب إدراج أمر أو خط أنابيب آخر بين خط الأنابيب والحالة التي تنوي قراءتها.
الإبقاء على الإعداد في واجهة الأوامر (Shell) الصحيحة
لتطبيق الخيار على الأوامر اللاحقة في جلسة zsh تفاعلية، شغّل setopt PIPE_FAIL في تلك الجلسة. لتمكينه لكل جلسة zsh جديدة، أضف السطر إلى ~/.zshrc، ثم افتح نافذة Terminal جديدة أو حمّل الملف باستخدام source ~/.zshrc. يحدد دليل Terminal من Apple أن zsh هو واجهة الأوامر الافتراضية، مع الإشارة إلى أن الإعدادات أو المسؤولين يمكنهم تحديد واجهة أوامر مختلفة. تأكد من واجهة الأوامر النشطة قبل الاعتماد على بناء الجملة الخاص بـ zsh. يشرح دليل Terminal من Apple كيفية اختيار Terminal لواجهة الأوامر.
البرنامج النصي الذي يبدأ بـ #!/bin/zsh يطلب zsh مباشرةً، بغض النظر عن واجهة الأوامر التفاعلية المهيأة لـ Terminal. وإذا تم تشغيل البرنامج النصي بواسطة واجهة أوامر أخرى بدلاً من ذلك، فإن setopt وpipestatus ليسا بديلين متوافقين مع أدوات التحكم في خطوط الأنابيب الخاصة بتلك الواجهة. حافظ على توافق المفسر وبناء الجملة، ثم احتفظ بنتيجة خط الأنابيب قبل القيام بأي مهام للمتابعة.
المصادر
16 Options (zsh) — The Z Shell Manual
6 Shell Grammar (zsh) — The Z Shell Manual
15 Parameters (zsh) — The Z Shell Manual
Change the default shell in Terminal on Mac — Apple Support