Kem Intensif DevOps · Pelajaran

Mereka Bentuk Fungsi dengan Skop Setempat dan Kod Pulangan

Tulis fungsi yang menggunakan pemboleh ubah setempat, status keluar dan nilai pulangan berasaskan printf, bukannya global yang rapuh.

Pelajaran 1 daripada 413 langkah

Mereka Bentuk Fungsi dengan Skop Setempat dan Kod Pulangan ialah pelajaran Kem Intensif DevOps percuma di CoddyKit. Ini ialah pelajaran 1 daripada 4. Sebanyak 3 pelajaran dalam laluan pembelajaran ini boleh dibaca sepenuhnya secara percuma — selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan praktikal dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Kem Intensif DevOps, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Kem Intensif DevOps merangkumi sejumlah 4 pelajaran.

Mengapa Skop Fungsi Penting

Dalam Bash, pemboleh ubah secara lalai adalah global. Pemboleh ubah yang ditetapkan dalam fungsi akan bocor ke skop pemanggil melainkan anda mengisytiharkannya secara jelas sebagai local. Ini merupakan punca biasa pepijat halus dalam skrip shell.

  • Fungsi tanpa local boleh menulis ganti pemboleh ubah pemanggil secara senyap.
  • Pemboleh ubah local dimusnahkan apabila fungsi kembali.
  • Sempadan skop yang jelas menjadikan fungsi boleh digunakan semula dan boleh diuji secara berasingan.

Pelajaran ini mengajar anda menulis fungsi yang serba lengkap: fungsi tersebut menggunakan pemboleh ubah local, menyampaikan hasil melalui kod keluar dan printf, serta tidak pernah bergantung pada keadaan global tersirat.

Masalah Kebocoran Global

Berikut ialah contoh nyata kebocoran pemboleh ubah global. Fungsi set_name menetapkan pemboleh ubah bernama result, yang secara senyap-senyap menimpa pemboleh ubah result milik pemanggil sendiri.

Jalankan skrip ini dan perhatikan output yang tidak dijangka — result milik pemanggil hilang selepas panggilan fungsi.

#!/usr/bin/env bash

set_name() {
    result="Alice"   # No 'local' — this is GLOBAL
}

result="important data"
echo "Before: $result"

set_name

echo "After:  $result"   # Prints 'Alice', not 'important data'

Mengisytiharkan Pemboleh Ubah Tempatan dengan 'local'

Perintah terbina dalam local mengehadkan skop pemboleh ubah kepada fungsi yang mengandungi pemboleh ubah itu serta mana-mana fungsi yang dipanggilnya. Di luar fungsi tersebut, pemboleh ubah itu sama ada tidak ditetapkan atau mengekalkan nilai sebelumnya.

  • local varname — mengisytiharkan tanpa memberikan nilai.
  • local varname="value" — mengisytiharkan dan memberikan nilai dalam satu langkah.
  • local -i count=0 — mengisytiharkan pemboleh ubah tempatan bertipe integer.
  • local -r PI=3.14159 — mengisytiharkan pemalar tempatan baca sahaja.

Amalan terbaik: isytiharkan setiap pemboleh ubah dalam fungsi sebagai local kecuali jika anda memang memerlukannya sebagai pemboleh ubah global.

#!/usr/bin/env bash

greet() {
    local name="$1"          # local — safe
    local greeting="Hello, ${name}!"
    echo "$greeting"
}  # 'name' and 'greeting' vanish here

name="global value"
greet "Bob"
echo "name is still: $name"   # Prints 'global value'

Kod Keluar sebagai Nilai Pulangan

Fungsi Bash tidak boleh return rentetan — return hanya menetapkan status keluar integer (0–255). Mengikut kebiasaan:

  • return 0 — berjaya
  • return 1 (atau sebarang nilai bukan sifar) — gagal

Pemanggil membaca status keluar melalui $? sejurus selepas panggilan, atau menggunakan fungsi itu secara langsung dalam syarat if. Kod keluar ialah cara lazim untuk menandakan kejayaan atau kegagalan daripada sesuatu fungsi.

#!/usr/bin/env bash

is_even() {
    local -i n="$1"
    (( n % 2 == 0 ))   # arithmetic command: exits 0 if true, 1 if false
}

for num in 2 3 7 10; do
    if is_even "$num"; then
        echo "$num is even"
    else
        echo "$num is odd"
    fi
done

Menyampaikan Hasil Rentetan melalui printf

Apabila fungsi perlu mengembalikan hasil rentetan, corak piawai ialah mencetak ke stdout dan menangkapnya dengan penggantian perintah $(). Penggunaan printf berbanding echo lebih digalakkan kerana:

  • printf tidak menambahkan baris baharu di hujung secara lalai (kecuali anda menyertakan \n).
  • Tingkah laku printf adalah konsisten dan ditakrifkan oleh POSIX; echo berbeza-beza mengikut shell.
  • Penggantian perintah membuang baris baharu di hujung, jadi printf '%s' "$value" adalah tepat.
#!/usr/bin/env bash

to_uppercase() {
    local input="$1"
    printf '%s' "${input^^}"   # Bash 4+ parameter expansion
}

word="hello"
upper=$(to_uppercase "$word")
echo "Original: $word"
echo "Upper:    $upper"

Menggabungkan Kod Keluar dan Output stdout

Fungsi yang direka dengan baik boleh mencetak hasil (apabila berjaya) dan menandakan kegagalan (melalui kod keluar) pada masa yang sama. Pemanggil menentukan tindakan berdasarkan kod keluar sebelum mempercayai output tersebut.

Corak di bawah digunakan secara meluas dalam pustaka Bash sebenar:

  • Apabila berjaya: gunakan printf untuk mencetak hasil dan return 0.
  • Apabila gagal: tulis diagnostik ke stderr (bukan stdout) dan gunakan return 1.
  • Menulis ralat ke stderr memastikan stdout kekal bersih untuk saluran paip.
#!/usr/bin/env bash

divide() {
    local -i numerator="$1"
    local -i denominator="$2"

    if (( denominator == 0 )); then
        printf 'Error: division by zero\n' >&2
        return 1
    fi

    printf '%d' $(( numerator / denominator ))
    return 0
}

if result=$(divide 20 4); then
    echo "20 / 4 = $result"
else
    echo "Division failed."
fi

if result=$(divide 10 0); then
    echo "10 / 0 = $result"
else
    echo "Division failed (caught the error)."
fi

Menggunakan 'local' untuk Melindungi Fungsi Rekursif

Rekursi ialah salah satu contoh paling jelas tentang sebab local sangat penting. Setiap panggilan rekursif mendapat salinan berasingan bagi setiap pemboleh ubah local pada tindanan panggilan. Tanpa local, setiap panggilan akan menimpa pemboleh ubah global yang sama dan menghasilkan keputusan yang salah.

Fungsi faktorial di bawah selamat kerana n dan sub adalah tempatan kepada setiap bingkai tindanan.

#!/usr/bin/env bash

factorial() {
    local -i n="$1"
    local -i sub

    if (( n <= 1 )); then
        printf '1'
        return 0
    fi

    sub=$(factorial $(( n - 1 )))
    printf '%d' $(( n * sub ))
}

for i in 1 2 3 4 5 6; do
    echo "${i}! = $(factorial $i)"
done

Mengelakkan Perangkap Subshell dengan local -n (Nameref)

Penggantian perintah $() dijalankan dalam subshell. Sebarang tugasan pemboleh ubah di dalamnya tidak kelihatan oleh shell induk. Apabila anda perlu membolehkan fungsi menulis ke dalam pemboleh ubah yang diberikan oleh pemanggil tanpa subshell, gunakan nameref (local -n), yang tersedia dalam Bash 4.3 dan versi lebih baharu.

  • local -n ref="$1" menjadikan ref sebagai alias untuk pemboleh ubah yang namanya disimpan dalam $1.
  • Memberikan nilai kepada ref di dalam fungsi mengubah pemboleh ubah pemanggil secara langsung.
  • Ini mengelakkan subshell sambil mengekalkan butiran pelaksanaan secara tempatan.
#!/usr/bin/env bash

# Fills caller's array by reference — no subshell needed
read_csv_line() {
    local -n _out="$1"    # nameref to caller's variable
    local line="$2"
    local IFS=','
    read -ra _out <<< "$line"
}

declare -a fields
read_csv_line fields "alice,30,engineer"

echo "Name:  ${fields[0]}"
echo "Age:   ${fields[1]}"
echo "Role:  ${fields[2]}"

Membina Pustaka Fungsi Kecil

Projek Bash dunia sebenar membahagikan fungsi yang boleh digunakan semula kepada fail pustaka yang dimuatkan oleh skrip dengan source (atau operator titik .). Peraturan reka bentuk pustaka yang baik:

  • Setiap pemboleh ubah dalam fungsi pustaka mestilah local.
  • Fungsi pustaka tidak pernah menggunakan exit — fungsi tersebut menggunakan return, supaya pemanggil terus berjalan.
  • Gunakan awalan ruang nama yang konsisten (contohnya, str_, log_) untuk mengelakkan pertembungan nama.
  • Lindungi daripada pemuatan sumber berganda dengan pemboleh ubah penanda.

Di bawah ialah pustaka utiliti rentetan minimum yang mengikuti konvensyen ini.

#!/usr/bin/env bash
# lib/str.sh  — string utility library

[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1

str_trim() {
    local str="$1"
    str="${str#"${str%%[![:space:]]*}"}"
    str="${str%"${str##*[![:space:]]}"}" 
    printf '%s' "$str"
}

str_repeat() {
    local -i times="$2"
    local char="$1"
    local -i i
    for (( i = 0; i < times; i++ )); do
        printf '%s' "$char"
    done
}

str_contains() {
    local haystack="$1"
    local needle="$2"
    [[ "$haystack" == *"$needle"* ]]
}

# --- self-test when executed directly ---
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
    trimmed=$(str_trim "   hello world   ")
    echo "Trimmed: '${trimmed}'"
    str_repeat '-' 20; echo
    if str_contains "bash scripting" "script"; then
        echo "Contains: yes"
    fi
fi

Mengesahkan Argumen dalam Fungsi

Fungsi yang menerima argumen hendaklah mengesahkannya lebih awal dan mengembalikan kod keluar khusus apabila input tidak sah. Ini dipanggil corak klausa pengawal — gagal dengan cepat dan jelas.

  • Semak bilangan argumen dengan $#.
  • Sahkan jenis atau format sebelum melakukan sebarang kerja.
  • Cetak mesej diagnostik ke stderr sahaja, jangan sekali-kali ke stdout.
  • Gunakan kod pulangan bukan sifar yang berbeza (contohnya, 1 = argumen salah, 2 = fail tidak ditemui) supaya pemanggil boleh bertindak balas secara berbeza terhadap mod kegagalan yang berlainan.
#!/usr/bin/env bash

file_line_count() {
    if (( $# != 1 )); then
        printf 'Usage: file_line_count <file>\n' >&2
        return 1
    fi

    local file="$1"

    if [[ ! -f "$file" ]]; then
        printf 'Error: not a file: %s\n' "$file" >&2
        return 2
    fi

    if [[ ! -r "$file" ]]; then
        printf 'Error: cannot read: %s\n' "$file" >&2
        return 3
    fi

    local -i count
    count=$(wc -l < "$file")
    printf '%d' "$count"
    return 0
}

# Test with /etc/hosts (exists on every Linux/macOS system)
if lines=$(file_line_count /etc/hosts); then
    echo "/etc/hosts has $lines lines"
else
    echo "Failed with exit code: $?"
fi

Menggabungkan Semuanya: Contoh Dunia Sebenar

Berikut ialah skrip lengkap dan kendiri yang menunjukkan semua konsep daripada pelajaran ini berfungsi bersama:

  • Pemboleh ubah local dalam setiap fungsi.
  • Kod keluar untuk menandakan kejayaan atau kegagalan.
  • printf untuk menyampaikan hasil rentetan.
  • Ralat ditulis ke stderr dan hasil ke stdout.
  • Klausa pengawal untuk pengesahan argumen.

Kaji alirannya: parse_version mengekstrak data, version_ge membandingkannya, dan main menggunakan kedua-duanya dengan kemas.

#!/usr/bin/env bash

# Parse a semver string into components via nameref
parse_version() {
    local -n _major="$2" _minor="$3" _patch="$4"
    local version="$1"
    local IFS='.'
    local -a parts
    read -ra parts <<< "$version"
    _major="${parts[0]:-0}"
    _minor="${parts[1]:-0}"
    _patch="${parts[2]:-0}"
}

# Return 0 if version $1 >= version $2
version_ge() {
    local -i maj_a min_a pat_a
    local -i maj_b min_b pat_b
    parse_version "$1" maj_a min_a pat_a
    parse_version "$2" maj_b min_b pat_b

    if   (( maj_a != maj_b )); then (( maj_a > maj_b ))
    elif (( min_a != min_b )); then (( min_a > min_b ))
    else                             (( pat_a >= pat_b ))
    fi
}

require_bash_version() {
    local required="$1"
    local actual="${BASH_VERSION%%(*}"
    if version_ge "$actual" "$required"; then
        printf 'Bash %s satisfies >= %s\n' "$actual" "$required"
        return 0
    else
        printf 'Error: need Bash >= %s, got %s\n' "$required" "$actual" >&2
        return 1
    fi
}

main() {
    require_bash_version "4.3" || return 1
    require_bash_version "99.0" || true   # demonstrates failure path
}

main

Semakan Pengetahuan: Pemboleh Ubah Tempatan dan Nilai Pulangan

Pertimbangkan fungsi Bash berikut. Apakah cara yang betul untuk menangkap hasil rentetannya dalam pemanggil, dan pernyataan manakah tentang pemboleh ubah tmp yang benar?

transform() {
    local tmp="${1,,}"   # lowercase
    printf '%s' "$tmp"
    return 0
}

Imbas Kembali: Fungsi dengan Skop Tempatan dan Kod Pulangan

Dalam pelajaran ini, anda telah mempelajari cara menulis fungsi Bash yang kemas, boleh digabungkan dan selamat:

  • Sentiasa gunakan local untuk pemboleh ubah dalam fungsi bagi mengelakkan pencemaran skop pemanggil.
  • Gunakan kod keluar (return 0/1/N) untuk menandakan kejayaan atau kegagalan — kod ini berintegrasi secara semula jadi dengan if, && dan ||.
  • Gunakan printf ke stdout untuk menyampaikan hasil rentetan; tangkap hasil tersebut dengan $() dalam pemanggil.
  • Tulis ralat ke stderr (>&2) supaya stdout kekal bersih untuk aliran data dan saluran paip.
  • Gunakan local -n (nameref) apabila anda perlu menulis ke dalam pemboleh ubah yang diberikan oleh pemanggil tanpa kos tambahan subshell.
  • Klausa pengawal (sahkan argumen lebih awal, pulangkan serta-merta apabila input tidak sah) menjadikan fungsi kukuh dan menerangkan perilakunya sendiri.
  • Fail pustaka hendaklah dimuatkan dengan source, menggunakan awalan ruang nama, tidak pernah memanggil exit, dan dilindungi daripada pemuatan sumber berganda.

Menguasai corak ini membezakan skrip sekali guna yang rapuh daripada pangkalan kod Bash profesional yang mudah diselenggara.

Percuma untuk bermula

Pelajari Kem Intensif DevOps dengan tutor kecerdasan buatan — percuma

Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.

Kursus
142
Pelajaran
568

Soalan Lazim

Adakah pelajaran “Mereka Bentuk Fungsi dengan Skop Setempat dan Kod Pulangan” percuma?

Ya — sebanyak 3 pelajaran dalam laluan pembelajaran Kem Intensif DevOps, termasuk “Mereka Bentuk Fungsi dengan Skop Setempat dan Kod Pulangan”, boleh dibaca sepenuhnya secara percuma di web ini. Selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan interaktif dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Kursus Kem Intensif DevOps merangkumi sejumlah 4 pelajaran.

Apakah yang akan saya pelajari dalam “Mereka Bentuk Fungsi dengan Skop Setempat dan Kod Pulangan”?

Tulis fungsi yang menggunakan pemboleh ubah setempat, status keluar dan nilai pulangan berasaskan printf, bukannya global yang rapuh. Anda berlatih Kem Intensif DevOps menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.

Adakah saya memerlukan pengalaman untuk memulakan Kem Intensif DevOps?

Tiada pengalaman terdahulu diperlukan. Pembelajaran Kem Intensif DevOps di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 1 daripada 4.

Berapa lamakah pelajaran “Mereka Bentuk Fungsi dengan Skop Setempat dan Kod Pulangan” diambil?

Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.

Bolehkah saya menulis dan menjalankan kod dalam pelajaran Kem Intensif DevOps ini?

Ya. Setiap pelajaran Kem Intensif DevOps menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.

Semua pelajaran dalam kursus ini

  1. Mereka Bentuk Fungsi dengan Skop Setempat dan Kod Pulangan
  2. Membina dan Memanggil Pustaka Bash Boleh Guna Semula
  3. Menghurai Bendera dan Argumen dengan getopts
  4. Menghantar Tatasusunan dan Peta Bersekutu antara Fungsi
← Kembali ke Kem Intensif DevOps