تا

تابع در Bash

{one: "۱ تمرین", other: "%{count} تمرین"}

درباره‌ی تابع

بسیاری از اسکریپت‌های Bash با سبک کاملاً امری نوشته می‌شوند: یک دستور را اجرا کنید، بعد دستور دیگری را اجرا کنید و به همین ترتیب. گاهی لازم است دنباله‌ای از دستورها را کنار هم بگذارید که در مفهوم یک هدف واحد را انجام می‌دهند. اینجاست که «توابع» به کار می‌آیند.

تعریف یک تابع

یک تابع را این‌طور تعریف می‌کنید:

my_function () {
    COMMANDS
}

مجموعه‌ی خالی پرانتزها فقط نشان می‌دهد که دارید یک تابع تعریف می‌کنید. چیزی داخل آن‌ها قرار نمی‌گیرد.

پارامترهای تابع

توابع، پس از تعریف، مثل هر دستور دیگری عمل می‌کنند (توکار باشد یا نباشد). مثل هر دستور دیگری، می‌توانید «آرگومان‌ها» را به تابع‌هایتان بدهید. داخل تابع، با استفاده از «پارامترهای موقعیتی» یعنی $1، $2 و غیره به آرگومان‌ها دسترسی دارید. (به یاد بیاورید که درباره‌ی پارامترهای موقعیتی در مفهوم متغیر خواندیم.)

Advanced

پارامتر ویژه‌ی $0 داخل تابع تغییر نمی‌کند؛ همچنان اسم اسکریپتی است که در حال اجراست. تابعی که در حال اجراست می‌تواند با متغیر $FUNCNAME به اسم خودش دسترسی داشته باشد.

بخش ۳٫۴٫۲ پارامترهای ویژه را در راهنما ببینید.

متغیر

می‌توانید داخل یک تابع متغیر تعریف کنید. اگر متغیر را با دستور local اعلان کنید، «دامنه‌ی» متغیر به تابع جاری محدود می‌شود (و به هر تابعی که توسط آن فراخوانی شود). در غیر این صورت، متغیر در «دامنه‌ی سراسری» قرار می‌گیرد.

متغیرهای محلی می‌توانند اسمی یکسان با یک متغیر سراسری داشته باشند. در آن صورت، متغیر محلی بر متغیر سراسری سایه می‌اندازد. برای نمونه، متغیر محلی‌ای که در یک تابع اعلان شده، متغیر سراسری همنام را پنهان می‌کند: ارجاع‌ها و انتساب‌ها به متغیر محلی اشاره دارند و متغیر سراسری بدون تغییر می‌ماند. وقتی تابع برمی‌گردد، متغیر سراسری دوباره قابل مشاهده می‌شود.

x=5

myfunc () {
    local x=100
    echo "in my function, $x == 100"
}

echo "in the global scope, $x == 5"

myfunc

echo "back in the global scope, $x == 5"

خروجی این است

in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5

داخل یک تابع، می‌توانید به متغیرهای موجود در دامنه‌ی «فراخواننده» دسترسی داشته باشید. یعنی می‌توانید از متغیرهای سراسری استفاده کنید و همچنین از متغیرهای محلی‌ای که در فراخواننده (یا در تابعی که فراخواننده را فراخوانی می‌کند) اعلان شده‌اند.

Advanced

از نظر فنی، «سراسری» واژه‌ی درستی نیست. برای بسط دادن یک متغیر در یک تابع، Bash «پشته‌ی فراخوانی» را تا دامنه‌ی سراسری بالا می‌رود تا تابعی را پیدا کند که اسم آن متغیر در آن اعلان شده است.

این مثال از بخش توابع پوسته در راهنما برگرفته شده است:

func1() {
    local var='func1 local'
    func2
}

func2() {
    echo "In func2, var = $var"
}

var=global
func1
func2

خروجی چنین است:

In func2, var = func1 local
In func2, var = global

به‌همین‌ترتیب، وقتی مقداری را به یک متغیر انتساب می‌دهید، این انتساب در همان دامنه‌ای که متغیر اعلان شده است انجام می‌شود. این «کنش از راه دور» می‌تواند کدی سخت‌فهم بسازد، چون همیشه روشن نیست که مقدار یک متغیر کجا انتساب داده شده است.

Advanced

می‌توان پشته‌ی فراخوانی را با متغیر آرایه‌ای FUNCNAME بررسی کرد.

مقادیر بازگشتی

یک تابع، مثل هر دستور دیگری، «وضعیت خروج» دارد. به‌طور پیش‌فرض، وضعیت یک تابع همان وضعیت خروج آخرین دستور اجراشده است.

می‌توانید از دستور return استفاده کنید تا با یک وضعیت خروج مشخص از تابع برگردید.

check_password () {
    if [[ $1 == "secret" ]]; then
        return 0
    else
        return 1
    fi
}

read -sp "Enter your password: " pass

if check_password "$pass"; then
    echo "Correct!"
else
    echo "Wrong password."
fi

استفاده از return بدون آرگومان، وضعیت آخرین دستور اجراشده را برمی‌گرداند.

Note

توجه کنید که می‌توان تابع check_password را به این صورت ساده کرد:

check_password () { [[ $1 == "secret" ]]; }

۱. ساختار شرطی [[...]] یک وضعیت خروج دارد: 0 برای «درست»، 1 برای «غلط». ۲. ساختار گروه‌بندی {...} باید پیش از آکولاد پایانی، یک خط جدید یا یک نقطه‌ویرگول داشته باشد.

خروجی تابع

وضعیت بازگشتی یک تابع فقط یک عدد است. پس یک تابع چگونه می‌تواند خروجی تولید کند؟

تابع شما می‌تواند در خروجی استاندارد چاپ کند. برای گرفتن آن، از «جانشینی دستور» که با آن آشنا هستید استفاده کنید:

d6 () { echo "$(( 1 + RANDOM % 6 ))"; }

die=$( d6 )
echo "You rolled a $die."

استفاده هم‌زمان از خروجی و وضعیت

وضعیت خروج یک تابع حتی وقتی خروجی را در متغیری می‌گیرید هم در دسترس است.

roll () {
    local n=$1
    if (( 4 <= n && n <= 20 )); then
        echo "$(( 1 + RANDOM % n ))"  # exit status is 0
    else
        return 1
    fi
}

read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
    echo "You rolled a $die."
else
    echo "I can't roll a die with $faces faces."
fi

بازگشت

توابع می‌توانند خودشان را به‌صورت بازگشتی فراخوانی کنند. به‌طور پیش‌فرض، محدودیتی برای عمق بازگشت وجود ندارد.

یک مثال:

fibonacci() {
    local n=$1
    if (( n <= 1 )); then
        echo "1"
    else
        local a=$(fibonacci "$(( n - 1 ))")
        local b=$(fibonacci "$(( n - 2 ))")
        echo "$(( a + b ))"
    fi
}

for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
Advanced

عمق بازگشت را می‌توان با متغیر FUNCNEST کنترل کرد.

bash -c '
    recur() {
        echo $1
        recur $(($1 + 1))
    }
    FUNCNEST=5
    recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)
ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود