این آزمون چهار درس بخش «ساختارمند نوشتن» را میسنجد. جواب هر سؤال همان لحظه با توضیح نشان داده میشود و نتیجهات در همین مرورگر ذخیره میشود (پیشرفت من).
تابعی با echo یک عدد چاپ میکند و با return ۰ تمام میشود. نتیجهی تابع را چطور در متغیر میگیری؟
$(...) خروجی چاپشده را میگیرد؛ $? فقط کد خروج است.
متغیری که داخل تابع (بدون local) مقدار میگیرد، بعد از پایان تابع چه میشود؟
هر متغیر داخل تابع را local کن (حتی شمارندهی حلقه).
چرا local out=$(false) کد خروج false را پنهان میکند؟
ShellCheck این را SC2155 مینامد.
داخل تابعی exit 1 نوشتهای. اثرش چیست؟
برای برگشت از تابع return؛ در die() عمداً exit میگذاریم.
تابع inc شمارندهی سراسری cnt را زیاد میکند. بعد از v=$(inc)، cnt چه میشود؟
برای تغییر متغیر بیرونی، تابع را مستقیم صدا بزن.
تابع hi را در اسکریپت تعریف کردهای و bash -c hi (فرزند) آن را نمیشناسد. راهحل؟
تابعها بهصورت پیشفرض به فرزندها نمیرسند.
arr=(a b c) و echo $arr چه چاپ میکند؟
$arr یعنی ${arr[0]}؛ برای همهی عضوها "${arr[@]}".
files=("my report.txt" "a.txt") و دستور cp "${files[@]}" backup/ چند آرگومان مبدأ میدهد؟
کوتیشن دور "${arr[@]}" عضوها را سالم نگه میدارد.
روی آرایهی سهعضوی unset 'arr[1]' میزنی. ${!arr[@]} چه میدهد؟
arr=("${arr[@]}") دوباره اندیسها را پیوسته میکند.
اگر declare -A را فراموش کنی و بنویسی m[apple]=1; m[banana]=2 چه میشود؟
بیصدا غلط است؛ همیشه declare -A.
کدام روش برای ساختن آرایه از خطهای یک فایل (حتی خطهای دارای فاصله) درست است؟
روش اول در فاصلهها میشکند.
p=/var/log/app.log.gz و echo ${p##*/} چه چاپ میکند؟
## بلندترین پیشوند تا آخرین / را برمیدارد (معادل basename).
برای سه کاراکتر آخر s از ${s:-3} استفاده کردهای و کل رشته برمیگردد. چرا و راهحل؟
فاصله قبل از عدد منفی برش را از مقدار پیشفرض جدا میکند.
تفاوت ${v:-d} و ${v-d}؟
${v:=d} است که مقدار را در v هم میریزد.
for f in *.txt; do mv $f ${f%.txt}.md; done با فایل «my notes.txt» چه میشود؟
همیشه کوتیشن دور گسترشها.
چرا ${s/[0-9]+/N} «یک یا چند رقم» را عوض نمیکند؟
با shopt -s extglob میتوان +([0-9]) نوشت.
کدام یک $uri را در متن here-doc عیناً نگه میدارد؟
جداکنندهی کوتیشندار هیچ گسترشی انجام نمیدهد؛ یا بدون کوتیشن و با \$uri.
چرا sudo cat > /etc/app.conf <<EOF با Permission denied شکست میخورد؟
sudo tee /etc/app.conf > /dev/null <<EOF.
<<-EOF دقیقاً چه چیزی را از ابتدای خطها برمیدارد؟
اگر ویرایشگر tab را به فاصله تبدیل کند، <<- کار نمیکند.
چرا متغیر شل را به awk با -v میدهیم و نه داخل متن برنامهی awk؟
awk "/$path/" با path=/admin خطای نحوی میدهد.
برای جستوجوی متن دقیق example.ir (نقطه واقعی) در فایل با متغیر name، کدام درستتر است؟
بدون -F نقطه «هر کاراکتر» است و exampleXir هم جور میشود.
کدام دستور ویرایش درجای فایل را هم روی GNU sed (لینوکس) و هم BSD sed (macOS) یکسان انجام میدهد؟
روی macOS گزینهی -i یک پسوند میخواهد؛ -i.bak در هر دو یکسان است.
با همهی آنچه در این بخش آموختی، اسکریپت sites.sh <فایل-دامنهها> <پوشهی-خروجی> را بنویس که از فهرست دامنهها، فایل تنظیمات nginx بسازد:
۱. تابعها (با local برای هر متغیر داخلشان): log سطح پیام (روی صفحه و با tee -a در build.log)، die پیام (ثبت خطا روی stderr و exit 1)، valid_domain (فقط کد خروج؛ با regex)، trim (فقط با گسترش پارامتر).
۲. فایل دامنهها را خطبهخط بخوان (while IFS= read -r)؛ هر خط را trim و حروف کوچک کن؛ کامنتها (#) و خطهای خالی را رد کن. دامنههای معتبر را در یک آرایه و نامعتبرها را در آرایهی دیگر بریز (برای نامعتبر، یک WARN ثبت کن).
۳. برای هر دامنهی معتبر با here-doc فایل پوشه/دامنه.conf را بساز (server_name با و بدون www.، root، و try_files $uri $uri/ =404 سالم)؛ روی فایل موجود رونویسی نکن.
۴. با یک آرایهی انجمنی تعداد دامنههای معتبر را به تفکیک پسوند (ir، com، …) بشمار و خلاصه (تعداد معتبر و نامعتبر، شمارش پسوندها مرتب) را چاپ کن.
۵. کد خروج: ۰ همهچیز درست؛ ۱ استفادهی غلط یا نبودن فایل؛ ۲ اگر دامنهی نامعتبر بود (ولی دامنههای معتبر بسازد).
۶. ثابت کن فایلهای ساختهشده را خود nginx میپذیرد (nginx -t با یک پوشش حداقلی) و در پایان همهچیز را پاک کن.
راهنمایی
ساختار: چهار تابع بالای فایل؛ بررسی آرگومانها با [[ $# -eq 2 ]] || die ...؛ حلقهی while IFS= read -r line; do line="$(trim "${line,,}")"; [[ -z $line || $line == \#* ]] && continue; ...; done < "$domains_file". برای پسوند: ${domain##*.} و ((per_tld[$tld]++)) بعد از declare -A per_tld. برای here-doc بدون کوتیشن، \$uri را escape کن. برای مرتبکردن کلیدها: printf '%s\n' "${!per_tld[@]}" | sort. برای nginx -t یک فایل پوششی با include پوشه/*.conf و مسیرهای موقت داخل پوشهی خودت بساز و -c و -p بده.
راهحل
mkdir -p ~/bashlab/quiz2 && cd ~/bashlab/quiz2cat > sites.sh <<'EOF'#!/usr/bin/env bash# sites.sh: build nginx server blocks from a list of domains# Usage: ./sites.sh <domains-file> <output-dir># Exit codes: 0 ok, 1 usage/missing file, 2 some domains were invalid
LOG_FILE="${LOG_FILE:-build.log}"
log() { local level="$1" shift echo "$(date '+%F %T') [$level] $*" | tee -a "$LOG_FILE"}
die() { log ERROR "$*" >&2 exit 1}
valid_domain() { [[ $1 =~ ^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$ ]]}
trim() { local s="$1" s="${s#"${s%%[![:space:]]*}"}" s="${s%"${s##*[![:space:]]}"}" printf '%s' "$s"}
[[ $# -eq 2 ]] || die "usage: $0 <domains-file> <output-dir>"domains_file="$1"out_dir="$2"[[ -f $domains_file ]] || die "'$domains_file' not found"mkdir -p "$out_dir"
valid=()invalid=()declare -A per_tld
while IFS= read -r line; do line="$(trim "${line,,}")" [[ -z $line || $line == \#* ]] && continue if valid_domain "$line"; then valid+=("$line") ((per_tld[${line##*.}]++)) else invalid+=("$line") log WARN "skipping invalid domain: '$line'" fidone < "$domains_file"
for d in "${valid[@]}"; do conf="$out_dir/$d.conf" if [[ -e $conf ]]; then log WARN "$conf already exists, skipped" continue fi cat > "$conf" <<NGINXserver { listen 80; server_name $d www.$d; root /var/www/$d; location / { try_files \$uri \$uri/ =404; }}NGINX log INFO "created $conf"done
echo "--- Summary"echo "valid: ${#valid[@]}"echo "invalid: ${#invalid[@]}"for tld in $(printf '%s\n' "${!per_tld[@]}" | sort); do echo " .$tld ${per_tld[$tld]}"done
if (( ${#invalid[@]} > 0 )); then exit 2fiEOFchmod +x sites.shcat > domains.txt <<'EOF'# customersShop.Example.IR blog.example.irexample.com
bad;domain-leading.commail.example.orgEOFecho "== اجرای اول"./sites.sh domains.txt out; echo "کد خروج: $?"echo "== فایلهای ساختهشده"ls outecho "== اجرای دوم (رونویسی نکند)"./sites.sh domains.txt out 2>&1 | grep -c "already exists"echo "== خطاهای استفاده"./sites.sh; echo "کد خروج: $?"./sites.sh nofile.txt out; echo "کد خروج: $?"echo "== یکی از فایلها"cat out/shop.example.ir.confecho "== nginx -t روی همهی فایلها"mkdir -p tmp prefixcat > nginx-test.conf <<EOFpid $PWD/nginx.pid;error_log $PWD/error.log;events {}http { access_log off; client_body_temp_path $PWD/tmp/body; proxy_temp_path $PWD/tmp/proxy; fastcgi_temp_path $PWD/tmp/fastcgi; uwsgi_temp_path $PWD/tmp/uwsgi; scgi_temp_path $PWD/tmp/scgi; include $PWD/out/*.conf;}EOFnginx -t -c "$PWD/nginx-test.conf" -p "$PWD/prefix"; echo "کد خروج: $?"echo "== log"grep -c INFO build.loggrep WARN build.log | cut -c21-80 | head -n 2echo "== پاکسازی"cd ~ && rm -rf ~/bashlab== اجرای اول2026-10-04 10:50:40 [WARN] skipping invalid domain: 'bad;domain'2026-10-04 10:50:40 [WARN] skipping invalid domain: '-leading.com'2026-10-04 10:50:40 [INFO] created out/shop.example.ir.conf2026-10-04 10:50:40 [INFO] created out/blog.example.ir.conf2026-10-04 10:50:40 [INFO] created out/example.com.conf2026-10-04 10:50:40 [INFO] created out/mail.example.org.conf--- Summaryvalid: 4invalid: 2 .com 1 .ir 2 .org 1کد خروج: 2== فایلهای ساختهشدهblog.example.ir.confexample.com.confmail.example.org.confshop.example.ir.conf== اجرای دوم (رونویسی نکند)4== خطاهای استفاده2026-10-04 10:50:41 [ERROR] usage: ./sites.sh <domains-file> <output-dir>کد خروج: 12026-10-04 10:50:41 [ERROR] 'nofile.txt' not foundکد خروج: 1== یکی از فایلهاserver { listen 80; server_name shop.example.ir www.shop.example.ir; root /var/www/shop.example.ir; location / { try_files $uri $uri/ =404; }}== nginx -t روی همهی فایلهاnginx: the configuration file /home/ali/bashlab/quiz2/nginx-test.conf syntax is oknginx: configuration file /home/ali/bashlab/quiz2/nginx-test.conf test is successfulکد خروج: 0== log4[WARN] skipping invalid domain: 'bad;domain'[WARN] skipping invalid domain: '-leading.com'== پاکسازی