0Pricing
Linux Command Line & Bash Scripting Mastery · درس

معالجات trap للتنظيف والإشارات

سجّل مصائد EXIT وERR وINT لحذف الملفات المؤقتة والتراجع عن الأعمال الجزئية بشكل موثوق

معالجات trap للتنظيف والإشارات درس مجاني في Linux Command Line & Bash Scripting Mastery على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Linux Command Line & Bash Scripting Mastery، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Linux Command Line & Bash Scripting Mastery 4 دروس في المجموع.

أهمية معالجات المصائد

عندما يخرج نص Bash — سواء بصورة طبيعية أو بسبب خطأ أو لأن المستخدم ضغط Ctrl+C — فقد يترك وراءه ملفات مؤقتة أو بيانات مكتوبة جزئيًا أو حالة تالفة. ومن دون التنظيف، تتراكم هذه الآثار وتتسبب في مشكلات يصعب تصحيحها.

تحل معالجات المصائد هذه المشكلة من خلال تسجيل دالة أو أمر ينفذه Bash تلقائيًا عند تلقي إشارة محددة أو إشارة زائفة.

  • EXIT — يُنفَّذ كلما خرج النص، لأي سبب كان
  • ERR — يُنفَّذ بعد أن يُرجع أي أمر حالة خروج غير صفرية
  • INT — يُنفَّذ عندما يرسل المستخدم SIGINT (Ctrl+C)
  • TERM — يُنفَّذ عند وصول SIGTERM (مثلًا من kill)

يسجّل النص المكتوب جيدًا هذه المصائد في بدايته تمامًا، قبل بدء أي عمل قد ينطوي على مخاطر.

بنية الأمر المضمّن trap

يسجّل الأمر المضمّن trap معالجًا لإشارة واحدة أو أكثر، سواء أكانت إشارات أم إشارات زائفة. والبنية العامة هي:

trap 'command_or_function' SIGNAL [SIGNAL...]

القواعد الأساسية:

  • الوسيط الأول هو سلسلة مقتبسة من تعليمات shell (أو اسم دالة) لتنفيذها عند إطلاق الإشارة.
  • يمكنكم إدراج إشارات متعددة بعد المعالج.
  • trap '' SIGNAL يتجاهل تلك الإشارة (معالج فارغ).
  • trap - SIGNAL يعيد ضبط الإشارة إلى سلوكها الافتراضي.

ترث الدوال المصائد عند استدعائها في الصدفة نفسها، لكن الصدفات الفرعية التي يتم إنشاؤها باستخدام ( ) أو & لا ترثها.

#!/usr/bin/env bash
# Syntax examples — not a full script

# Register a cleanup function on EXIT
trap cleanup EXIT

# Inline handler for INT and TERM
trap 'echo "Interrupted!"; exit 1' INT TERM

# Ignore SIGHUP
trap '' HUP

# Reset SIGPIPE to default
trap - PIPE

مصيدة EXIT — شبكة الأمان الخاصة بكم

تُعد الإشارة الزائفة EXIT أهم مصيدة ينبغي تسجيلها. فهي تُنفَّذ عند خروج الصدفة لأي سبب: الإكمال الطبيعي، أو exit N، أو خطأ غير معالج، أو إشارة. لذلك فهي مثالية لإزالة الملفات المؤقتة.

أفضل الممارسات:

  • أنشئوا الملفات المؤقتة باستخدام mktemp كي يكون اسمها فريدًا وغير قابل للتنبؤ.
  • خزّنوا المسار في متغير فورًا.
  • سجّلوا مصيدة EXIT مباشرة بعد الإنشاء لضمان تنفيذ التنظيف دائمًا.

يُحفَظ رمز خروج النص — ويُتجاهل رمز خروج معالج المصيدة إلا إذا استدعيتم exit صراحةً بداخله.

#!/usr/bin/env bash
set -euo pipefail

TMPFILE=$(mktemp /tmp/myapp.XXXXXX)

cleanup() {
    echo "Removing temp file: $TMPFILE" >&2
    rm -f "$TMPFILE"
}
trap cleanup EXIT

# Do real work
echo "Processing..." > "$TMPFILE"
sort "$TMPFILE" -o "$TMPFILE"
cp "$TMPFILE" /tmp/myapp_result.txt

echo "Done. Result saved."

التعامل مع ملفات ومجلدات مؤقتة متعددة

تنشئ النصوص الواقعية غالبًا عدة عناصر مؤقتة — ملفات ومجلدات وFIFOs وملفات قفل. وبدلًا من تتبع كل عنصر على حدة، اجمعوها في مصفوفة وأزيلوا كل شيء ضمن دالة تنظيف واحدة.

يتوسع هذا النمط بسهولة: ففي كل مرة تنشئون فيها موردًا مؤقتًا جديدًا، أضيفوه إلى المصفوفة. وستتعامل مصيدة EXIT دائمًا مع القائمة كاملة، بصرف النظر عن الموضع الذي يخرج منه النص.

#!/usr/bin/env bash
set -euo pipefail

CLEANUP_TARGETS=()

add_cleanup() {
    CLEANUP_TARGETS+=("$1")
}

cleanup() {
    echo "Running cleanup..." >&2
    for target in "${CLEANUP_TARGETS[@]:-}"; do
        rm -rf "$target"
        echo "Removed: $target" >&2
    done
}
trap cleanup EXIT

# Create and track temp resources
TMPDIR_WORK=$(mktemp -d /tmp/work.XXXXXX)
add_cleanup "$TMPDIR_WORK"

TMPFILE_LOG=$(mktemp /tmp/run.XXXXXX.log)
add_cleanup "$TMPFILE_LOG"

echo "Working in $TMPDIR_WORK" | tee "$TMPFILE_LOG"
touch "$TMPDIR_WORK/output.txt"
echo "Step complete."

مصيدة ERR — التقاط حالات الفشل

تُنفَّذ الإشارة الزائفة ERR بعد أن يُرجع أي أمر بسيط حالة خروج غير صفرية، بشرط أن يكون خيار الصدفة set -e (errexit) مفعّلًا، أو ألا يكون الأمر داخل بنية مركبة من نوع if أو while أو ||/&&.

استخدموا مصيدة ERR من أجل:

  • تسجيل السطر الذي حدث فيه الفشل باستخدام $LINENO و$BASH_COMMAND.
  • بدء منطق التراجع قبل تنفيذ مصيدة EXIT.
  • إرسال رسائل خطأ منظمة إلى stderr.

مهم: لا تُنفَّذ مصائد ERR داخل الدوال إلا إذا شغّلتم صراحةً set -E (errtrace) لكي ترث الدوال مصيدة ERR من الصدفة المستدعية.

#!/usr/bin/env bash
set -eEuo pipefail

on_error() {
    local exit_code=$?
    local line=$1
    echo "ERROR: command failed with exit code $exit_code at line $line" >&2
    echo "  Command: $BASH_COMMAND" >&2
}
trap 'on_error $LINENO' ERR

echo "Starting task..."
cp /nonexistent/file /tmp/   # This will fail
echo "This line is never reached."

مصيدة INT — التعامل السلس مع Ctrl+C

يؤدي الضغط على Ctrl+C إلى إرسال SIGINT إلى مجموعة العمليات الموجودة في الواجهة. ومن دون مصيدة، يخرج Bash فورًا، وقد يترك عملًا جزئيًا وراءه.

يتيح لكم تسجيل مصيدة INT ما يلي:

  • طباعة رسالة إلغاء واضحة وملائمة للمستخدم.
  • تنفيذ أي تراجع مرحلي قبل تمرير التنفيذ إلى مصيدة EXIT.
  • الخروج برمز حالة ذي معنى (يُعد 130 الرمز الاصطلاحي لـ SIGINT: 128 + 2).

بعد انتهاء معالج INT، استدعوا exit 130 لتمرير رمز الخروج الصحيح. وستُنفَّذ مصيدة EXIT تلقائيًا بعد ذلك.

#!/usr/bin/env bash
set -euo pipefail

TMPFILE=$(mktemp /tmp/demo.XXXXXX)

cleanup() {
    rm -f "$TMPFILE"
    echo "Temp file removed." >&2
}

on_interrupt() {
    echo "" >&2
    echo "Caught SIGINT — cancelling gracefully." >&2
    exit 130   # EXIT trap (cleanup) runs automatically after this
}

trap cleanup EXIT
trap on_interrupt INT

echo "Running long task. Press Ctrl+C to cancel."
for i in $(seq 1 10); do
    echo "Step $i/10..."
    sleep 1
done
echo "All steps complete."

دمج مصائد EXIT وERR وINT

في النصوص المستخدمة في بيئة الإنتاج، تسجّلون عادةً المصائد الثلاث كلها معًا لتغطية كل مسارات الخروج:

  • EXIT — تُنفَّذ دائمًا وتتولى تنظيف الملفات.
  • ERR — تسجّل الأمر الذي فشل ورقم السطر.
  • INT — تطبع رسالة إلغاء وتخرج بالرمز 130.

نظرًا إلى أن EXIT تُنفَّذ دائمًا في النهاية، فلا يحتاج منطق التنظيف إلى أن يكون إلا في موضع واحد. ويمكن لمعالجي ERR وINT التركيز على التسجيل وتعيين الحالة، ثم تفويض التنظيف الفعلي إلى EXIT من خلال استدعاء exit.

استخدموا set -eEuo pipefail في البداية لكي تؤدي الأخطاء في الدوال أيضًا إلى تشغيل ERR، ولكي تلتقط pipefail حالات الفشل داخل مسارات الأوامر.

#!/usr/bin/env bash
set -eEuo pipefail

TMPFILE=$(mktemp /tmp/combined.XXXXXX)
ROLLBACK_NEEDED=false

cleanup() {
    if [[ "$ROLLBACK_NEEDED" == true ]]; then
        echo "Rolling back partial changes..." >&2
    fi
    rm -f "$TMPFILE"
    echo "Cleanup done." >&2
}

on_error() {
    ROLLBACK_NEEDED=true
    echo "ERR at line $1: $BASH_COMMAND" >&2
}

on_interrupt() {
    echo "Interrupted by user." >&2
    exit 130
}

trap cleanup       EXIT
trap 'on_error $LINENO' ERR
trap on_interrupt  INT

echo "hello" > "$TMPFILE"
echo "Script finished successfully."

إعادة ضبط المصائد وتعطيلها ديناميكيًا

قد تحتاجون أحيانًا إلى تعديل مصيدة أثناء تنفيذ النص — مثلًا لإجراء تنظيف لمرة واحدة بعد اكتمال قسم حرج، بحيث لا تؤدي العمليات العادية اللاحقة إلى تراجع غير ضروري.

  • trap - SIGNAL يعيد ضبط الإشارة إلى سلوكها الافتراضي المضمّن.
  • trap '' SIGNAL يتجاهل الإشارة تمامًا (ولا يمكن إنهاء العملية بواسطتها).
  • يمكنكم تسجيل معالج جديد في أي وقت؛ ويكون لاستدعاء trap الأخير الأولوية.

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

#!/usr/bin/env bash
set -euo pipefail

TMPFILE=$(mktemp /tmp/staged.XXXXXX)

rollback() {
    echo "ROLLBACK: removing $TMPFILE" >&2
    rm -f "$TMPFILE"
}

cleanup_only() {
    echo "CLEANUP: removing $TMPFILE" >&2
    rm -f "$TMPFILE"
}

# Start with rollback in case something fails during the risky phase
trap rollback EXIT

echo "Performing risky operation..."
echo "critical data" > "$TMPFILE"
# ... imagine more risky steps here ...

# Risky phase succeeded — switch to simple cleanup
trap cleanup_only EXIT

echo "Risky phase done. Now doing safe finalization."
cp "$TMPFILE" /tmp/staged_result.txt
echo "All done."

تمرير رموز الخروج عبر المصائد

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

داخل معالج المصيدة، يحتوي $? على رمز الخروج الذي أدى إلى تشغيل المصيدة. التقطوا قيمته في السطر الأول من المعالج، قبل أن يغيّرها أي أمر آخر.

#!/usr/bin/env bash
set -euo pipefail

TMPFILE=$(mktemp /tmp/exitcode.XXXXXX)

cleanup() {
    local original_exit=$?   # capture BEFORE any other command
    echo "Cleaning up (original exit: $original_exit)" >&2
    rm -f "$TMPFILE"
    # Exit with the original code so callers see the real result
    exit "$original_exit"
}
trap cleanup EXIT

echo "Doing work..."
echo "data" > "$TMPFILE"

# Simulate failure
if [[ ! -f /tmp/required_marker ]]; then
    echo "Required marker missing!" >&2
    exit 2
fi

echo "Done."

المصائد في الدوال والصدفات الفرعية

يُعد فهم وراثة المصائد أمرًا بالغ الأهمية لكتابة نصوص صحيحة:

  • تعمل الدوال في الصدفة نفسها وترث المصائد — لكن ERR لا تنتقل إلى الدوال إلا عند تفعيل set -E (errtrace).
  • تبدأ الصدفات الفرعية (( ... )) بنسخة من مصائد الصدفة الأصلية، لكن التغييرات داخل الصدفة الفرعية لا تؤثر في الصدفة الأصلية.
  • تُعد مهام الخلفية (cmd &) صدفات فرعية، ولا ترث مصائد INT/TERM من الصدفة الأصلية (بل تحصل على معالجة الإشارات الافتراضية).

قاعدة عملية: أبقوا تسجيل المصائد في النص الرئيسي، واستخدموا set -E عندما تحتاجون إلى انتقال ERR إلى الدوال، ولا تعتمدوا مطلقًا على وراثة المصائد عبر &.

#!/usr/bin/env bash
set -eEuo pipefail   # -E = errtrace: ERR propagates into functions

on_error() {
    echo "ERR in function or main at line $1" >&2
}
trap 'on_error $LINENO' ERR

risky_function() {
    echo "Inside risky_function"
    ls /nonexistent_path   # triggers ERR — works because of set -E
}

# Subshell: has its own copy; changes don't affect parent
(
    trap '' ERR   # disable ERR only inside subshell
    ls /nonexistent_path 2>/dev/null || true
    echo "Subshell completed without triggering parent ERR"
)

risky_function

نمط واقعي: الاستبدال الذري للملفات

من أنماط Bash الدفاعية الكلاسيكية الاستبدال الذري للملفات: اكتبوا في ملف مؤقت، ثم أعيدوا تسميته إلى المسار المطلوب. فإذا فشل أي شيء قبل إعادة التسمية، يظل الملف الأصلي دون تغيير. وتجعل المصائد هذا النمط محكمًا.

الخطوات:

  1. أنشئوا ملفًا مؤقتًا في نظام الملفات نفسه الذي يوجد فيه الملف الهدف (لكي يكون mv ذريًا).
  2. سجّلوا مصيدة EXIT لإزالة الملف المؤقت إذا اضطررنا إلى الخروج مبكرًا.
  3. اكتبوا المحتوى الجديد وتحققوا من صحته.
  4. أعيدوا التسمية ذريًا (mv) — عند هذه النقطة فقط يُستبدل الملف الأصلي.
  5. تزيل مصيدة EXIT الملف المؤقت فقط إذا كان لا يزال موجودًا (فبعد نجاح mv يكون قد اختفى).
#!/usr/bin/env bash
set -euo pipefail

DEST="/tmp/important_config.conf"
TMPFILE=$(mktemp "$(dirname "$DEST")/.tmp.XXXXXX")

cleanup() {
    rm -f "$TMPFILE"   # no-op if mv already moved it
}
trap cleanup EXIT

# Write new content to temp file
cat > "$TMPFILE" <<'EOF'
[settings]
version=2
mode=production
EOF

# Validate before replacing
if ! grep -q 'version=' "$TMPFILE"; then
    echo "Validation failed — aborting replacement" >&2
    exit 1
fi

# Atomic rename — DEST is replaced only here
mv "$TMPFILE" "$DEST"
echo "Config updated atomically: $DEST"

اختبار المعرفة: وراثة مصيدة ERR

اختبروا مدى فهمكم لسلوك مصيدة ERR مع الدوال في Bash.

مراجعة: معالجات المصائد للتنظيف والإشارات

تعلّمتم في هذا الدرس كيفية جعل نصوص Bash أكثر مرونة باستخدام معالجات المصائد:

  • تُعد EXIT شبكة الأمان الشاملة — سجّلوها فورًا بعد إنشاء أي مورد مؤقت، والتقطوا دائمًا $? أولًا للحفاظ على رمز الخروج الأصلي.
  • تتيح لكم ERR تسجيل حالات الفشل باستخدام $LINENO و$BASH_COMMAND؛ استخدموا set -E لكي ترثها الدوال.
  • تتعامل INT مع Ctrl+C بسلاسة — اطبعوا رسالة واخرجوا بالرمز 130 لكي تتولى EXIT التنظيف.
  • اجمعوا الموارد المؤقتة في مصفوفة وأزيلوها جميعًا ضمن دالة تنظيف واحدة.
  • استخدموا trap - SIGNAL لإعادة الضبط، وtrap '' SIGNAL لتجاهل الإشارات ديناميكيًا.
  • يضمن نمط إعادة التسمية الذرية (mktemp → write → validate → mv)، بالاقتران مع مصيدة EXIT، ألا تؤدي عمليات الكتابة الجزئية إلى إفساد الملف الهدف.

يوفر الجمع بين set -eEuo pipefail والمصائد الموضوعة بعناية أساسًا دفاعيًا متينًا لأي نص Bash مخصص لبيئة الإنتاج.

الأسئلة الشائعة

هل درس «معالجات trap للتنظيف والإشارات» مجاني؟

نعم — نص درس «معالجات trap للتنظيف والإشارات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Linux Command Line & Bash Scripting Mastery، انتقل إلى CoddyKit PRO. تتضمن دورة Linux Command Line & Bash Scripting Mastery 4 دروس في المجموع.

ماذا ستتعلم في «معالجات trap للتنظيف والإشارات»؟

سجّل مصائد EXIT وERR وINT لحذف الملفات المؤقتة والتراجع عن الأعمال الجزئية بشكل موثوق تتمرن على Linux Command Line & Bash Scripting Mastery مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Linux Command Line & Bash Scripting Mastery؟

لا تُشترط خبرة سابقة. Linux Command Line & Bash Scripting Mastery على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «معالجات trap للتنظيف والإشارات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Linux Command Line & Bash Scripting Mastery هذا؟

نعم. كل درس في Linux Command Line & Bash Scripting Mastery يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. الوضع الصارم باستخدام set -euo pipefail
  2. معالجات trap للتنظيف والإشارات
  3. الملفات المؤقتة الآمنة ومجلدات القفل
  4. السكربتات القابلة للتكرار ومنطق إعادة المحاولة بالتراجع التدريجي
← العودة إلى Linux Command Line & Bash Scripting Mastery