Bash script สำหรับมือใหม่: สคริปต์แรก ตัวแปร เงื่อนไข ลูป และ cron
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ต้องใส่./เพราะไดเรกทอรีปัจจุบันไม่อยู่ในPATHbash 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: с нуля до уверенного администрирования”
- Bash-скрипты: от команды в терминале до рабочего скрипта автоматизации
- Пакеты, окружение и первая автоматизация: apt, профиль и cron