Bash برای Oracle DBAها — بخش ۷: Functions و استفاده مجدد در Bash
تا اینجا با متغیرها، شرطها، حلقهها، exit status و command substitution آشنا شدیم. اما وقتی اسکریپتهای Oracle DBA بزرگتر و پیچیدهتر میشوند — مثلاً یک اسکریپت Audit که دهها check مختلف انجام میدهد — تکرار کد و دشواری نگهداری مشکلات جدی ایجاد میکند. راهحل استاندارد در Bash، استفاده از Functions است.
در این مقاله با تعریف تابع، ارسال پارامتر، متغیرهای محلی، بازگشت مقدار و الگوهای رایج در اسکریپتهای Audit/Remediation آشنا میشویم.
۱) چرا Functions؟
بدون تابع، اگر یک بلوک منطق (مثلاً بررسی وجود یک فایل و چاپ پیام PASS/FAIL) در چند جای اسکریپت لازم باشد، باید آن را کپی کنید. این یعنی:
- کد تکراری و مستعد خطا؛
- اگر منطق تغییر کند، باید همهی نسخهها را دستی اصلاح کنید؛
- خوانایی کاهش مییابد.
Function به ما اجازه میدهد یک بلوک کد را یکبار تعریف کنیم و بارها صدا بزنیم. این دقیقاً همان فلسفهی PROCEDURE و FUNCTION در Oracle PL/SQL است.
۲) تعریف Function — دو شکل سینتکس
در Bash دو روش برای تعریف تابع وجود دارد:
# شکل ۱: با کلیدواژهی function
function greet {
echo "Hello, Oracle DBA!"
}
# شکل ۲: با پرانتز (POSIX-compatible)
greet() {
echo "Hello, Oracle DBA!"
}
هر دو کار میکنند، اما شکل دوم (name() { ... }) قابلحملتر است و در اکثر shellها پشتیبانی میشود. در اسکریپتهای حرفهای معمولاً همین شکل ترجیح داده میشود.
۳) صدا زدن Function
برای صدا زدن یک تابع، فقط نام آن را بنویسید — بدون پرانتز:
greet() {
echo "Hello, Oracle DBA!"
}
greet # صدا زدن تابع
نکته مهم: تابع باید قبل از صدا زدن، تعریف شده باشد. Bash اسکریپت را بهترتیب از بالا به پایین میخواند.
۴) پارامترها و آرگومانها
تابعهای Bash پارامترهای نامدار نمیگیرند (مثل PL/SQL). بلکه آرگومانها بهصورت positional parameters درون تابع در دسترس هستند:
$1,$2, ... : آرگومان اول، دوم، ...$@: تمام آرگومانها بهصورت لیست"$@": تمام آرگومانها بهصورت لیست safe (هر کدام quote شده)$#: تعداد آرگومانها$0: نام اسکریپت (نه نام تابع)
check_file() {
local file="$1"
if [[ -f "$file" ]]; then
echo "OK: $file exists."
else
echo "WARN: $file not found."
fi
}
check_file /etc/ssh/sshd_config
check_file /etc/oratab
در این مثال، $1 داخل تابع به اولین آرگومان پاسدادهشده اشاره دارد. این همان الگویی است که در اسکریپتهای Audit برای بررسی فایلهای config استفاده میشود.
۵) متغیرهای محلی: local
بهطور پیشفرض، تمام متغیرهای داخل تابع global هستند. یعنی اگر در تابع count=5 بنویسید، مقدار متغیر count در کل اسکریپت تغییر میکند. برای جلوگیری از این اثر جانبی، از کلیدواژهی local استفاده میکنیم:
my_func() {
local temp="temporary value"
echo "Inside: $temp"
}
my_func
echo "Outside: $temp" # خالی است، چون temp محلی بود
استفاده از local در اسکریپتهای DBA یک best practice حیاتی است؛ چون بدون آن، یک تابع میتواند بهطور ناخواسته متغیرهای سراسری اسکریپت را overwrite کند.
۶) بازگشت مقدار از Function
در Bash، تابعها مانند PL/SQL مقدار را با RETURN برنمیگردانند. بلکه دو روش رایج وجود دارد:
روش ۱: return برای exit status
return یک عدد برمیگرداند (مانند exit status). فقط اعداد ۰ تا ۲۵۵ مجاز هستند:
is_root() {
if [[ "$EUID" -eq 0 ]]; then
return 0 # true
else
return 1 # false
fi
}
if is_root; then
echo "Running as root."
else
echo "Not root."
fi
روش ۲: echo + command substitution برای مقدار متنی
اگر میخواهید یک رشته یا عدد را از تابع بگیرید، آن را echo کنید و با $(...) بگیرید:
get_oracle_home() {
local home=""
if [[ -f /etc/oratab ]]; then
home=$(grep -E '^\s*+ORCL:' /etc/oratab | cut -d: -f2)
fi
echo "$home"
}
OH=$(get_oracle_home)
echo "Oracle Home: $OH"
این الگو در اسکریپتهای DBA برای گرفتن مقادیری مثل ORACLE_HOME، نسخهی دیتابیس، یا وضعیت یک service بسیار کاربردی است.
۷) Function برای لاگکردن
یکی از پرکاربردترین الگوها در اسکریپتهای Audit و Remediation، داشتن یک تابع log است که پیامها را با برچسب PASS/FAIL/INFO چاپ میکند:
log_pass() {
local msg="$1"
echo -e " \e[32m[PASS]\e[0m $msg"
}
log_fail() {
local msg="$1"
echo -e " \e[31m[FAIL]\e[0m $msg"
}
log_info() {
local msg="$1"
echo -e " \e[34m[INFO]\e[0m $msg"
}
# استفاده
log_pass "SSH config is compliant."
log_fail "Crypto policy is not set to FUTURE."
log_info "Checking crypto policies..."
این الگو دقیقاً همان سبکی است که در اسکریپت audit_07_System_Crypto_Policy.sh با echo -e و رنگها دیده میشود. اگر آن اسکریپت از Functions استفاده میکرد، اینگونه بازنویسی میشد:
#!/bin/bash
set -euo pipefail
FAIL_COUNT=0
log_pass() { echo -e " \e[32m[PASS]\e[0m $1"; }
log_fail() { echo -e " \e[31m[FAIL]\e[0m $1"; FAIL_COUNT=$((FAIL_COUNT + 1)); }
check_crypto_policy() {
local current
current=$(update-crypto-policies --show 2>/dev/null)
if [[ "$current" == "FUTURE" || "$current" == "FIPS" ]]; then
log_pass "System Crypto Policy is $current."
else
log_fail "System Crypto Policy is '$current', expected FUTURE or FIPS."
fi
}
check_crypto_policy
همانطور که میبینید، استفاده از Functions باعث میشود منطق هر check مجزا، خوانا و قابلنگهداری باشد.
۸) Function برای بررسی Root
در اسکریپت remediate_07_System_Crypto_Policy.sh این الگو دیده میشود:
if [ "$EUID" -ne 0 ]; then echo "Please run as root"; exit 1; fi
با Functions میتوان این را به یک تابع قابلاستفادهمجدد تبدیل کرد:
require_root() {
if [[ "$EUID" -ne 0 ]]; then
echo "ERROR: This script must be run as root."
exit 1
fi
}
require_root # صدا زدن در ابتدای اسکریپت
حالا این تابع را میتوان در هر اسکریپت remediation استفاده کرد.
۹) Function با چند آرگومان
تابعها میتوانند چندین آرگومان بگیرند. در این مثال، یک تابع برای بررسی تطبیق یک مقدار با یک الگو:
check_value() {
local label="$1"
local actual="$2"
local expected="$3"
if [[ "$actual" == "$expected" ]]; then
log_pass "$label is '$actual'."
else
log_fail "$label is '$actual', expected '$expected'."
fi
}
check_value "Crypto Policy" "$CURRENT_POLICY" "FUTURE"
check_value "SELinux Mode" "$(getenforce)" "Enforcing"
با این الگو، تمام checkهای مشابه با یک خط کد انجام میشوند و منطق فقط در یکجا نگهداری میشود.
۱۰) متغیرهای ویژهی Bash در Function
چند متغیر ویژه که درون تابع مفید هستند:
$FUNCNAME: نام تابع جاری (مفید برای debug)؛$BASH_LINENO: شمارهی خطی که تابع از آن صدا زده شده؛$?: exit status آخرین command داخل تابع.
debug_func() {
echo "Called from function: $FUNCNAME"
echo "At line: ${BASH_LINENO[0]}"
}
debug_func
۱۱) خواندن از stdin در Function
تابعها میتوانند ورودی را از stdin بخوانند. این الگو برای پردازش خطبهخط خروجی یک command در یک تابع مفید است:
process_lines() {
local count=0
while IFS= read -r line; do
((count++))
done
echo "Total lines: $count"
}
df -h | process_lines
۱۲) الگوی Export و Reuse
اگر یک مجموعهی تابع مفید دارید که در چند اسکریپت استفاده میکنید، میتوانید آنها را در یک فایل جداگانه قرار دهید و source کنید:
# File: /opt/oracle/scripts/lib/dba_functions.sh
log_pass() { echo -e " \e[32m[PASS]\e[0m $1"; }
log_fail() { echo -e " \e[31m[FAIL]\e[0m $1"; }
require_root() { [[ "$EUID" -ne 0 ]] && { echo "Run as root"; exit 1; }; }
#!/bin/bash
source /opt/oracle/scripts/lib/dba_functions.sh
require_root
log_pass "Functions loaded successfully."
source (یا .) محتوای فایل را در shell جاری اجرا میکند، طوری که انگار آن کد مستقیماً در اسکریپت نوشته شده است. این دقیقاً مشابه @import یا INCLUDE در سایر زبانهاست.
۱۳) خطاهای رایج
۱. فراموش کردن local:
# خطرناک: i سراسری است و ممکن است با متغیر بیرون تداخل کند
count_lines() {
i=0
while IFS= read -r _; do ((i++)); done
echo "$i"
}
# امن
count_lines() {
local i=0
while IFS= read -r _; do ((i++)); done
echo "$i"
}
۲..quote نکردن "$@" در حلقه:
# نادرست: اگر آرگومان فاصله داشته باشد، میشکند
for arg in $@; do echo "$arg"; done
# درست
for arg in "$@"; do echo "$arg"; done
۳. صدا زدن تابع قبل از تعریف:
check_root # خطا: تابع هنوز تعریف نشده!
require_root() { ... }
همیشه توابع را در ابتدای اسکریپت تعریف کنید و منطق اصلی را در انتها قرار دهید.
۱۴) ساختار پیشنهادی یک اسکریپت DBA
#!/bin/bash
set -euo pipefail
# ── 1. Constants ──────────────────────────
ORACLE_BASE="/u01/app/oracle"
LOG_FILE="/var/log/oracle_audit.log"
# ── 2. Function Definitions ───────────────
log_pass() { ... }
log_fail() { ... }
require_root() { ... }
check_file() { ... }
# ── 3. Main Logic ─────────────────────────
require_root
check_file "/etc/ssh/sshd_config"
check_file "$ORACLE_BASE/network/admin/sqlnet.ora"
# ── 4. Summary ───────────────────────────
echo "Audit complete. Failures: $FAIL_COUNT"
این ساختار، اسکریپت را تمیز، خوانا و قابلنگهداری نگه میدارد.
۱۵) جمعبندی
Functions ابزار اصلی ساختارمند کردن اسکریپتهای Bash هستند. در این بخش یاد گرفتیم که:
- تابع با
name() { ... }تعریف میشود؛ - آرگومانها با
$1،$2،"$@"و$#در دسترساند؛ localاز تداخل متغیرها جلوگیری میکند؛returnبرای exit status وecho+$(...)برای مقدار متنی استفاده میشود؛- الگوهای
log_pass،log_failوrequire_rootدر اسکریپتهای Audit/Remediation بسیار پرکاربردند؛ sourceاجازه میدهد Functions را بین اسکریپتها بهاشتراک بگذاریم.
در مقاله بعدی به سراغ Redirection، Pipes و Streams میرویم؛ یعنی چطور stdin، stdout و stderr را کنترل کنیم، خروجیها را به فایل هدایت کنیم، و چند command را با pipe به هم متصل کنیم — مفاهیمی که در اسکریپتهای Hardening بهوفور دیده میشوند.
مقاله قبلی: Bash برای Oracle DBAها — بخش ۶: Command Substitution و Here Docs در Bash
مقاله بعدی: Bash برای Oracle DBAها — بخش ۸: Redirection، Pipes و Streams در Bash