0Pricing
Linux Command Line & Bash Scripting Mastery · บทเรียน

การแยกวิเคราะห์บันทึกเว็บและแอปพลิเคชันในระดับใหญ่

ดึงรหัสสถานะ เวลาแฝง และฟิลด์ไคลเอ็นต์จากบันทึกการเข้าถึงด้วย grep, cut และ awk

การแยกวิเคราะห์บันทึกเว็บและแอปพลิเคชันในระดับใหญ่ เป็นบทเรียน Linux Command Line & Bash Scripting Mastery ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Linux Command Line & Bash Scripting Mastery และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Linux Command Line & Bash Scripting Mastery มีบทเรียนทั้งหมด 4 บทเรียน

บันทึกการเข้าถึงเว็บคืออะไร

เซิร์ฟเวอร์ HTTP ทุกชนิด ไม่ว่าจะเป็น Apache, Nginx หรือ Caddy จะเขียนหนึ่งบรรทัดลงในบันทึกการเข้าถึงสำหรับทุกคำขอ การทำความเข้าใจโครงสร้างของบรรทัดเหล่านี้เป็นพื้นฐานของงานวิเคราะห์บันทึกทั้งหมด

บรรทัด รูปแบบบันทึกแบบรวม (CLF) โดยทั่วไปมีลักษณะดังนี้:

  • IP ของไคลเอนต์ — ผู้ส่งคำขอ
  • เวลาประทับ — เวลาที่เหตุการณ์เกิดขึ้น
  • บรรทัดคำขอ — วิธีการ เส้นทาง และโพรโทคอล
  • รหัสสถานะ — การตอบกลับ HTTP (200, 404, 500…)
  • จำนวนไบต์ที่ส่ง — ขนาดเนื้อหาการตอบกลับ
  • ผู้อ้างอิง — หน้าต้นทาง
  • ตัวระบุผู้ใช้ — ข้อความที่ระบุเบราว์เซอร์หรือบอต

ตัวอย่างบรรทัดจาก /var/log/nginx/access.log:

192.168.1.10 - alice [11/Jun/2026:14:32:01 +0000] "GET /api/orders HTTP/1.1" 200 1482 "-" "curl/7.88.1"

เมื่อมีปริมาณการใช้งานสูง ไฟล์เหล่านี้อาจเติบโตเป็นหลายล้านบรรทัดต่อวัน เป้าหมายของบทเรียนนี้คือการดึง กรอง และรวมค่าจากฟิลด์ต่าง ๆ อย่างมีประสิทธิภาพโดยใช้เครื่องมือ BASH มาตรฐาน

สุ่มดูบันทึกสดด้วย tail และ grep

ก่อนเขียนกระบวนการประมวลผลต่อเนื่องใด ๆ ให้ตรวจสอบบันทึกเพื่อทำความเข้าใจรูปแบบก่อน tail ช่วยให้ดูสตรีมข้อมูลสดได้ ส่วน grep ช่วยจำกัดผลลัพธ์ให้เหลือเฉพาะบรรทัดที่เกี่ยวข้องได้ทันที

รูปแบบที่ใช้บ่อย:

  • tail -n 1000 access.log — 1,000 บรรทัดล่าสุด
  • tail -f access.log — ติดตามแบบเรียลไทม์
  • tail -f access.log | grep '" 5' — เฉพาะข้อผิดพลาดระดับ 5xx ที่เข้ามา

ประเด็นสำคัญคือ grep จะจับคู่กับทั้งบรรทัด ดังนั้นการยึดรูปแบบด้วยจุดเริ่มต้นจึงมีความสำคัญ การจับคู่ ' 500 ' (ที่มีช่องว่าง) ช่วยป้องกันการจับคู่เส้นทาง URL ที่มีข้อความ 500 โดยไม่ตั้งใจ

#!/usr/bin/env bash
# Watch only HTTP 5xx errors arriving in real time
tail -f /var/log/nginx/access.log \
  | grep --line-buffered '" 5[0-9][0-9] '

ดึงรหัสสถานะด้วย cut

cut แยกแต่ละบรรทัดตามตัวคั่นและแสดงฟิลด์ที่เลือก ในรูปแบบบันทึกแบบรวม รหัสสถานะอยู่ที่ฟิลด์ 9 เมื่อแยกด้วยช่องว่าง แต่เครื่องหมายอัญประกาศรอบบรรทัดคำขอทำให้การนับจากจุดอ้างอิงที่ทราบแน่ชัดปลอดภัยกว่า

เคล็ดลับที่เชื่อถือได้คือ เนื่องจากบรรทัดคำขออยู่ภายในเครื่องหมายอัญประกาศเสมอ รหัสสถานะจึงเป็นโทเค็นแรกหลังเครื่องหมายอัญประกาศปิดของฟิลด์คำขอ การใช้ cut -d'"' -f3 จะแยกทุกอย่างหลังเครื่องหมายอัญประกาศของคำขอ จากนั้น cut -d' ' -f2 ครั้งที่สองจะเลือกเฉพาะรหัสสถานะ

การใช้ cut สองขั้นตอนนี้เป็นสำนวนมาตรฐานสำหรับบันทึก CLF ทำงานเร็วและไม่ต้องพึ่งพาส่วนประกอบภายนอก

#!/usr/bin/env bash
# Print only the HTTP status code from each log line
# Input format: ... "GET /path HTTP/1.1" 200 1482 ...
cut -d'"' -f3 /var/log/nginx/access.log \
  | cut -d' ' -f2 \
  | sort \
  | uniq -c \
  | sort -rn

นับรหัสสถานะด้วย awk

awk มีความสามารถมากกว่า cut เพราะสะสมสถานะข้ามบรรทัดได้ รูปแบบมาตรฐานสำหรับนับจำนวนครั้งที่พบคือใช้อาร์เรย์แบบเชื่อมโยง โดยให้ค่าที่สนใจเป็นกุญแจ

ใน CLF ฟิลด์ $9 (เริ่มนับจาก 1 และคั่นด้วยช่องว่าง) คือรหัสสถานะ awk จะประมวลผลแต่ละบรรทัด เพิ่มตัวนับ แล้วแสดงสรุปที่เรียงลำดับแล้วในบล็อก END

เหตุใดจึงควรเลือก awk แทน cut | sort | uniq -c เพราะ awk ทำงานครบใน<ไม่กี่รอบเดียว>โดยไม่ต้องเรียงลำดับไฟล์ทั้งหมดก่อน ซึ่งสำคัญมากเมื่อบันทึกมีขนาดหลายร้อยกิกะไบต์

#!/usr/bin/env bash
# Count HTTP status codes in a single awk pass
awk '{ count[$9]++ }
END {
  for (status in count)
    printf "%6d  %s\n", count[status], status
}' /var/log/nginx/access.log \
  | sort -rn

กรองข้อผิดพลาดและดึง IP ของไคลเอนต์

งานปฏิบัติการที่พบบ่อยอย่างหนึ่งคือการค้นหา IP ของไคลเอนต์ที่ทำให้เกิดข้อผิดพลาดมากที่สุด งานนี้รวมการกรอง (เลือกเฉพาะบรรทัดข้อผิดพลาด) เข้ากับการดึงฟิลด์ (IP ที่ฟิลด์ 1)

กลยุทธ์ของกระบวนการประมวลผลต่อเนื่อง:

  • ใช้ awk กรองตามช่วงรหัสสถานะและดึง IP ในขั้นตอนเดียว เพื่อหลีกเลี่ยงการใช้ grep แยกอีกครั้ง
  • ส่งต่อไปยัง sort | uniq -c | sort -rn | head เพื่อดูรายการอันดับต้น ๆ อย่างรวดเร็ว

รูปแบบนี้เร็วพอที่จะใช้กับไฟล์บันทึกขนาด 10 GB บนเซิร์ฟเวอร์เครื่องเดียวได้ โดยไม่ต้องโหลดไฟล์ทั้งหมดเข้าสู่หน่วยความจำ

#!/usr/bin/env bash
# Top 10 IPs generating HTTP 4xx or 5xx errors
awk '$9 ~ /^[45][0-9][0-9]$/ { print $1 }' \
    /var/log/nginx/access.log \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -10

แยกวิเคราะห์เวลาแฝงของการตอบกลับจากบันทึกแอปพลิเคชัน

เซิร์ฟเวอร์แอปพลิเคชัน เช่น Rails, Gunicorn และ Express ที่ใช้ morgan มักบันทึกระยะเวลาของคำขอ Nginx สามารถตั้งค่าให้เขียน $request_time เป็นฟิลด์เพิ่มเติมท้ายแต่ละบรรทัดได้

ตัวอย่างรูปแบบบันทึก Nginx แบบกำหนดเองใน nginx.conf:

log_format timed '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" rt=$request_time';

เมื่อมีเวลาแฝงอยู่ในบันทึกแล้ว คุณสามารถใช้ awk คำนวณค่าเฉลี่ย ค่าสูงสุด และค่าประมาณเปอร์เซ็นไทล์จากคำขอหลายล้านรายการได้ โดยไม่ต้องโหลดข้อมูลลงฐานข้อมูล

#!/usr/bin/env bash
# Compute average and max request_time from Nginx timed log
# Assumes last field is rt=<seconds> e.g. rt=0.042
awk '{
  # Extract numeric value after rt=
  n = split($NF, a, "=")
  if (n == 2 && a[1] == "rt") {
    t = a[2] + 0
    sum += t
    count++
    if (t > max) max = t
  }
}
END {
  if (count > 0)
    printf "Requests: %d  Avg: %.4fs  Max: %.4fs\n", count, sum/count, max
}' /var/log/nginx/timed_access.log

สร้างฮิสโตแกรมเวลาแฝงด้วย awk

ค่าเฉลี่ยเพียงค่าเดียวจะบดบังเวลาแฝงช่วงหาง ฮิสโตแกรมจะแสดงการกระจายว่า คำขอส่วนใหญ่ทำงานเร็วแต่มีบางส่วนช้ามาก (หางยาว) หรือการกระจายมีความสม่ำเสมอ

เคล็ดลับคือจัดแต่ละค่าเข้าช่วงที่ปัดเศษแล้ว โดยใช้การคำนวณจำนวนเต็มภายใน awk การคูณด้วย 1000 (แปลงวินาทีเป็นมิลลิวินาที) แล้วใช้การหารจำนวนเต็มจะทำให้ขอบเขตของแต่ละช่วงชัดเจน

วิธีนี้สร้างฮิสโตแกรมแบบข้อความที่อ่านได้โดยตรงในเทอร์มินัล ซึ่งมักเร็วกว่าการส่งข้อมูลไปยัง Grafana เมื่อต้องการตรวจสอบอย่างรวดเร็ว

#!/usr/bin/env bash
# Latency histogram (50ms buckets) from Nginx timed log
awk '{
  n = split($NF, a, "=")
  if (n == 2 && a[1] == "rt") {
    ms = int(a[2] * 1000)      # convert to ms
    bucket = int(ms / 50) * 50 # round down to 50ms boundary
    hist[bucket]++
  }
}
END {
  for (b in hist)
    printf "%6dms  %d\n", b, hist[b]
}' /var/log/nginx/timed_access.log \
  | sort -n

ดึง User-Agent และตรวจจับบอต

ฟิลด์ User-Agent (ฟิลด์ 6 เมื่อแยกด้วย ") ใช้ระบุไคลเอนต์ โปรแกรมรวบรวมข้อมูล โปรแกรมขูดข้อมูล และบอตอันตรายมักทำให้ตัวชี้วัดคลาดเคลื่อนและทำให้จำนวนข้อผิดพลาดสูงเกินจริง การกรองสิ่งเหล่านี้ออกช่วยให้เห็นภาพการใช้งานจากผู้ใช้จริงได้ชัดเจนขึ้น

ลักษณะทั่วไปของบอต: bot, crawler, spider, curl, python-requests, Googlebot, Bingbot.

ใช้ grep -iv (กลับเงื่อนไขโดยไม่สนใจตัวพิมพ์เล็กใหญ่) เพื่อแยกบอตที่รู้จักออก หรือใช้ awk แยกด้วย " แล้วจับคู่กับฟิลด์ UA โดยตรง

#!/usr/bin/env bash
# Count top 15 User-Agent strings, excluding known bots
awk -F'"' '{ print $6 }' /var/log/nginx/access.log \
  | grep -iv -e 'bot' -e 'crawler' -e 'spider' -e 'curl' \
              -e 'python' -e 'wget' -e 'Go-http-client' \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -15

รวมปริมาณการใช้งานตามปลายทาง

การทราบว่าปลายทางใดได้รับการใช้งานมากที่สุดและสร้างข้อผิดพลาดมากที่สุด จะช่วยจัดลำดับความสำคัญของการปรับปรุงประสิทธิภาพและการวางแผนความจุ เส้นทางคำขออยู่ภายในฟิลด์คำขอที่อยู่ในเครื่องหมายอัญประกาศ

แยกด้วย " เลือกฟิลด์ 2 (บรรทัดคำขอ) จากนั้นตัดวิธีการและโพรโทคอลออกเพื่อแยกเส้นทาง สำหรับ API ที่มีพารามิเตอร์ในเส้นทาง เช่น /users/12345 คุณอาจต้องทำให้เป็นรูปแบบมาตรฐานโดยเปลี่ยนรหัสเป็น /users/:id ด้วย sed หรือรูปแบบ awk ที่ซับซ้อนขึ้น

#!/usr/bin/env bash
# Top 20 requested endpoints (method + path, no query string)
awk -F'"' '{ print $2 }' /var/log/nginx/access.log \
  | awk '{ print $1, $2 }' \
  | sed 's|/[0-9][0-9]*\b|/:id|g' \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -20

เชื่อมโยงข้อผิดพลาดกับปลายทางด้วย awk

การวิเคราะห์แบบผ่านครั้งเดียวที่ทรงพลังที่สุดคือการรวมหลายฟิลด์พร้อมกัน ได้แก่ ปลายทาง รหัสสถานะ และเวลาแฝงหากต้องการ อาร์เรย์แบบเชื่อมโยงของ awk ที่ใช้ค่าผสมเป็นกุญแจทำให้วิธีนี้ทั้งชัดเจนและรวดเร็ว

รูปแบบด้านล่างนับข้อผิดพลาด 5xx แยกตามปลายทางในการประมวลผลครั้งเดียว ไม่มีไฟล์ชั่วคราวและไม่มีการเรียงลำดับระหว่างทางจนกว่าจะถึงขั้นตอนสุดท้าย วิธีนี้ใช้ในสคริปต์สังเกตการณ์ระบบจริงเมื่อต้องการคำตอบภายในเวลาไม่ถึงหนึ่งนาทีจากบันทึกขนาดใหญ่

#!/usr/bin/env bash
# Count 5xx errors per endpoint path in a single pass
awk -F'"' '{
  # $2 = request line e.g. "GET /api/orders HTTP/1.1"
  # $0 in original space-split: $9 = status
  split($0, fields, " ")
  status = fields[9]
  if (status ~ /^5/) {
    split($2, req, " ")
    path = req[2]
    # Normalise numeric IDs
    gsub(/\/[0-9]+/, "/:id", path)
    errors[path]++
  }
}
END {
  for (p in errors)
    printf "%6d  %s\n", errors[p], p
}' /var/log/nginx/access.log \
  | sort -rn \
  | head -20

ประมวลผลบันทึกที่หมุนเวียนและบีบอัด

เซิร์ฟเวอร์ส่วนใหญ่จะหมุนเวียนบันทึกทุกวัน ไฟล์เก่าจะถูกบีบอัดด้วย gzip เป็น access.log.1.gz, access.log.2.gz และอื่น ๆ เครื่องมือมาตรฐานไม่สามารถอ่านไฟล์เหล่านี้โดยตรงได้ แต่มีสองวิธีที่ทำงานได้อย่างเหมาะสม:

  • zcat — คลายการบีบอัดไปยังเอาต์พุตมาตรฐาน แล้วส่งต่อเข้าสู่กระบวนการประมวลผลต่อเนื่อง
  • zgrep — ใช้ grep ค้นหาภายในไฟล์ gzip โดยตรงโดยไม่ต้องแตกไฟล์

หากต้องการวิเคราะห์บันทึกตลอดหนึ่งสัปดาห์ที่มีทั้งไฟล์แบบไม่บีบอัดและไฟล์บีบอัด ให้ใช้การแทนที่กระบวนการหรือรวมไฟล์ด้วย zcat ส่วนโค้ดตัวอย่างด้านล่างประมวลผลไฟล์ที่หมุนเวียนล่าสุด 7 ไฟล์รวมกับบันทึกปัจจุบันที่กำลังทำงานอยู่ ในการเรียกใช้ awk เพียงครั้งเดียว โดยไม่ต้องใช้ไฟล์ชั่วคราว

#!/usr/bin/env bash
# Aggregate status codes across a week of rotated logs
# Handles both plain and gzip-compressed rotation files

LOG_DIR="/var/log/nginx"

{
  cat  "${LOG_DIR}/access.log" 2>/dev/null
  zcat "${LOG_DIR}/access.log".*.gz 2>/dev/null
} | awk '
{ count[$9]++ }
END {
  for (s in count)
    printf "%6d  %s\n", count[s], s
}' | sort -rn

ฟิลด์ใดเก็บรหัสสถานะ HTTP ในรูปแบบบันทึกแบบรวม

คุณกำลังเขียนคำสั่งบรรทัดเดียวของ awk เพื่อกรองเฉพาะการตอบกลับ HTTP 4xx จากบันทึกการเข้าถึง Nginx มาตรฐานในรูปแบบ Combined Log Format (คั่นด้วยช่องว่าง และครอบบรรทัดคำขอด้วยเครื่องหมายอัญประกาศ) หมายเลขฟิลด์ใดระบุรหัสสถานะ HTTP ได้ถูกต้อง

สรุปบทเรียน: สายงานวิเคราะห์บันทึก

ในบทเรียนนี้ คุณได้สร้างชุดเครื่องมือที่ครบถ้วนสำหรับวิเคราะห์บันทึกเว็บและแอปพลิเคชันในระดับขนาดใหญ่ โดยใช้เฉพาะยูทิลิตีมาตรฐานของ BASH

เทคนิคสำคัญที่ครอบคลุม:

  • เริ่มจากโครงสร้าง — Combined Log Format มีโครงสร้างฟิลด์ที่คาดเดาได้ การรู้โครงสร้างนี้ช่วยให้คุณแยกข้อมูลได้อย่างน่าเชื่อถือด้วย cut -d'"' หรือการอ้างอิงฟิลด์ของ awk
  • การดึงรหัสสถานะ — awk '{ count[$9]++ }' นับรหัสทั้งหมดได้ภายในการวนอ่านเพียงรอบเดียว และกรองด้วย $9 ~ /^5/ เพื่อค้นหาข้อผิดพลาดจากเซิร์ฟเวอร์
  • การวิเคราะห์เวลาแฝง — แยกวิเคราะห์ฟิลด์กำหนดเอง rt= ด้วย awk เพื่อคำนวณค่าเฉลี่ย ค่าสูงสุด และช่วงของฮิสโตแกรมได้โดยไม่ต้องใช้เครื่องมือภายนอก
  • การวิเคราะห์ไคลเอ็นต์และบอต — แยกข้อมูลด้วย " โดยใช้ -F'"' เพื่อเข้าถึงฟิลด์ User-Agent จากนั้นส่งต่อผ่าน grep -iv เพื่อแยกบอตออกก่อนรวมผล
  • การปรับมาตรฐานปลายทาง — ใช้ gsub(/\/[0-9]+/, "/:id") ภายใน awk เพื่อรวมเส้นทางที่มีพารามิเตอร์ให้เป็นรูปแบบเดียวก่อนนับ
  • บันทึกที่ถูกหมุนเวียน — รวม cat และ zcat ในเชลล์ย่อย เพื่อส่งไฟล์จากการหมุนเวียนทั้งหมดเข้าสู่สายงานเดียวกัน

รูปแบบเหล่านี้นำมาประกอบกันได้ คุณสามารถเชื่อมการกรอง การดึงข้อมูล การปรับมาตรฐาน และการรวมผลไว้ในสายงานเดียว ซึ่งประมวลผลได้หลายร้อยล้านบรรทัดบนฮาร์ดแวร์ทั่วไป หากเชี่ยวชาญพื้นฐานเหล่านี้แล้ว คุณแทบไม่จำเป็นต้องใช้บริการรวมบันทึกโดยเฉพาะสำหรับการตรวจสอบเหตุขัดข้องเฉพาะกิจ

คำถามที่พบบ่อย

บทเรียน “การแยกวิเคราะห์บันทึกเว็บและแอปพลิเคชันในระดับใหญ่” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การแยกวิเคราะห์บันทึกเว็บและแอปพลิเคชันในระดับใหญ่” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Linux Command Line & Bash Scripting Mastery ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Linux Command Line & Bash Scripting Mastery มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การแยกวิเคราะห์บันทึกเว็บและแอปพลิเคชันในระดับใหญ่”

ดึงรหัสสถานะ เวลาแฝง และฟิลด์ไคลเอ็นต์จากบันทึกการเข้าถึงด้วย grep, cut และ awk คุณปฏิบัติ Linux Command Line & Bash Scripting Mastery ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Linux Command Line & Bash Scripting Mastery หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Linux Command Line & Bash Scripting Mastery บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “การแยกวิเคราะห์บันทึกเว็บและแอปพลิเคชันในระดับใหญ่” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Linux Command Line & Bash Scripting Mastery นี้ได้ไหม

ได้ บทเรียน Linux Command Line & Bash Scripting Mastery ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การแยกวิเคราะห์บันทึกเว็บและแอปพลิเคชันในระดับใหญ่
  2. การติดตามบันทึกแบบเรียลไทม์และการแจ้งเตือนแบบสตรีม
  3. การสอบถาม journald ด้วย journalctl ในสคริปต์
  4. การคำนวณตัวชี้วัดและฮิสโตแกรมจากกระแสบันทึก
← กลับไปที่ Linux Command Line & Bash Scripting Mastery