رفتن به محتوا
LoopX

پردازش متن با sed و awk

توی این درس یاد می‌گیری دو ابزار پردازش متن: sed (stream editor) که متن را جایگزین یا حذف می‌کند (sed 's/old/new/g') و با sed -i فایل را «درجا» ویرایش می‌کند، و awk که متن را به ستون‌ها (فیلدها) می‌شکند و محاسبه می‌کند (awk '{print $1}'، awk -F:). شرط و جمع زدن در awk، بخش‌های BEGIN و END و شمارش با آرایه را می‌بینی. تمرین: از /etc/passwd نام کاربرها و shell شان را استخراج می‌کنی.

مسئله: ویرایش صدها فایل بدون باز کردن آن‌ها

Section titled “مسئله: ویرایش صدها فایل بدون باز کردن آن‌ها”

باید در ۲۰۰ فایل تنظیمات، پورت 80 را به 8080 تغییر بدهی. یا از خروجی یک دستور فقط ستون سوم و پنجم را بخواهی، یا مجموع یک ستون را حساب کنی. با ویرایشگر و ماشین‌حساب ساعت‌ها طول می‌کشد؛ با sed و awk چند ثانیه. این دو ابزار مثل «جستجو و جایگزینی» و «اکسل خط فرمان» هستند.

تشبیه: قیچی و ماشین‌حساب

Section titled “تشبیه: قیچی و ماشین‌حساب”

sed مثل «جستجو و جایگزینی» ویرایشگر است ولی روی جریان متن و بدون باز کردن: هر خط می‌آید، تغییر می‌کند و می‌رود. awk مثل یک جدول‌خوان است: هر خط را به ستون‌ها می‌شکند و می‌توانی روی ستون‌ها شرط بگذاری و جمع بزنی. اولی «ویرایش»، دومی «تحلیل و گزارش».

هر دو ابزار ورودی را خط به خط می‌خوانند. sed خط را (با دستورهای ویرایشی) تغییر می‌دهد؛ awk خط را به فیلد می‌شکند، شرط‌ها را می‌سنجد و عمل‌ها را اجرا می‌کند.

داده‌های تمرین: یک فایل تنظیمات، یک CSV فروش و یک لاگ ساده:

Terminal window
cat > app.conf <<'LXEOF'
# تنظیمات برنامه
name=web
port=80
debug=true
server=old-server.example.com
backup_server=old-server.example.com
LXEOF
cat > sales.csv <<'LXEOF'
product,qty,price
apple,10,2
banana,5,3
apple,4,2
cherry,20,1
banana,2,3
LXEOF
awk 'BEGIN{for(i=1;i<=20;i++) print "2026-10-03 12:" (i<10?"0":"") i ":00", (i%4==0?"ERROR":"INFO"), "task", i, "done in", i*10 "ms"}' > run.log
echo "ساخته شد"; ls
خروجی
ساخته شد
app.conf
run.log
sales.csv

مثال ۱: جایگزینی با sed، s/قدیم/جدید/

Section titled “مثال ۱: جایگزینی با sed، s/قدیم/جدید/”

دستور s (substitute): s/الگو/جایگزین/. به‌طور پیش‌فرض اولین مورد هر خط را عوض می‌کند و فایل اصلی را تغییر نمی‌دهد (فقط نتیجه را چاپ می‌کند):

Terminal window
echo "--- جایگزینی ساده:"
echo "hello world, hello sed" | sed 's/hello/bye/'
echo "--- با g (همه‌ی موارد هر خط):"
echo "hello world, hello sed" | sed 's/hello/bye/g'
echo "--- روی فایل:"
sed 's/80/8080/' app.conf | grep port
echo "--- فایل اصلی هنوز دست‌نخورده:"
grep port app.conf
خروجی
--- جایگزینی ساده:
bye world, hello sed
--- با g (همه‌ی موارد هر خط):
bye world, bye sed
--- روی فایل:
port=8080
--- فایل اصلی هنوز دست‌نخورده:
port=80

g یعنی global (همه‌ی تطبیق‌ها). و چون sed خروجی را چاپ می‌کند، فایل اصلی تغییر نکرد؛ برای تغییر واقعی باید -i بدهی (مثال ۳).

مثال ۲: جداکننده‌ی دیگر، &، و regex در sed

Section titled “مثال ۲: جداکننده‌ی دیگر، &، و regex در sed”

اگر الگو شامل / است، هر کاراکتر دیگری را به‌جای جداکننده بگذار (|، #، ,):

Terminal window
echo "/usr/local/bin" | sed 's/\/usr\/local/\/opt/'
echo "--- خواناتر با |:"
echo "/usr/local/bin" | sed 's|/usr/local|/opt|'
echo "--- & یعنی خود متن منطبق:"
echo "port 80 and 443" | sed 's/[0-9][0-9]*/[&]/g'
echo "--- بی‌حساس به حروف (I) و regex توسعه‌یافته (-E با گروه \\1):"
echo "Hello HELLO hello" | sed 's/hello/X/Ig'
echo "ali:1001" | sed -E 's/([a-z]+):([0-9]+)/\2 → \1/'
خروجی
/opt/bin
--- خواناتر با |:
/opt/bin
--- & یعنی خود متن منطبق:
port [80] and [443]
--- بی‌حساس به حروف (I) و regex توسعه‌یافته (-E با گروه \1):
X X X
1001 → ali

در بخش جایگزین، & کل متن منطبق است و \1 \2 گروه‌های داخل پرانتز (با -E). این برای جابه‌جا کردن بخش‌های یک متن عالی است.

⚡ بررسی سریع

sed 's/a/b/' چه چیزی را عوض می‌کند؟

مثال ۳: ویرایش درجا، sed -i

Section titled “مثال ۳: ویرایش درجا، sed -i”

با -i تغییر در خود فایل اعمال می‌شود. همیشه قبلش بدون -i امتحان کن، و بهتر است با -i.bak یک نسخه‌ی پشتیبان هم بگیری:

Terminal window
cp app.conf app-test.conf
echo "--- پیش‌نمایش (بدون -i):"
sed 's/^port=.*/port=9090/' app-test.conf | grep port
echo "--- اعمال با بکاپ (-i.bak):"
sed -i.bak 's/^port=.*/port=9090/' app-test.conf
grep port app-test.conf
echo "--- بکاپ اصلی:"
grep port app-test.conf.bak
ls app-test.conf*
خروجی
--- پیش‌نمایش (بدون -i):
port=9090
--- اعمال با بکاپ (-i.bak):
port=9090
--- بکاپ اصلی:
port=80
app-test.conf
app-test.conf.bak

^port=.* یعنی «خطی که با port= شروع می‌شود، با هر چیزی بعدش»؛ کل خط با port=9090 جایگزین شد. فایل app-test.conf.bak نسخه‌ی قبل از تغییر است. (روی macOS sed -i بدون پسوند کار نمی‌کند و -i '' لازم است؛ -i.bak در هر دو کار می‌کند.)

مثال ۴: آدرس‌دهی خط‌ها: حذف و چاپ انتخابی

Section titled “مثال ۴: آدرس‌دهی خط‌ها: حذف و چاپ انتخابی”

قبل از دستور می‌توانی کدام خط‌ها را مشخص کنی: شماره‌ی خط (3)، بازه (2,4)، آخرین خط ($)، یا یک regex (/الگو/):

Terminal window
echo "--- فقط خط‌های 2 تا 3 (-n + p):"
sed -n '2,3p' app.conf
echo "--- حذف خط‌های توضیح (که با # شروع می‌شوند):"
sed '/^#/d' app.conf
echo "--- حذف خط اول و آخر:"
sed '1d;$d' app.conf | head -3
echo "--- فقط خط‌های دارای server (مثل grep):"
sed -n '/server/p' app.conf
echo "--- جایگزینی فقط در خط‌های مشخص (خط‌هایی که server دارند):"
sed '/^backup/s/old-server/new-server/' app.conf | grep server
خروجی
--- فقط خط‌های 2 تا 3 (-n + p):
name=web
port=80
--- حذف خط‌های توضیح (که با # شروع می‌شوند):
name=web
port=80
debug=true
server=old-server.example.com
backup_server=old-server.example.com
--- حذف خط اول و آخر:
name=web
port=80
debug=true
--- فقط خط‌های دارای server (مثل grep):
server=old-server.example.com
backup_server=old-server.example.com
--- جایگزینی فقط در خط‌های مشخص (خط‌هایی که server دارند):
server=old-server.example.com
backup_server=new-server.example.com

-n چاپ خودکار را خاموش می‌کند و p یعنی «همین خط را چاپ کن» (ترکیب -n و p = «فقط خط‌های انتخاب‌شده»). d یعنی حذف. چند دستور را با ; یا -e پشت هم می‌نویسی. آدرس قبل از s هم می‌آید تا جایگزینی فقط روی آن خط‌ها انجام شود.

مثال ۵: درج و تغییر خط، i، a، c

Section titled “مثال ۵: درج و تغییر خط، i، a، c”
Terminal window
echo "--- افزودن بعد از خط 2 (a):"
sed '2a\# خط اضافه‌شده' app.conf | head -4
echo "--- درج قبل از خطی که port دارد (i):"
sed '/^port=/i\# پورت زیر را عوض نکن' app.conf | sed -n '3,4p'
echo "--- تعویض کل خط debug (c):"
sed 's/^debug=.*/debug=false/' app.conf | grep debug
echo "--- غیرفعال کردن یک خط با # (توضیح‌شدن):"
sed '/^debug=/s/^/#/' app.conf | grep debug
خروجی
--- افزودن بعد از خط 2 (a):
# تنظیمات برنامه
name=web
# خط اضافه‌شده
port=80
--- درج قبل از خطی که port دارد (i):
# پورت زیر را عوض نکن
port=80
--- تعویض کل خط debug (c):
debug=false
--- غیرفعال کردن یک خط با # (توضیح‌شدن):
#debug=true

a\ متن را بعد از خط، i\ قبل از آن و c\ به‌جای آن می‌گذارد. و s/^/#/ ابتدای خط یک # اضافه می‌کند (غیرفعال کردن یک تنظیم)، که رایج‌ترین ویرایش تنظیمات است.

awk هر خط را با فاصله به فیلد می‌شکند: $1 فیلد اول، $2 دوم، $NF آخرین فیلد، $0 کل خط، NF تعداد فیلدها، NR شماره‌ی خط:

Terminal window
echo "ali 25 tehran" | awk '{print $1}'
echo "ali 25 tehran" | awk '{print $3, $1}'
echo "ali 25 tehran" | awk '{print "فیلد‌ها:", NF, "آخرین:", $NF}'
echo "--- run.log: ستون‌های 3 و 4 از خط‌های 1 تا 3:"
head -3 run.log | awk '{print NR ": " $3, $4}'
خروجی
ali
tehran ali
فیلد‌ها: 3 آخرین: tehran
--- run.log: ستون‌های 3 و 4 از خط‌های 1 تا 3:
1: INFO task
2: INFO task
3: INFO task

ساختار کلی awk: awk 'شرط { عمل }'. اگر شرط نباشد برای همه‌ی خط‌ها؛ اگر عمل نباشد، خط چاپ می‌شود. برخلاف cut، awk چند فاصله‌ی پشت هم را یک جداکننده می‌بیند، که برای خروجی دستورهایی مثل ps و df عالی است:

Terminal window
df -h / | awk 'NR==2 {print "فضای آزاد:", $4, "از", $2, "(پر:", $5 ")"}'
ls -l /etc/hostname /etc/os-release | awk '{print $5, $9}'
خروجی
فضای آزاد: 408G از 453G (پر: 6%)
3 /etc/hostname
21 /etc/os-release

مثال ۷: جداکننده‌ی دلخواه، -F، و تمرین اصلی

Section titled “مثال ۷: جداکننده‌ی دلخواه، -F، و تمرین اصلی”

-F جداکننده‌ی فیلد را عوض می‌کند. /etc/passwd با : جدا شده است:

Terminal window
echo "--- نام کاربر و shell او (ستون‌های 1 و 7):"
awk -F: '{print $1, $7}' /etc/passwd | head -4
echo "--- فقط کاربرهایی که shell واقعی دارند:"
awk -F: '$7 ~ /bash$/ {print $1 " → " $7}' /etc/passwd
echo "--- جداکننده‌ی خروجی (OFS) و CSV:"
awk -F, 'NR>1 {print $1, $2}' OFS=" | " sales.csv | head -3
خروجی
--- نام کاربر و shell او (ستون‌های 1 و 7):
root /bin/bash
daemon /usr/sbin/nologin
bin /usr/sbin/nologin
sys /usr/sbin/nologin
--- فقط کاربرهایی که shell واقعی دارند:
root → /bin/bash
ubuntu → /bin/bash
ali → /bin/bash
--- جداکننده‌ی خروجی (OFS) و CSV:
apple | 10
banana | 5
apple | 4

این همان تمرین درس است: awk -F: '{print $1, $7}' /etc/passwd اسم کاربرها و shell‌شان را می‌دهد. ~ یعنی «با این regex جور است»؛ و NR>1 سطر اول (هدر) را رد می‌کند.

مثال ۸: شرط و جمع، BEGIN و END

Section titled “مثال ۸: شرط و جمع، BEGIN و END”

بخش BEGIN { } قبل از خواندن ورودی و END { } بعد از آن اجرا می‌شود. با این‌ها جمع و میانگین می‌گیری:

Terminal window
echo "--- جمع تعداد (ستون 2) بدون هدر:"
awk -F, 'NR>1 {s += $2} END {print "جمع qty:", s}' sales.csv
echo "--- مبلغ کل (qty × price):"
awk -F, 'NR>1 {total += $2 * $3} END {print "مجموع فروش:", total}' sales.csv
echo "--- فقط ردیف‌هایی که qty بیشتر از 4 است:"
awk -F, 'NR>1 && $2 > 4 {print $1, $2}' sales.csv
echo "--- میانگین و تعداد:"
awk -F, 'NR>1 {n++; s += $2} END {printf "تعداد=%d میانگین qty=%.2f\n", n, s/n}' sales.csv
echo "--- یک عنوان با BEGIN:"
awk -F, 'BEGIN {print "گزارش فروش"; print "----------"} NR>1 {print $1 ": " $2*$3}' sales.csv | head -4
خروجی
--- جمع تعداد (ستون 2) بدون هدر:
جمع qty: 41
--- مبلغ کل (qty × price):
مجموع فروش: 69
--- فقط ردیف‌هایی که qty بیشتر از 4 است:
apple 10
banana 5
cherry 20
--- میانگین و تعداد:
تعداد=5 میانگین qty=8.20
--- یک عنوان با BEGIN:
گزارش فروش
----------
apple: 20
banana: 15

متغیرهای awk نیاز به تعریف ندارند و مقدار اولیه‌شان صفر است. printf مثل زبان C قالب‌بندی می‌کند (%d عدد صحیح، %.2f دو رقم اعشار، %s رشته).

مثال ۹: شمارش با آرایه، فروش هر محصول

Section titled “مثال ۹: شمارش با آرایه، فروش هر محصول”

آرایه‌ها در awk انجمنی (associative) هستند: کلیدشان هر متنی می‌تواند باشد. این الگوی «گروه‌بندی و جمع» است (مثل GROUP BY در SQL):

Terminal window
echo "--- مجموع فروش هر محصول:"
awk -F, 'NR>1 {sum[$1] += $2 * $3} END {for (p in sum) print p, sum[p]}' sales.csv | sort
echo "--- تعداد دفعات تکرار هر نوع خط در run.log (INFO/ERROR):"
awk '{count[$3]++} END {for (k in count) print count[k], k}' run.log | sort -rn
echo "--- بیشترین مدت (ms) در خط‌های ERROR:"
awk '$3 == "ERROR" {gsub("ms", "", $NF); if ($NF + 0 > max) max = $NF + 0} END {print max "ms"}' run.log
خروجی
--- مجموع فروش هر محصول:
apple 28
banana 21
cherry 20
--- تعداد دفعات تکرار هر نوع خط در run.log (INFO/ERROR):
15 INFO
5 ERROR
--- بیشترین مدت (ms) در خط‌های ERROR:
200ms

count[$3]++ برای هر مقدار ستون ۳ (نوع پیام: INFO یا ERROR) یک شمارنده می‌سازد و یکی بالا می‌برد؛ for (k in count) روی کلیدها می‌چرخد (ترتیب نامشخص است، پس sort بعدش). gsub جایگزینی regex در خود awk است. این تحلیل «پرتکرارترین‌ها» را بدون sort | uniq -c هم می‌دهد (درس pipe).

مثال ۱۰: ترکیب sed و awk در یک pipeline

Section titled “مثال ۱۰: ترکیب sed و awk در یک pipeline”
Terminal window
echo "--- ۱) sed: تمیز کردن، awk: ستون‌ها:"
printf 'name: ali , age: 25\nname: sara , age: 31\n' | sed 's/[ ]*,[ ]*/,/; s/:[ ]*/:/g' | awk -F'[:,]' '{print $2, $4}'
echo "--- ۲) تغییر در همه‌ی فایل‌های یک پوشه (پیش‌نمایش با sed -n):"
mkdir -p confs && printf 'host=old\nport=80\n' > confs/a.conf && printf 'host=old\n' > confs/b.conf
sed -n 's/host=old/host=new/p' confs/*.conf
echo "--- ۳) اعمال با -i:"
sed -i 's/host=old/host=new/' confs/*.conf
grep -H host confs/*.conf
خروجی
--- ۱) sed: تمیز کردن، awk: ستون‌ها:
ali 25
sara 31
--- ۲) تغییر در همه‌ی فایل‌های یک پوشه (پیش‌نمایش با sed -n):
host=new
host=new
--- ۳) اعمال با -i:
confs/a.conf:host=new
confs/b.conf:host=new

الگوی رایج کار با فایل‌های تنظیمات: ۱) با -n ... p ببین چه چیزی تغییر می‌کند، ۲) بعد با -i اعمال کن (ترجیحاً با .bak). و اگر منطق ستونی/محاسباتی داری، awk؛ اگر جایگزینی ساده، sed.

پشت پرده: چرخه‌ی خط‌به‌خط

Section titled “پشت پرده: چرخه‌ی خط‌به‌خط”

هر دو ابزار الگوی ثابتی دارند: یک خط را بخوان ← عمل‌ها را اجرا کن ← خروجی بده ← خط بعدی. در sed این چرخه «pattern space» نام دارد: خط در یک بافر قرار می‌گیرد، دستورها (s، d، p) به‌ترتیب روی بافر اجرا می‌شوند و در پایان (مگر -n) بافر چاپ می‌شود. در awk برای هر خط، متغیرهای $0 و $1... ساخته می‌شوند و بلوک‌هایی که شرطشان درست است اجرا می‌شوند.

دو نکته‌ی مهم: ۱) هیچ‌کدام کل فایل را در حافظه نمی‌ریزند، پس برای فایل‌های چندگیگابایتی هم کار می‌کنند؛ ۲) sed -i در واقع یک فایل موقت می‌سازد، نتیجه را در آن می‌نویسد و بعد جایگزین فایل اصلی می‌کند (یک inode جدید؛ لینک‌های سخت و دسترسی‌ها ممکن است تحت تأثیر قرار بگیرند؛ --follow-symlinks برای لینک‌های نمادین). و برای تغییرات بزرگ همیشه بکاپ.

انتخاب ابزار: cut برای برش ساده‌ی ستون با جداکننده‌ی ثابت، sed برای جایگزینی و حذف/درج خط، awk برای ستون‌های متغیر، شرط، محاسبه و گزارش. وقتی منطق پیچیده شد، یک اسکریپت bash یا پایتون بنویس.

دستور sed معنی
s/a/b/ اولین a هر خط را به b
s/a/b/g همه‌ی a ها
s/a/b/I بی‌حساس به حروف
-n '2,4p' فقط خط‌های ۲ تا ۴
'/الگو/d' حذف خط‌های منطبق
'$d' / '1d' حذف آخرین / اولین خط
'2a\متن' / '2i\متن' افزودن بعد / قبل از خط ۲
-E regex توسعه‌یافته (\1 برای گروه)
-i / -i.bak ویرایش درجا / با بکاپ
-e 'cmd1' -e 'cmd2' چند دستور
در awk معنی
$1 $2 ... $NF فیلدها ($NF آخرین)
$0 کل خط
NR / NF شماره‌ی خط / تعداد فیلد
-F: جداکننده‌ی فیلد (:)
BEGIN {...} / END {...} قبل از شروع / بعد از پایان
$3 > 100 {print} شرط روی فیلد
/regex/ {print} شرط regex روی خط
$1 ~ /x/ فیلد با regex جور است
sum += $2 جمع
count[$1]++ آرایه‌ی شمارنده
printf "%d %.2f %s\n" چاپ قالب‌بندی‌شده
Terminal window
echo "a-a-a" | sed 's/a/X/'
echo "a-a-a" | sed 's/a/X/g'
خروجی
X-a-a
X-X-X

بدون g فقط اولین مورد هر خط عوض می‌شود. راه‌حل: g برای همه.

۲) sed -i بدون پیش‌نمایش و بکاپ

Section titled “۲) sed -i بدون پیش‌نمایش و بکاپ”
Terminal window
echo "version=1.0" > ver.txt
sed -i 's/1.0/2.0/' ver.txt
cat ver.txt
echo "--- ولی نقطه = هر کاراکتر! این هم عوض می‌شود:"
echo "version=1x0" > ver2.txt
sed -i 's/1.0/2.0/' ver2.txt
cat ver2.txt
خروجی
version=2.0
--- ولی نقطه = هر کاراکتر! این هم عوض می‌شود:
version=2.0

1.0 در regex با 1x0 هم جور است (نقطه = هر کاراکتر). راه‌حل: 1\.0، و قبل از -i پیش‌نمایش و بکاپ (-i.bak).

۳) quote اشتباه با متغیر shell

Section titled “۳) quote اشتباه با متغیر shell”
Terminal window
name="ali"
echo "hello NAME" | sed 's/NAME/$name/'
echo "--- با دابل‌quote (shell متغیر را جایگزین می‌کند):"
echo "hello NAME" | sed "s/NAME/$name/"
خروجی
hello $name
--- با دابل‌quote (shell متغیر را جایگزین می‌کند):
hello ali

داخل '...' متغیر باز نمی‌شود. راه‌حل: دابل‌quote برای متغیر (و مواظب / یا & در مقدار باش).

۴) استفاده از awk با جداکننده‌ی اشتباه

Section titled “۴) استفاده از awk با جداکننده‌ی اشتباه”
Terminal window
echo "a,b,c" | awk '{print $2}'
echo "--- با -F,:"
echo "a,b,c" | awk -F, '{print $2}'
خروجی
--- با -F,:
b

بدون -F, کل خط یک فیلد است و $2 خالی. راه‌حل: -F, (یا -F'[,;]' برای چند جداکننده).

۵) فراموش کردن حذف سطر هدر در محاسبه

Section titled “۵) فراموش کردن حذف سطر هدر در محاسبه”
Terminal window
echo "--- بدون NR>1 (سطر هدر هم شمرده می‌شود):"
awk -F, '{n++; s += $2} END {printf "تعداد=%d جمع=%d میانگین=%.2f\n", n, s, s/n}' sales.csv
echo "--- با NR>1:"
awk -F, 'NR>1 {n++; s += $2} END {printf "تعداد=%d جمع=%d میانگین=%.2f\n", n, s, s/n}' sales.csv
خروجی
--- بدون NR>1 (سطر هدر هم شمرده می‌شود):
تعداد=6 جمع=41 میانگین=6.83
--- با NR>1:
تعداد=5 جمع=41 میانگین=8.20

هدر (qty) به‌عنوان عدد صفر جمع می‌شود و یک ردیف اضافی در شمارش می‌آید؛ جمع یکی است ولی تعداد و میانگین غلط می‌شود (۶ به‌جای ۵ ردیف و میانگین ۶٫۸۳ به‌جای ۸٫۲۰). راه‌حل: NR>1.

✎ تمرینآسان

در app.conf همه‌ی old-server ها را به new-server تغییر بده (فقط چاپ کن، فایل را عوض نکن) و بگو چند خط عوض شد.

دیدن جواب
Terminal window
cat > app.conf <<'LXEOF'
server=old-server.example.com
backup_server=old-server.example.com
port=80
LXEOF
sed 's/old-server/new-server/g' app.conf
echo "تعداد خط‌های عوض‌شده: $(sed -n 's/old-server/new-server/p' app.conf | wc -l)"
خروجی
server=new-server.example.com
backup_server=new-server.example.com
port=80
تعداد خط‌های عوض‌شده: 2
✎ تمرینمتوسط

تمرین اصلی: از /etc/passwd اسم کاربرها و shell شان را استخراج کن. با awk -F: ستون‌های ۱ و ۷ را چاپ کن، بعد فقط کاربرهایی که shell شان nologin نیست را نشان بده و تعدادشان را بنویس.

دیدن جواب
Terminal window
echo "--- ۵ خط اول:"
awk -F: '{print $1 "\t" $7}' /etc/passwd | head -5
echo "--- کاربرهای با shell غیر nologin:"
awk -F: '$7 !~ /nologin$/ {print $1, $7}' /etc/passwd
echo "تعدادشان: $(awk -F: '$7 !~ /nologin$/' /etc/passwd | wc -l)"
خروجی
--- ۵ خط اول:
root /bin/bash
daemon /usr/sbin/nologin
bin /usr/sbin/nologin
sys /usr/sbin/nologin
sync /bin/sync
--- کاربرهای با shell غیر nologin:
root /bin/bash
sync /bin/sync
ubuntu /bin/bash
ali /bin/bash
تعدادشان: 4

!~ یعنی «با این regex جور نیست».

✎ تمرینسخت

یک گزارش بساز: در sales.csv برای هر محصول مجموع تعداد و مجموع مبلغ (qty×price) را چاپ کن، مرتب بر اساس مبلغ نزولی، و در انتها یک خط «جمع کل». (راهنما: آرایه‌ی awk + sort -k3 -nr، یا همه‌ی کار در awk.)

دیدن جواب
Terminal window
cat > sales.csv <<'LXEOF'
product,qty,price
apple,10,2
banana,5,3
apple,4,2
cherry,20,1
banana,2,3
LXEOF
awk -F, 'NR>1 {q[$1] += $2; m[$1] += $2 * $3; all += $2 * $3}
END {for (p in q) printf "%s %d %d\n", p, q[p], m[p]; printf "جمع‌کل - - %d\n", all}' sales.csv | sort -k3 -nr
خروجی
apple 14 28
banana 7 21
cherry 20 20
جمع‌کل - - 69
؟ آزمونک
  1. sed 's/cat/dog/g' چه می‌کند؟

  2. برای ویرایش درجا با بکاپ؟

  3. awk -F: '{print $1}' /etc/passwd چه چاپ می‌کند؟

  4. $NF در awk چیست؟

  5. بخش END { } در awk کی اجرا می‌شود؟

  6. sed -n "2,4p" چه می‌کند؟

  • sed: s/قدیم/جدید/ (اولین مورد) و s/.../.../g (همه)؛ آدرس خط (2,4، /regex/، $) با d (حذف)، p (چاپ با -n)، a\ i\ c\؛ -i ویرایش درجا (با -i.bak بکاپ).
  • awk: فیلدها $1 $2 ... $NF، -F: جداکننده، شرط ($3 > 100، /regex/، NR>1)، BEGIN/END، جمع (s += $2) و آرایه‌ی انجمنی (count[$1]++).
  • اول بدون -i امتحان کن؛ در regex نقطه = هر کاراکتر (1\.0)؛ متغیر shell نیاز به دابل‌quote دارد.
  • cut ساده، sed ویرایش، awk تحلیل ستونی و گزارش؛ برای منطق پیچیده، اسکریپت.
  • هر دو خط‌به‌خط کار می‌کنند و برای فایل‌های بزرگ مناسب‌اند.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
sed 's/old/new/g' fileجایگزینی همه‌ی موارد (چاپ نتیجه)
sed -i.bak 's/old/new/g' fileویرایش درجا با بکاپ
sed -n '2,4p' fileفقط خط‌های ۲ تا ۴
sed '/^#/d' fileحذف خط‌های توضیح
sed 's|/a|/b|' fileجداکننده‌ی دیگر برای مسیرها
awk '{print $1, $NF}' fileستون اول و آخر
awk -F: '{print $1, $7}' /etc/passwdستون‌ها با جداکننده‌ی :
awk '$3 > 100 {print $1}' fileشرط روی ستون
awk '{s+=$1} END{print s}' fileجمع یک ستون
awk '{c[$1]++} END{for(k in c) print c[k], k}' fileشمارش هر مقدار
awk 'NR>1' fileرد کردن سطر هدر