۱۴. Bash و پردازش متن¶
اسکریپت عملیاتی که با خطا ساکت میشود، خطرناکتر از اسکریپت نبودن است. این فصل ابزار پردازش متن و الگوی نوشتن اسکریپت ایمن را میدهد.
راهنمای سریع¶
| ابزار | کاربرد |
|---|---|
grep -E/-w/-c/-v |
فیلتر خطوط |
cut -d: -f1 / awk '{print $N}' |
انتخاب ستون |
sort / uniq -c / sort -nr |
مرتبسازی و شمارش |
sed 's/a/b/g' |
جایگزینی (سیال) |
tr / column -t / paste / comm / diff |
تبدیل، جدول، مقایسه |
jq |
پردازش JSON |
xargs -0 / find -print0 |
اجرای فرمان روی فهرست امن |
tee / watch / timeout |
تقسیم خروجی، تکرار، محدودیت زمانی |
خروجی و pipe¶
command > output.txt
command >> output.txt
command 2> errors.log
command >all.log 2>&1
command1 | command2
command | tee -a run.log # هم نمایش، هم ذخیره
command 2>&1 | less
diff <(sort a.txt) <(sort b.txt) # process substitution
> فایل را بازنویسی میکند و >> به انتها میافزاید. برای خروجی حساس یا مهم، مسیر مقصد را بررسی کنید. (set -o noclobber از بازنویسی ناخواسته جلوگیری میکند.)
grep، cut، sort و uniq¶
grep -in 'failed' app.log
grep -Rni --include='*.conf' 'listen' /etc/nginx
grep -E 'error|warn' app.log
grep -v '^#' /etc/ssh/sshd_config | grep -v '^$' # بدون کامنت و خط خالی
cut -d: -f1 /etc/passwd
sort names.txt | uniq -c
sort -nr counts.txt | head
sort -k2 -n -u data.txt
uniq معمولاً ردیفهای تکراری مجاور را ادغام میکند؛ برای شمارش همه موارد، ابتدا sort کنید.
awk و sed¶
awk '{print $1}' access.log
awk '$9 == 500 {print $0}' access.log
awk -F: '$3 >= 1000 {print $1, $3}' /etc/passwd # جداکنندهٔ دلخواه
awk '{s += $10} END {printf "%.1f MB\n", s/1024/1024}' access.log # جمع یک ستون
sed -n '1,40p' file.txt
sed 's/old/new/g' file.txt
sed -i.bak 's/old/new/g' file.conf # درجا + نسخهٔ پشتیبان
sed بالا فایل را تغییر نمیدهد و نتیجه را به stdout میفرستد. برای تغییر درجا از sed -i فقط پس از تهیه نسخه پشتیبان (-i.bak) استفاده کنید و قبلش خروجی را بدون -i ببینید.
JSON با jq¶
curl -s https://api.example.com/status | jq .
jq -r '.items[] | "\(.name)\t\(.status)"' data.json
docker inspect CONTAINER | jq '.[0].State'
journalctl -u myapp -o json -n 100 | jq -r '.MESSAGE'
xargs و null separator¶
find . -type f -name '*.log' -print0 | xargs -0 -r ls -lh
find . -type f -name '*.log' -print0 | xargs -0 -r -P4 gzip # -P4: چهار کار موازی
استفاده از -print0 و -0 نام فایلهای دارای فاصله یا نویسههای خاص را ایمنتر مدیریت میکند. -r از اجرا با ورودی خالی جلوگیری میکند.
متغیرها، نقلقول و شرطها¶
name="backup"
printf 'Task: %s\n' "$name"
if [ -f /etc/hosts ]; then echo "hosts exists"; fi
[[ "$USER" == root ]] && echo "root" # [[ ]] در bash: امنتر
echo "${LOG_DIR:-/var/log}" # مقدار پیشفرض
echo "${TARGET:?TARGET باید تنظیم شود}" # اگر خالی بود، خطا و توقف
files=(a.txt b.txt "c d.txt") # آرایه
for f in "${files[@]}"; do echo "$f"; done
while IFS= read -r line; do echo "$line"; done < input.txt # خواندن امن خطبهخط
متغیرها را معمولاً داخل نقلقول دوتایی بگذارید: "$variable"؛ این کار از شکستن ناخواسته بر اساس فاصله و glob جلوگیری میکند. $(...) را بهجای backtick بنویسید.
قالب امن اسکریپت¶
#!/usr/bin/env bash
# نمونه: قالب اسکریپت عملیاتی ایمن
set -Eeuo pipefail
umask 077
LOG_TAG="example-task"
WORKDIR="$(mktemp -d)"
LOCK=/var/lock/example-task.lock
log() { logger -t "$LOG_TAG" -- "$*"; printf '[%s] %s\n' "$(date -Is)" "$*"; }
die() { log "ERROR: $*"; exit 1; }
cleanup() { rm -rf -- "${WORKDIR:?}"; }
trap cleanup EXIT
trap 'die "خطا در خط $LINENO (کد $?)"' ERR
usage() { echo "usage: $0 [-n] TARGET_DIR"; exit 2; }
DRY=0
while getopts ':n' opt; do case $opt in n) DRY=1;; *) usage;; esac; done
shift $((OPTIND - 1))
[ $# -eq 1 ] || usage
TARGET="$1"
exec 9>"$LOCK"; flock -n 9 || die "نمونهٔ دیگری در حال اجراست"
[ -d "$TARGET" ] || die "مسیر نامعتبر: $TARGET"
log "شروع (dry-run=$DRY)"
if [ "$DRY" -eq 1 ]; then
find "$TARGET" -type f -mtime +30 -print
else
find "$TARGET" -type f -mtime +30 -delete
fi
log "پایان"
نکات:
set -eرفتار توقف را در بعضی زمینهها تغییر میدهد (داخلif،&&،||)؛ آن را جایگزین مدیریت خطا و بررسی exit code ندانید.set -uمتغیر تعریفنشده را خطا میکند؛set -o pipefailخطای فرمانهای میانی pipeline را پنهان نمیکند.- اسکریپت را ابتدا با داده آزمایشی، گزینهٔ dry-run و کاربر محدود آزمایش کنید.
- قبل از کار مخرب، ورودی را اعتبارسنجی کنید (مسیر خالی،
/، mount نبودن).
ابزارهای کیفیت¶
bash -n script.sh # فقط بررسی نحو
bash -x script.sh # اجرا با ردیابی هر خط
shellcheck script.sh # تحلیل ایستا (نصب: apt install shellcheck) ← بیشتر دامها را میگیرد
دام pipefail با grep -q / head
cmd | grep -q x با pipefail ممکن است بهخاطر SIGPIPE کد خطا بدهد حتی وقتی تطبیق یافت شده. برای شرطها از if cmd | grep -q x; then دقت کنید یا خروجی را در متغیر بگیرید.
کد خروج¶
کد 0 معمولاً موفقیت و غیرصفر خطا/وضعیت خاص را نشان میدهد. برای pipeline، set -o pipefail کمک میکند خطای فرمانهای میانی پنهان نشود. کدهای رایج: 1 عمومی، 2 سوءاستفاده از دستور، 126 اجرا نشد (مجوز)، 127 پیدا نشد، 130 Ctrl+C، 137 کشته شد با KILL/OOM (128+9)، 143 با TERM (128+15).
ابزارهای کمکی روزمره¶
watch -n 2 'df -h /var' # تکرار هر ۲ ثانیه
timeout 10 curl -sS https://example.com # قطع بعد از ۱۰ ثانیه
column -t -s: /etc/passwd | head
date -d '2 days ago' +%F
printf '%s\n' "نمونه" | wc -m # شمارش نویسه (UTF-8)
خطاهای رایج¶
| نشانه | علت احتمالی | راهحل |
|---|---|---|
: command not found با ^M |
پایان خط ویندوز (CRLF) | sed -i 's/\r$//' script.sh ؛ dos2unix |
unbound variable |
set -u و متغیر تنظیمنشده |
${VAR:-} یا تعریف متغیر |
| اسکریپت با فاصله در نام فایل میشکند | نقلقول فراموش شده | "$var" ؛ -print0/xargs -0 |
rm -rf "$DIR/"* روی /* رفت |
DIR خالی بود |
"${DIR:?}" |
[: too many arguments |
متغیر بدون نقلقول با فاصله | [[ ]] یا "$var" |
| pipeline خطا را نمیگیرد | بدون pipefail |
set -o pipefail |
sed -i فایل را خراب کرد |
regex اشتباه، بدون بکاپ | -i.bak ؛ اول بدون -i |
خلاصهٔ اجرایی¶
set -Eeuo pipefail+trap+flock+ dry-run برای هر اسکریپت.- همه چیز را نقلقول کنید؛
${VAR:?}قبل از حذف. shellcheckهمیشه.sed -i.bak،find -printقبل از-delete.