คู่มือ

Bash script สำหรับมือใหม่: สคริปต์แรก ตัวแปร เงื่อนไข ลูป และ cron

อัปเดต: · Skillaria.Top

Bash script คือไฟล์ข้อความที่มีคำสั่งของ shell เริ่มต้นด้วยบรรทัด #!/bin/bash ได้สิทธิ์รันด้วย chmod +x และเรียกด้วย ./script.sh ใน script ใช้ตัวแปร อาร์กิวเมนต์ $1 และ $2 เงื่อนไข if ลูป for และ while ส่วนการรันตามกำหนดเวลาตั้งค่าด้วย crontab -e

Bash script คือไฟล์ข้อความธรรมดาที่รวมคำสั่งซึ่งปกติคุณต้องพิมพ์ทีละบรรทัดใน terminal script เริ่มต้นด้วยบรรทัด shebang #!/bin/bash ได้สิทธิ์รันด้วย chmod +x และเรียกด้วย ./script.sh คู่มือ bash script สำหรับมือใหม่นี้ครอบคลุมตั้งแต่บรรทัดแรกไปจนถึงการรันตามกำหนดเวลา ได้แก่ ตัวแปร อาร์กิวเมนต์ เงื่อนไข ลูป การจัดการข้อผิดพลาด และ cron

Bash script แรกในหนึ่งนาที

สร้างไฟล์ด้วยโปรแกรมแก้ไขข้อความใดก็ได้ เช่น nano:

nano hello.sh
#!/bin/bash
# script แรก: ทักทายและแสดงข้อมูลเล็กน้อยเกี่ยวกับระบบ
echo "สวัสดี $USER!"
echo "วันนี้: $(date +%Y-%m-%d)"
echo "คุณอยู่ที่: $(pwd)"

บันทึก (Ctrl+O, Enter, Ctrl+X) ให้สิทธิ์รัน แล้วรัน:

$ chmod +x hello.sh
$ ./hello.sh
สวัสดี alice!
วันนี้: 2026-10-12
คุณอยู่ที่: /home/alice

บรรทัดที่ขึ้นต้นด้วย # คือคอมเมนต์ $(คำสั่ง) แทรกผลลัพธ์ของคำสั่งลงในข้อความโดยตรง

Shebang: ทำไมต้องมีบรรทัดแรก

บรรทัด #!/bin/bash บอกระบบว่าต้องใช้ตัวแปลภาษาตัวใดรันไฟล์ หากไม่มี script จะรันด้วย shell ที่มีอยู่ และบางระบบ /bin/sh ไม่ใช่ bash แต่เป็น dash ที่เรียบง่ายกว่า ซึ่งบางไวยากรณ์จะใช้ไม่ได้

อีกรูปแบบที่พบบ่อยคือ #!/usr/bin/env bash ซึ่งค้นหา bash จาก PATH เหมาะเมื่อ bash ไม่ได้อยู่ใน /bin

วิธีรัน script และเหตุผลที่ต้องมี ./

มีสองวิธี:

  • ./hello.sh — ต้องมี shebang และสิทธิ์ x ต้องใส่ ./ เพราะไดเรกทอรีปัจจุบันไม่อยู่ใน PATH
  • bash hello.sh — ไม่ต้องมีสิทธิ์รัน เพราะระบุตัวแปลภาษาไว้แล้ว

หากไม่มี ./ จะเห็น command not found และหากไม่มี chmod +x จะเห็น Permission denied ซึ่งเป็นสองสาเหตุนี้เอง หากต้องการเรียก script ของตัวเองด้วยชื่อจากที่ใดก็ได้ ให้เก็บไว้ใน ~/bin หรือ /usr/local/bin

ตัวแปร

ตัวแปรกำหนดค่าโดยไม่มีช่องว่างรอบเครื่องหมาย = และอ่านค่าด้วย $:

#!/bin/bash
name="backup"
dir="/var/backups"
today=$(date +%F)

echo "ไฟล์: ${dir}/${name}-${today}.tar.gz"

วงเล็บปีกกาแยกชื่อตัวแปรออกจากข้อความรอบข้าง บรรทัด name = "backup" ที่มีช่องว่างเป็นข้อผิดพลาด เพราะ bash จะพยายามรันคำสั่งชื่อ name

เครื่องหมายคำพูดมีความหมาย

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

file="รายงาน ฉบับ.txt"
rm $file      # ผิดพลาด: จะพยายามลบ "รายงาน" และ "ฉบับ.txt"
rm "$file"    # ถูกต้อง
echo '$HOME'  # แสดง $HOME ตามตัวอักษร
echo "$HOME"  # แสดง /home/alice

ตัวแปรสภาพแวดล้อม

ตัวแปรอย่าง $HOME, $USER, $PATH ถูกกำหนดโดยระบบ ตัวแปรของคุณเองส่งต่อให้โปรเซสลูกได้ด้วย export การตั้งค่าสภาพแวดล้อมถาวรใส่ไว้ใน ~/.bashrc หรือ ~/.profile

อาร์กิวเมนต์ของ script

ค่าที่ส่งมาตอนรันสามารถเข้าถึงได้ผ่าน $1, $2 และต่อไปเรื่อยๆ

ตัวแปรเก็บอะไร
$0ชื่อของ script
$1, $2…อาร์กิวเมนต์ตัวแรก ตัวที่สอง
$#จำนวนอาร์กิวเมนต์
"$@"อาร์กิวเมนต์ทั้งหมดแยกกัน
$?รหัสออกของคำสั่งล่าสุด
$$PID ของ script ปัจจุบัน
#!/bin/bash
echo "Script $0 ได้รับ $# อาร์กิวเมนต์"
echo "อาร์กิวเมนต์แรก: $1"
$ ./args.sh nginx 80
Script ./args.sh ได้รับ 2 อาร์กิวเมนต์
อาร์กิวเมนต์แรก: nginx

รับข้อมูลจากผู้ใช้ด้วยคำสั่ง read: read -p "ใส่ชื่อ: " name

เงื่อนไข if

ทุกคำสั่งคืนรหัสออก: 0 หมายถึงสำเร็จ ค่าอื่นหมายถึงผิดพลาด โครงสร้าง if ตรวจรหัสนี้โดยตรง สำหรับการเปรียบเทียบใช้ [[ ... ]] และต้องมีช่องว่างภายในวงเล็บ

#!/bin/bash
if [[ $# -eq 0 ]]; then
    echo "วิธีใช้: $0 <ไดเรกทอรี>"
    exit 1
fi

if [[ -d "$1" ]]; then
    echo "มีไดเรกทอรี $1"
elif [[ -f "$1" ]]; then
    echo "$1 เป็นไฟล์ ไม่ใช่ไดเรกทอรี"
else
    echo "ไม่พบ $1"
fi
การตรวจสอบความหมาย
-f fileมีไฟล์ปกติอยู่
-d dirมีไดเรกทอรีอยู่
-e pathมีพาธอยู่
-x fileไฟล์รันได้
-z "$var" / -n "$var"สตริงว่าง / ไม่ว่าง
"$a" == "$b", !=เปรียบเทียบสตริง
-eq -ne -lt -le -gt -geเปรียบเทียบตัวเลข

ตรวจสอบคำสั่งใดก็ได้โดยตรง:

if systemctl is-active --quiet nginx; then
    echo "nginx กำลังทำงาน"
else
    echo "nginx หยุดทำงานแล้ว"
fi

สำหรับตัวเลือกหลายแบบ case เหมาะกว่า:

case "$1" in
    start)   echo "กำลังเริ่มทำงาน" ;;
    stop)    echo "กำลังหยุดทำงาน" ;;
    *)       echo "ไม่รู้จักคำสั่ง: $1"; exit 1 ;;
esac

ลูป for และ while

for: วนผ่านรายการ

for host in web1 web2 db1; do
    echo "กำลังตรวจสอบ $host"
    ping -c 1 -W 1 "$host" > /dev/null && echo "  เข้าถึงได้" || echo "  เข้าถึงไม่ได้"
done

for file in /var/log/*.log; do
    echo "$file: $(wc -l < "$file") บรรทัด"
done

for i in {1..5}; do
    echo "ครั้งที่ $i"
done

while: ทำซ้ำตราบที่เงื่อนไขเป็นจริง

ตัวอย่างคลาสสิกคืออ่านไฟล์ทีละบรรทัด:

while read -r user; do
    echo "ผู้ใช้: $user"
done < users.txt

และการรอให้บริการเริ่มทำงาน:

count=0
until curl -sf http://localhost:8080/health > /dev/null; do
    count=$((count + 1))
    if [[ $count -ge 10 ]]; then
        echo "บริการไม่สามารถเริ่มทำงานได้"
        exit 1
    fi
    sleep 3
done
echo "บริการพร้อมใช้งาน"

การคำนวณใน bash เขียนในวงเล็บคู่: $((a + b)) ทำงานกับจำนวนเต็มเท่านั้น

ฟังก์ชัน

โค้ดที่ซ้ำกันควรแยกเป็นฟังก์ชัน อาร์กิวเมนต์ในฟังก์ชันยังเป็น $1, $2 เหมือนเดิม และ local ป้องกันไม่ให้ตัวแปรหลุดออกไปภายนอก

log() {
    local level="$1"; shift
    echo "$(date '+%F %T') [$level] $*"
}

log INFO "เริ่มการสำรองข้อมูล"
log ERROR "พื้นที่ดิสก์เต็ม"

การจัดการข้อผิดพลาด: set -euo pipefail

โดยค่าเริ่มต้น bash จะทำงานต่อหลังคำสั่งล้มเหลว ดังนั้น script สำรองข้อมูลอาจจบ "สำเร็จ" ทั้งที่ไม่ได้คัดลอกอะไรเลย ผู้เขียนมักเพิ่มโหมดเข้มงวดไว้ต้นไฟล์:

#!/bin/bash
set -euo pipefail
  • -e — หยุดทันทีที่เกิดข้อผิดพลาดแรก
  • -u — ถือว่าการใช้ตัวแปรที่ยังไม่ได้ประกาศเป็นข้อผิดพลาด (ป้องกันกรณี rm -rf "$DIR/" เมื่อ DIR ว่าง)
  • -o pipefail — ไปป์ไลน์ถือว่าล้มเหลวหากส่วนใดส่วนหนึ่งล้มเหลว

เพื่อดีบัก รัน script ด้วย bash -x script.sh bash จะพิมพ์ทุกคำสั่งก่อนรัน และตรวจหาข้อผิดพลาดทั่วไปก่อนรันได้ด้วย shellcheck

ตัวอย่างจริง: script สำรองข้อมูล

#!/bin/bash
set -euo pipefail

SRC="${1:-/var/www}"
DST="/var/backups/site"
KEEP_DAYS=7
STAMP=$(date +%F_%H-%M)

mkdir -p "$DST"
tar -czf "$DST/site-$STAMP.tar.gz" -C "$(dirname "$SRC")" "$(basename "$SRC")"
echo "สร้างไฟล์สำรอง $DST/site-$STAMP.tar.gz แล้ว"

# ลบไฟล์สำรองที่เก่ากว่า KEEP_DAYS วัน
find "$DST" -name "site-*.tar.gz" -mtime +"$KEEP_DAYS" -delete

รูปแบบ ${1:-/var/www} หมายความว่า "อาร์กิวเมนต์ตัวแรก หากไม่มีให้ใช้ /var/www" บันทึก script เป็น /usr/local/bin/backup-site.sh และให้สิทธิ์รัน: sudo chmod +x /usr/local/bin/backup-site.sh

รันตามกำหนดเวลา: cron

Cron คือบริการที่รันคำสั่งตามเวลาที่กำหนด งานของผู้ใช้แก้ไขด้วย crontab -e ดูด้วย crontab -l ส่วนงานของ root ใช้ sudo crontab -e

บรรทัดของงานประกอบด้วยห้าฟิลด์เวลาและคำสั่ง:

# นาที ชั่วโมง วันของเดือน เดือน วันในสัปดาห์ คำสั่ง
30 2 * * * /usr/local/bin/backup-site.sh >> /var/log/backup.log 2>&1
กำหนดการเวลาที่รัน
*/5 * * * *ทุก 5 นาที
0 * * * *ต้นทุกชั่วโมง
30 2 * * *ทุกวันเวลา 02:30
0 9 * * 1-5วันจันทร์ถึงศุกร์เวลา 09:00
0 3 1 * *เวลา 03:00 ของวันที่ 1 ทุกเดือน
@rebootครั้งเดียวหลังบูต

ทำไม script รันด้วยมือได้แต่ไม่รันใน cron

  • สภาพแวดล้อมต่างกัน Cron รันงานด้วย PATH ที่จำกัด และไม่อ่าน ~/.bashrc ให้เขียนพาธเต็มของคำสั่งและไฟล์ หรือกำหนด PATH ไว้ต้น crontab
  • ไดเรกทอรีปัจจุบันต่างกัน งานเริ่มที่ไดเรกทอรีส่วนตัว ไม่ใช่ที่ที่ script อยู่ ใช้พาธแบบสัมบูรณ์
  • เครื่องหมาย % ใน crontab เครื่องหมาย % หมายถึงขึ้นบรรทัดใหม่ ดังนั้น date +%F ที่เขียนตรงๆ ต้องใส่ escape เป็น \% ทางที่ง่ายกว่าคือย้ายคำสั่งเข้า script
  • ผลลัพธ์หายไป เปลี่ยนทิศทางผลลัพธ์ไปยัง log (>> file 2>&1) ไม่เช่นนั้นจะมองไม่เห็นข้อผิดพลาด การรันของ cron ดูได้ด้วย journalctl -u cron (บนระบบตระกูล RHEL ชื่อบริการคือ crond)

บนระบบที่ใช้ systemd สามารถใช้ systemd timer แทน cron ได้ timer เขียนผลลัพธ์ลงใน journal และสามารถรันชดเชยงานที่พลาดไปได้

สรุป

Bash script สำหรับมือใหม่คือไฟล์ที่มี shebang #!/bin/bash สิทธิ์รัน และลำดับของคำสั่ง ใส่ตัวแปรในเครื่องหมายคำพูด ตรวจอาร์กิวเมนต์ด้วย if ใช้ลูป for และ while และโหมดเข้มงวด set -euo pipefail คุณจะได้เครื่องมืออัตโนมัติที่เชื่อถือได้ ซึ่ง cron รันตามเวลาได้ bash script สภาพแวดล้อม และ cron ถูกสอนแบบลงมือทำในหลักสูตร Linux

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

bash ต่างจาก sh อย่างไร?

sh คือ shell มาตรฐาน POSIX ส่วน bash คือเวอร์ชันขยายที่มีอาร์เรย์ วงเล็บคู่ และความสามารถอื่น script ที่ใช้ไวยากรณ์ของ bash ต้องรันด้วย bash ไม่ใช่ sh

นามสกุลของ bash script ควรเป็นอะไร?

Linux ไม่สนใจนามสกุล ตัวแปลภาษาถูกกำหนดจาก shebang นามสกุล .sh เป็นเพียงข้อตกลงให้คนอ่านรู้ว่าเป็น script

จะส่งรหัสผ่านให้ script อย่างปลอดภัยได้อย่างไร?

อย่าส่งเป็นอาร์กิวเมนต์ เพราะผู้ใช้คนอื่นเห็นได้ผ่าน ps ให้ใช้ตัวแปรสภาพแวดล้อมที่อ่านจากไฟล์สิทธิ์ 600 หรือใช้ read -s เพื่อรับค่าโดยไม่แสดงบนหน้าจอ

จะตรวจว่า script รันด้วยสิทธิ์ root อยู่หรือไม่?

เปรียบเทียบรหัสผู้ใช้กับศูนย์: if [[ $EUID -ne 0 ]]; then echo "ต้องใช้สิทธิ์ root"; exit 1; fi

จะรัน script เบื้องหลังโดยไม่หยุดเมื่อออกจาก SSH อย่างไร?

ใช้ nohup ./script.sh & หรือเซสชัน tmux หรือ screen สำหรับการทำงานระยะยาว ควรห่อ script เป็นบริการ systemd

เรียนหัวข้อนี้ผ่านวิดีโอ

หัวข้อนี้สอนอย่างละเอียดพร้อมฝึกปฏิบัติในคอร์ส “Linux: с нуля до уверенного администрирования”

ไปที่คอร์ส