grep, sed และ awk: ตัวอย่างการประมวลผลข้อความในเทอร์มินัล Linux
grep ค้นหาบรรทัดตามรูปแบบ (grep -rn error /var/log), sed แก้ไขข้อความในสตรีมหรือในไฟล์ (sed -i 's/old/new/g' file) ส่วน awk ทำงานกับคอลัมน์และคำนวณค่า (awk '{print $1}' access.log) เมื่อนำมาต่อกันเป็นท่อ ทั้งสามทำหน้าที่แทนสคริปต์ยาว ๆ ได้: grep กรองบรรทัด sed แก้ข้อความ awk ตัดฟิลด์และสรุปผล
grep, sed และ awk คือยูทิลิตี้คลาสสิกสามตัวของ Linux สำหรับประมวลผลข้อความ grep ค้นหาบรรทัดที่ตรงกับรูปแบบ sed แทนที่และลบข้อความในสตรีมโดยไม่ต้องเปิดตัวแก้ไข ส่วน awk แยกบรรทัดออกเป็นคอลัมน์และคำนวณค่าได้ ด้านล่างนี้คือตัวอย่าง grep sed awk ที่ใช้จริงเมื่อวิเคราะห์ล็อก คอนฟิก และผลลัพธ์ของคำสั่ง พร้อมข้อผิดพลาดที่พบบ่อย
ควรใช้เครื่องมือไหนเมื่อไร
ทั้งสามตัวอ่านข้อความทีละบรรทัดและส่งผลลัพธ์ไปยัง stdout จึงต่อกันด้วย | ได้ง่าย ความต่างอยู่ที่งานที่แต่ละตัวทำได้ดีที่สุด
| งาน | เครื่องมือ | ตัวอย่าง |
|---|---|---|
| ค้นหาบรรทัดที่มีข้อความ | grep | grep ERROR app.log |
| แทนที่ข้อความ | sed | sed 's/http:/https:/g' file |
| ลบหรือแสดงบรรทัดตามหมายเลข | sed | sed -n '10,20p' file |
| ดึงคอลัมน์ออกมา | awk | awk '{print $1}' access.log |
| รวมค่า หาค่าเฉลี่ย หรือจัดกลุ่ม | awk | awk '{s+=$10} END {print s}' |
กฎง่าย ๆ คือ ถ้าต้องการแค่กรอง ใช้ grep ถ้าต้องการเปลี่ยนข้อความ ใช้ sed ถ้าข้อความมีคอลัมน์หรือต้องคำนวณ ใช้ awk
grep: ค้นหาบรรทัดตามรูปแบบ
ตัวอย่างพื้นฐานของ grep
# บรรทัดที่มีคำว่า error ในไฟล์
grep error /var/log/syslog
# ไม่สนใจตัวพิมพ์เล็กใหญ่: Error, ERROR, error
grep -i error app.log
# แสดงหมายเลขบรรทัด
grep -n "Connection refused" app.log
# ค้นซ้ำทุกไฟล์ในไดเรกทอรี พร้อมชื่อไฟล์
grep -rn "DB_HOST" /etc/myapp/
# แสดงเฉพาะชื่อไฟล์ที่พบข้อความ
grep -rl "TODO" src/
# กลับเงื่อนไข: ทุกบรรทัดยกเว้นคอมเมนต์และบรรทัดว่าง
grep -v -e '^#' -e '^$' /etc/ssh/sshd_config
# นับจำนวนบรรทัดที่ตรง
grep -c " 500 " access.log
แฟล็ก -w ค้นหาคำทั้งคำ เช่น grep -w log จะไม่พบ login แฟล็ก -F ปิดการใช้ regex และค้นหาข้อความตามตัวอักษร เหมาะเมื่อในรูปแบบมีจุด วงเล็บ หรือเครื่องหมายดอกจัน
บริบทรอบบรรทัดที่พบ
ข้อผิดพลาดในล็อกแทบไม่เคยเข้าใจได้จากบรรทัดเดียว คุณมักต้องเห็นสแต็กหรือคำขอที่ทำให้เกิดข้อผิดพลาดนั้น แฟล็กบริบทช่วยได้:
# แสดง 3 บรรทัดหลังข้อความที่พบ (After)
grep -A 3 "Traceback" app.log
# แสดง 2 บรรทัดก่อนหน้า (Before)
grep -B 2 "OOM" /var/log/kern.log
# แสดง 5 บรรทัดทั้งสองด้าน (Context)
grep -C 5 "segfault" /var/log/syslog
grep คั่นกลุ่มผลลัพธ์ด้วยบรรทัด -- ทำให้อ่านผลลัพธ์ได้ง่าย
grep และนิพจน์ทั่วไป (regex)
โดยปกติ grep เข้าใจ regex แบบพื้นฐาน (BRE) เมื่อใช้แฟล็ก -E จะเปิดใช้ regex แบบขยาย (ERE) ซึ่ง +, ?, | และวงเล็บทำงานได้โดยไม่ต้องใส่แบ็กสแลช
| โครงสร้าง | ความหมาย | ตัวอย่าง |
|---|---|---|
^ | จุดเริ่มต้นของบรรทัด | grep '^root' /etc/passwd |
$ | จุดสิ้นสุดของบรรทัด | grep 'bash$' /etc/passwd |
. | อักขระตัวใดก็ได้ | grep 'h.t' |
* | ศูนย์ครั้งขึ้นไป | grep 'ab*c' |
+ (ERE) | หนึ่งครั้งขึ้นไป | grep -E '[0-9]+' |
| (ERE) | หรือ | grep -E 'error|warn' |
[...] | อักขระหนึ่งตัวจากชุดที่กำหนด | grep '[Ee]rror' |
# บรรทัดที่มี error หรือ warning
grep -E "ERROR|WARN" app.log
# เฉพาะ IP ในล็อก หนึ่งต่อบรรทัด (-o พิมพ์เฉพาะส่วนที่ตรง)
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log
grep -P: ดึงค่าระหว่างส่วนที่รู้จัก
GNU grep มีแฟล็ก -P ซึ่งใช้ regex แบบ Perl มี \K ("ทิ้งทุกอย่างที่ตรงก่อนตำแหน่งนี้") และ lookahead ช่วยให้ดึงเฉพาะค่าที่ต้องการได้:
$ echo 'user_id=4821 status=ok' | grep -oP 'user_id=\K[0-9]+'
4821
$ grep -oP '"version":\s*"\K[^"]+' package.json
2.4.1
แฟล็ก -P ไม่มีในทุกบิลด์ของ grep (เช่น BSD grep บน macOS ไม่มี) ดังนั้นในสคริปต์ที่ต้องรันได้ทุกที่ ควรใช้ sed หรือ awk
grep -q ในเงื่อนไขของสคริปต์
grep -q ไม่พิมพ์อะไรเลย แต่คืนค่าออกเท่านั้น: 0 หมายถึงพบ 1 หมายถึงไม่พบ จึงเขียนการตรวจสอบใน if ได้โดยตรง:
if grep -q "^PermitRootLogin no" /etc/ssh/sshd_config; then
echo "ปิดการล็อกอินของ root แล้ว"
fi
วิธีนี้สั้นกว่าการเก็บผลลัพธ์ไว้ในตัวแปรแล้วตรวจว่าว่างหรือไม่ และ grep จะหยุดทันทีที่พบการตรงครั้งแรก
sed: แทนที่และลบข้อความ
การแทนที่: คำสั่ง s
คำสั่งหลักของ sed คือ s/รูปแบบ/แทนที่ด้วย/แฟล็ก หากไม่ใส่แฟล็ก g จะแทนที่เฉพาะการตรงครั้งแรกในแต่ละบรรทัด
# แสดงข้อความหลังแทนที่ (ไฟล์ไม่เปลี่ยน)
sed 's/foo/bar/' file.txt
# แทนที่ทุกจุดในบรรทัด
sed 's/foo/bar/g' file.txt
# ไม่สนใจตัวพิมพ์เล็กใหญ่ (GNU sed)
sed 's/error/ERROR/gI' app.log
# ใช้ตัวคั่นอื่น หากในข้อความมีเครื่องหมายสแลช
sed 's|/var/www|/srv/www|g' nginx.conf
sed -i: แทนที่ในไฟล์โดยตรง
แฟล็ก -i เขียนผลลัพธ์กลับลงไฟล์ เพื่อไม่ให้ต้นฉบับหาย ให้ระบุส่วนต่อท้ายสำหรับสำรองไฟล์:
# สร้าง config.ini.bak แล้วแก้ไข config.ini
sed -i.bak 's/^debug=true/debug=false/' config.ini
# แทนที่ในทุกไฟล์ .conf ในไดเรกทอรี
find /etc/myapp -name "*.conf" -exec sed -i 's/old-host/new-host/g' {} +
ก่อนแก้ไขจำนวนมาก ให้รันคำสั่งโดยไม่ใส่ -i ก่อนแล้วดูผลลัพธ์ บน macOS (BSD sed) แฟล็ก -i ต้องมีอาร์กิวเมนต์: sed -i '' 's/a/b/' file
ทำงานกับบรรทัดตามหมายเลขและรูปแบบ
# แสดงบรรทัดที่ 10–20 (-n ปิดการพิมพ์อัตโนมัติ, p คือพิมพ์)
sed -n '10,20p' file.txt
# ลบบรรทัดที่ 5
sed '5d' file.txt
# ลบบรรทัดสุดท้าย
sed '$d' file.txt
# ลบบรรทัดว่างและคอมเมนต์
sed -e '/^$/d' -e '/^#/d' nginx.conf
# แทนที่เฉพาะในบรรทัดที่มีคำว่า "listen"
sed '/listen/s/80/8080/' nginx.conf
# เพิ่มบรรทัดหลังข้อความที่ตรง (GNU sed)
sed '/^\[mysqld\]/a max_connections = 500' my.cnf
การอ้างอิงกลับและ &
ส่วนของรูปแบบที่อยู่ในวงเล็บสามารถนำมาใช้ในการแทนที่ได้ด้วย \1, \2 ส่วนสัญลักษณ์ & หมายถึงข้อความที่ตรงทั้งหมด
# สลับชื่อกับนามสกุล (ERE ผ่าน -E)
$ echo "Иванов Пётр" | sed -E 's/(\S+) (\S+)/\2 \1/'
Пётр Иванов
# ครอบตัวเลขทุกตัวด้วยวงเล็บเหลี่ยม
$ echo "port 8080, timeout 30" | sed -E 's/[0-9]+/[&]/g'
port [8080], timeout [30]
# เปลี่ยนวันที่ 2026-10-14 เป็น 14.10.2026
$ echo 2026-10-14 | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/'
14.10.2026
awk: คอลัมน์ เงื่อนไข และการคำนวณ
แสดงคอลัมน์ที่ต้องการ
awk แบ่งแต่ละบรรทัดเป็นฟิลด์ตามช่องว่างและแท็บ: $1 คือฟิลด์แรก $2 คือฟิลด์ที่สอง $NF คือฟิลด์สุดท้าย และ $0 คือทั้งบรรทัด
# IP จากล็อก nginx (คอลัมน์แรก)
awk '{print $1}' /var/log/nginx/access.log
# ชื่อผู้ใช้และ shell จาก /etc/passwd (ตัวคั่นคือเครื่องหมายโคลอน)
awk -F: '{print $1, $7}' /etc/passwd
# คอลัมน์สุดท้าย
df -h | awk '{print $NF}'
# กำหนดตัวคั่นของผลลัพธ์เอง
awk -F: -v OFS=';' '{print $1, $3}' /etc/passwd
เงื่อนไข: พิมพ์เฉพาะบรรทัดที่ต้องการ
ก่อนบล็อกในวงเล็บปีกกา สามารถใส่เงื่อนไขได้ awk จะรันการกระทำเฉพาะบรรทัดที่ตรงเงื่อนไข
# ผู้ใช้ที่มี UID ตั้งแต่ 1000 ขึ้นไป (ผู้ใช้ทั่วไป ไม่ใช่ระบบ)
awk -F: '$3 >= 1000 {print $1}' /etc/passwd
# คำขอที่มีรหัสตอบกลับ 500 ขึ้นไป (ฟิลด์ที่ 9 ในรูปแบบ combined)
awk '$9 >= 500' access.log
# บรรทัดที่คอลัมน์ที่ 6 ตรงกับ regex
awk '$6 ~ /POST/' access.log
# ไฟล์ที่ใหญ่กว่า 100 MB จากผลลัพธ์ของ ls -l
ls -l | awk '$5 > 100*1024*1024 {print $9}'
BEGIN, END และ NR: awk ในฐานะโปรแกรมขนาดเล็ก
บล็อก BEGIN ทำงานก่อนอ่านบรรทัดแรก ส่วน END ทำงานหลังบรรทัดสุดท้าย NR เก็บหมายเลขบรรทัดปัจจุบัน และในบล็อก END จะเป็นจำนวนบรรทัดทั้งหมด
# รวมขนาดการตอบกลับ (ฟิลด์ที่ 10) เป็นเมกะไบต์
awk '{sum += $10} END {printf "%.1f MB\n", sum/1024/1024}' access.log
# เวลาตอบกลับเฉลี่ยจากคอลัมน์สุดท้าย
awk '{s += $NF} END {if (NR) print s/NR}' timings.log
# ข้ามหัวตารางของไฟล์ CSV
awk -F, 'NR > 1 {print $2}' users.csv
# นับคำขอตาม IP: อาร์เรย์เชิงสัมพันธ์
awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head
ตัวอย่างสุดท้ายเป็นแบบคลาสสิกของการวิเคราะห์ล็อก อาร์เรย์ count ที่ใช้ IP เป็นคีย์แทนที่ sort | uniq -c ได้ และขยายต่อได้ง่าย เช่น รวมปริมาณทราฟฟิกของแต่ละ IP
ท่อ: grep, sed และ awk ทำงานร่วมกัน
พลังที่แท้จริงอยู่ที่การรวมกัน แต่ละตัวทำงานของตัวเอง และ | ส่งผลลัพธ์ต่อไป
IP 10 อันดับแรกที่ได้ข้อผิดพลาด 5xx
grep ' 50[0-9] ' /var/log/nginx/access.log \
| awk '{print $1}' \
| sort | uniq -c | sort -rn | head -10
412 203.0.113.45
118 198.51.100.7
36 192.0.2.19
ในที่นี้ grep กรองบรรทัดแบบหยาบ awk ตัดฟิลด์ และ sort | uniq -c นับจำนวนซ้ำ คำสั่งนี้เขียนเป็น awk ตัวเดียวได้เช่นกัน: awk '$9 ~ /^5/ {c[$1]++} END {for (i in c) print c[i], i}' access.log | sort -rn | head ซึ่งแม่นยำกว่า เพราะตรวจคอลัมน์รหัสตอบกลับโดยตรง
URL ที่ถูกเปิดบ่อยที่สุด
awk '{print $7}' access.log | sed 's/?.*//' | sort | uniq -c | sort -rn | head
sed ตัดส่วน query string ออก เพื่อให้ /search?q=a และ /search?q=b ถูกนับเป็นหน้าเดียวกัน
ค่าจากคอนฟิกโดยไม่รวมคอมเมนต์
grep -v '^\s*#' /etc/myapp/app.env | grep '=' | awk -F= '{print $1}'
ข้อผิดพลาดในชั่วโมงที่กำหนด
grep '14/Oct/2026:03:' access.log | awk '$9 >= 500 {print $4, $7, $9}'
ข้อผิดพลาดที่พบบ่อย
- ใช้เครื่องหมายคำพูดคู่แทนคู่เดี่ยวใน awk. ใน
awk "{print $1}"shell จะแทนที่$1ด้วยตัวแปรของตัวเองก่อนที่ awk จะทำงาน เขียนโปรแกรม awk และนิพจน์ sed ในเครื่องหมายคำพูดคู่เดี่ยว - ลืม
gใน sed. หากไม่มี จะแทนที่เฉพาะการตรงแรกในบรรทัด ที่เหลือจะไม่เปลี่ยน - ใช้
sed -iโดยไม่ตรวจสอบ. regex ที่ผิดจะทำให้ไฟล์เสียโดยไม่มีข้อความเตือน ให้รันโดยไม่ใส่-iก่อน หรือใช้-i.bak - ใช้
+และ|กับ grep ไม่ได้. เป็นไวยากรณ์แบบขยาย ต้องใช้grep -E(หรือsed -E) - จุดในรูปแบบ.
grep 192.168.1.1จะพบ192x168y1z1ด้วย เพราะจุดหมายถึง "อักขระตัวใดก็ได้" ใช้grep -Fหรือใส่แบ็กสแลชเป็น\. - ช่องว่างในคอลัมน์. หากฟิลด์อาจมีช่องว่าง (CSV หรือพาธไฟล์) ให้กำหนดตัวคั่นอย่างชัดเจน เช่น
awk -F,หรือawk -F'\t' - cat ที่ไม่จำเป็น.
cat file | grep xใช้ได้ แต่grep x fileสั้นกว่าและไม่สร้างโปรเซสเพิ่ม
เครื่องมืออื่นที่ใช้แทน grep, sed และ awk ได้
สำหรับ JSON เครื่องมือเหล่านี้ไม่เหมาะ ควรใช้ jq เพื่อแยกโครงสร้าง (jq '.items[] | select(.status=="active") | .name') และ yq สำหรับ YAML การค้นหาแบบเร็วในโปรเจกต์ขนาดใหญ่มักใช้ ripgrep (คำสั่ง rg) ซึ่งเคารพ .gitignore โดยค่าเริ่มต้น หากคุ้นกับไวยากรณ์ของ Perl คำสั่ง perl -pe 's/a/b/g' และ perl -lane 'print $F[0]' ก็แทน sed และ awk ได้ในบรรทัดเดียว แต่ grep, sed และ awk มีแทบทุกเซิร์ฟเวอร์โดยไม่ต้องติดตั้ง จึงควรรู้จักไว้เป็นอันดับแรก
สรุป
grep ค้นหาบรรทัด sed แก้ไขบรรทัด awk ทำงานกับคอลัมน์และคำนวณ จดจำชุดพื้นฐานนี้ไว้: grep -rniE กับ -A/-B/-C, sed 's/.../.../g' กับ -i.bak และ -n 'Np', awk -F กับ $1, $NF, เงื่อนไข และบล็อก END การวิเคราะห์ล็อกเกือบทุกแบบประกอบจากชิ้นส่วนเหล่านี้ ตัวอย่าง grep sed awk ชุดนี้ พร้อม find, jq และ curl ถูกฝึกใช้จริงในบทเรียนหลักสูตร Linux เรื่องการประมวลผลข้อความในเทอร์มินัล
คำถามที่พบบ่อย
grep กับ egrep ต่างกันอย่างไร?
egrep เป็นชื่อเรียกเก่าของ grep -E (regex แบบขยาย) GNU grep รุ่นใหม่จะแสดงคำเตือนเมื่อเรียก egrep จึงควรเขียน grep -E
จะค้นหาข้อความในทุกไฟล์ของไดเรกทอรีอย่างไร?
ใช้ grep -rn "ข้อความ" /path โดย -r ค้นแบบเรียกซ้ำ และ -n แสดงหมายเลขบรรทัด หากต้องการจำกัดประเภทไฟล์ เพิ่ม --include="*.php"
จะแทนที่ข้อความในไฟล์จำนวนมากพร้อมกันอย่างไร?
รวมการค้นหาเข้ากับ sed: grep -rl "old" . | xargs sed -i 's/old/new/g' ควรทำ commit หรือสำรองข้อมูลก่อน
จะพิมพ์หลายคอลัมน์ใน awk คั่นด้วยแท็บอย่างไร?
กำหนดตัวคั่นขาออก: awk -v OFS='\t' '{print $1, $3}' file หากไม่กำหนด OFS ฟิลด์จะถูกคั่นด้วยช่องว่าง
จะลบช่องว่างท้ายบรรทัดใน sed อย่างไร?
คำสั่ง sed -i 's/[[:space:]]*$//' file ลบช่องว่างและแท็บที่ท้ายทุกบรรทัดในไฟล์
หัวข้อนี้สอนอย่างละเอียดพร้อมฝึกปฏิบัติในคอร์ส “Linux: с нуля до уверенного администрирования”
ไปที่คอร์ส