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

تمرير المصفوفات والخرائط الترابطية بين الدوال

استخدم namerefs والتوسعة غير المباشرة لتمرير هياكل البيانات المعقدة إلى الدوال ومنها بأمان.

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

لماذا يُعد تمرير المصفوفات إلى الدوال أمرًا معقدًا

في Bash، لا تُعد المصفوفات والمصفوفات الترابطية قيمًا من الدرجة الأولى. عند محاولة تمرير مصفوفة إلى دالة، لا يمكنك استخدام $my_array ببساطة، إذ يؤدي ذلك إلى توسيع العنصر الأول فقط.

تأمل هذه المحاولة غير الصحيحة:

my_func "${my_array[@]}"

تستقبل الدالة قائمة مسطحة من الكلمات. ولا تملك أي وسيلة لمعرفة أين ينتهي أحد العناصر ويبدأ العنصر التالي، كما تفقد بنية المصفوفة بالكامل.

يوفر Bash آليتين موثوقتين لتمرير هياكل البيانات المعقدة:

  • Namerefs (declare -n) — مرجع إلى متغير آخر باستخدام اسمه (في Bash 4.3 والإصدارات الأحدث)
  • Indirect expansion (${!varname[@]}) — الوصول إلى متغير يُخزَّن اسمه في متغير آخر (في إصدارات Bash الأقدم)

يغطي هذا الدرس كلتا الطريقتين، لتتمكنوا من كتابة دوال قوية قابلة لإعادة الاستخدام تعمل مع المصفوفات والخرائط.

تمرير مصفوفة باستخدام اسمها عبر Nameref

أنظف نهج حديث هو استخدام nameref: تمررون اسم المصفوفة كسلسلة نصية، ثم تعرّفون داخل الدالة متغيرًا محليًا يمثل مرجعًا إليها.

الصياغة الأساسية:

  • declare -n ref="$1" — يصبح ref اسمًا مستعارًا لأي اسم متغير تم تمريره كوسيط أول
  • يمكنكم قراءة ref والتكرار عبر عناصره وتعديله تمامًا كما لو كان المصفوفة الأصلية

يؤدي ذلك إلى تجنب نسخ المصفوفة بأكملها، ويتيح للدالة التعامل مع مصفوفات بأي حجم.

#!/usr/bin/env bash

print_array() {
    declare -n arr="$1"   # nameref: arr -> caller's variable
    echo "Array has ${#arr[@]} elements:"
    for elem in "${arr[@]}"; do
        echo "  - $elem"
    done
}

fruits=(apple banana cherry)
print_array fruits

تعديل مصفوفة المستدعي داخل دالة

بما أن nameref هو اسم مستعار حقيقي، فإن الكتابة من خلال nameref تغيّر المتغير الأصلي للمستدعي. ولا حاجة إلى إعادة القيم باستخدام echo أو إلى استخدام المتغيرات العامة.

يجعل ذلك namerefs مثالية للدوال التي تحوّل المصفوفات في موضعها، مثل توحيد تنسيق السلاسل النصية أو تصفية العناصر أو فرزها.

#!/usr/bin/env bash

to_uppercase() {
    declare -n _arr="$1"
    for i in "${!_arr[@]}"; do
        _arr[$i]="${_arr[$i]^^}"   # ^^ = uppercase in Bash 4+
    done
}

words=(hello world bash)
echo "Before: ${words[@]}"
to_uppercase words
echo "After:  ${words[@]}"

إرجاع مصفوفة جديدة عبر Nameref

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

يحاكي هذا النمط الأسلوب التقليدي في C المتمثل في تمرير مؤشر إلى مخزن نتائج، لكن تنفيذه هنا يتم بالكامل داخل الصدفة.

#!/usr/bin/env bash

get_even_numbers() {
    local -a source=("$@")
    # Last argument is the output array name — pop it off
    local out_name="${source[-1]}"
    unset 'source[-1]'

    declare -n _out="$out_name"
    _out=()   # clear
    for n in "${source[@]}"; do
        (( n % 2 == 0 )) && _out+=("$n")
    done
}

declare -a evens
get_even_numbers 1 2 3 4 5 6 7 8 evens
echo "Evens: ${evens[@]}"

فخ تعارض أسماء Nameref

هناك مشكلة خطيرة واحدة في namerefs: إذا شارك متغير nameref المحلي الاسم نفسه مع متغير المستدعي، يدخل Bash في حلقة إحالة ذاتية لا نهائية ويفشل البرنامج النصي.

على سبيل المثال، إذا سمّى المستدعي مصفوفتَه arr ونفذت الدالة declare -n arr="$1"، فإن nameref المسمى arr سيشير إلى نفسه.

القاعدة المتبعة لتجنب ذلك: أضيفوا شرطة سفلية في بداية أسماء متغيرات nameref الداخلية، مثل _arr و_ref و_out. فنادرًا ما يستخدم المستدعون هذه الأسماء، مما يجعل التعارضات نادرة للغاية.

#!/usr/bin/env bash

# DANGER: caller uses variable named 'arr'
arr=(one two three)

bad_func() {
    declare -n arr="$1"   # collision! arr refers to itself
    echo "${arr[@]}"
}

good_func() {
    declare -n _arr="$1"  # underscore prefix avoids collision
    echo "${_arr[@]}"
}

# bad_func arr   # would hang / error
good_func arr

تمرير المصفوفات الترابطية (الخرائط) باستخدام الاسم

تعمل المصفوفات الترابطية (declare -A) بالطريقة نفسها مع namerefs. مرروا اسم الخريطة كسلسلة نصية، وعرّفوا nameref داخل الدالة، لتحصلوا على صلاحية كاملة لقراءة جميع أزواج المفتاح والقيمة وتعديلها.

يُعد ذلك بالغ الأهمية لخرائط الإعدادات ومحللات الخيارات وجداول البحث التي يجب مشاركتها بين دوال المكتبة.

#!/usr/bin/env bash

print_map() {
    declare -n _map="$1"
    echo "Map contents:"
    for key in "${!_map[@]}"; do
        printf '  %-15s => %s\n' "$key" "${_map[$key]}"
    done
}

declare -A config
config[host]="localhost"
config[port]="5432"
config[db]="myapp"

print_map config

دمج خريطتين باستخدام Nameref

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

تتيح namerefs كتابة دالة عامة واحدة باسم merge_map تعمل مع أي مصفوفات ترابطية في البرنامج النصي.

#!/usr/bin/env bash

merge_map() {
    # merge_map dest src
    declare -n _dest="$1"
    declare -n _src="$2"
    for key in "${!_src[@]}"; do
        _dest["$key"]="${_src[$key]}"
    done
}

declare -A defaults=([timeout]=30 [retries]=3 [verbose]=false)
declare -A overrides=([retries]=5 [log_level]=debug)

merge_map defaults overrides

for k in "${!defaults[@]}"; do
    echo "$k = ${defaults[$k]}"
done

التوسيع غير المباشر: التوافق مع إصدارات Bash الأقدم

إذا كان يجب دعم Bash 4.0–4.2 (أو الصدفات الافتراضية الأقدم في macOS)، فلن يكون declare -n متاحًا. والبديل هو التوسيع غير المباشر باستخدام ${!varname}.

يقيّم التوسيع غير المباشر قيمة varname باعتبارها اسم متغير، ثم يعيد قيمة ذلك المتغير. وبالنسبة إلى المصفوفات، تدمجه مع صياغة خاصة للحصول على جميع العناصر.

القيود مقارنةً بـ namerefs:

  • للقراءة فقط عمليًا (إذ تتطلب الكتابة مجددًا استخدام eval، الذي ينطوي على مخاطر الحقن)
  • لا يمكن تكرار مفاتيح المصفوفات الترابطية بأمان من دون eval
  • استخدموا namerefs متى أمكن، والجؤوا إلى التوسيع غير المباشر عند الضرورة فقط
#!/usr/bin/env bash

sum_array() {
    local arr_name="$1"
    # Indirect: build a temporary copy by expanding arr_name[@]
    local -a items
    eval "items=( \"\${${arr_name}[@]}\" )"
    local total=0
    for n in "${items[@]}"; do
        (( total += n ))
    done
    echo "$total"
}

numbers=(10 20 30 40)
result=$(sum_array numbers)
echo "Sum: $result"

إنشاء مكتبة نصوص برمجية معيارية

في مشاريع Bash الاحترافية، تُخزَّن دوال الأدوات في ملفات مكتبة (مثل lib/array_utils.sh)، وتستوردها البرامج النصية التي تحتاج إليها.

النمط المتبع هو:

  • يعرّف كل ملف مكتبة الدوال فقط، من دون تأثيرات جانبية على المستوى الأعلى
  • الحماية من استيراد الملف مرتين باستخدام متغير حارس: [[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1
  • تستخدم البرامج النصية المستهلكة source أو . لتحميل المكتبة
  • تتبادل الدوال البيانات المعقدة عبر namerefs، مع إبقاء المنطق بأكمله داخل الدوال

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

مثال عملي: محلل خيارات يعيد خريطة

حالة استخدام واقعية: محلل لخيارات سطر الأوامر يملأ مصفوفة ترابطية يمررها المستدعي. يمرر المستدعي اسم المصفوفة كوسيط أول، تليه وسائط "$@" الفعلية.

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

#!/usr/bin/env bash

parse_opts() {
    declare -n _opts="$1"
    shift
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --host)   _opts[host]="$2";   shift 2 ;;
            --port)   _opts[port]="$2";   shift 2 ;;
            --user)   _opts[user]="$2";   shift 2 ;;
            *)        echo "Unknown option: $1" >&2; shift ;;
        esac
    done
}

declare -A opts=([host]=localhost [port]=5432 [user]=admin)
parse_opts opts --host db.example.com --port 3306

echo "Connecting to ${opts[host]}:${opts[port]} as ${opts[user]}"

دمج Namerefs للإدخال والإخراج في دالة واحدة

يمكن للدوال الاحتفاظ بعدة namerefs في الوقت نفسه، واحد للإدخال وآخر للإخراج. يتيح ذلك إنشاء دوال تحويل خالصة: تقرأ من بنية مصدر، وتحسب نتيجة، وتكتبها في بنية وجهة، وكل ذلك من دون المساس بالحالة العامة.

يؤدي استخدام أسماء مختلفة تبدأ بشرطة سفلية لكل nameref إلى استحالة التعارضات، حتى عندما تكون أسماء متغيرات المستدعي بسيطة.

#!/usr/bin/env bash

filter_map_by_key_prefix() {
    # Usage: filter_map_by_key_prefix src_map dest_map prefix
    declare -n _src="$1"
    declare -n _dst="$2"
    local prefix="$3"

    _dst=()
    for key in "${!_src[@]}"; do
        if [[ $key == ${prefix}* ]]; then
            _dst["$key"]="${_src[$key]}"
        fi
    done
}

declare -A settings=(
    [db_host]=localhost [db_port]=5432
    [cache_ttl]=300     [cache_driver]=redis
    [app_debug]=true
)
declare -A db_settings

filter_map_by_key_prefix settings db_settings db_

for k in "${!db_settings[@]}"; do
    echo "$k = ${db_settings[$k]}"
done

اختبار المعرفة: Namerefs وتمرير المصفوفات

اختبروا فهمكم لتمرير المصفوفات والخرائط بين الدوال في Bash.

مراجعة: تمرير هياكل البيانات المعقدة في Bash

إليكم ما تعلمتموه في هذا الدرس:

  • لا يمكن تمرير المصفوفات بالقيمة، لذا مرروا اسم المتغير كسلسلة نصية بدلًا من ذلك.
  • declare -n ref="$varname" ينشئ nameref، وهو اسم مستعار حي لمتغير المستدعي. تمر عمليات القراءة والكتابة معًا إلى المتغير الأصلي. ويتطلب Bash 4.3 أو إصدارًا أحدث.
  • يحدث تعارض nameref عندما يتشارك المتغير الداخلي والخارجي الاسم نفسه. امنعوا ذلك بإضافة بادئة إلى namerefs الداخلية، مثل _arr و_map.
  • تعمل معاملات الإخراج عبر تمرير اسم مصفوفة أو خريطة نتائج فارغة؛ ثم تملؤها الدالة من خلال nameref.
  • تتيح namerefs المتعددة في دالة واحدة إجراء تحويلات خالصة من الإدخال إلى الإخراج من دون تأثيرات جانبية عامة.
  • يُعد التوسيع غير المباشر (${!varname[@]} + eval) بديلًا أقدم للإصدارات الأقدم من Bash 4.3، لكنه ينطوي على مخاطر الحقن، ويكون للقراءة فقط عمليًا.
  • اجمعوا هذه الأساليب مع ملفات المكتبة المستوردة لإنشاء قواعد شيفرة Bash معيارية وقابلة لإعادة الاستخدام والاختبار.

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

هل درس «تمرير المصفوفات والخرائط الترابطية بين الدوال» مجاني؟

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

ماذا ستتعلم في «تمرير المصفوفات والخرائط الترابطية بين الدوال»؟

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

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

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

كم من الوقت يستغرق درس «تمرير المصفوفات والخرائط الترابطية بين الدوال»؟

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

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

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

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

  1. تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع
  2. بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها
  3. تحليل الأعلام والوسائط باستخدام getopts
  4. تمرير المصفوفات والخرائط الترابطية بين الدوال
← العودة إلى Linux Command Line & Bash Scripting Mastery