ทบทวนก่อนสอบปลายภาค
01476113 ภาคเรียนที่ 1/2569
เนื้อหาสัปดาห์ 6 ถึง 11 และเรื่องสิทธิ์ไฟล์จากสัปดาห์ 5
ผลในแล็บจำลองจาก Ubuntu
ข้อเขียนราว 8 ข้อ แต่ละข้อเป็นโจทย์สถานการณ์ ให้อธิบายพร้อมคำสั่ง
นับจาก Sheet1 และ Sheet1 (8) รวม 34 ข้อ
ข้อที่ไฮไลต์ฟ้ามากคือ pipe, permission และ cron ควรเตรียมให้เขียนได้คล่องที่สุด
เขียนหนึ่งบรรทัดว่าโจทย์ถามกี่อย่าง เช่น สาเหตุ วิธีแก้ และวิธีป้องกัน แล้วตอบให้ครบทุกข้อย่อย
บอกว่าระบบทำงานอย่างไรจึงเกิดผลนั้น เช่น “uniq ยุบเฉพาะบรรทัดซ้ำที่อยู่ติดกัน จึงต้อง sort ก่อน”
ใส่คำสั่งที่รันได้จริง อธิบาย option สำคัญทีละตัว อย่าเขียนแค่ชื่อคำสั่ง
ปิดท้ายด้วยคำสั่งตรวจ เช่น ls -l id df -h systemctl status crontab -l
Least Privilege, ไม่ใช้ 777, quote ตัวแปร, ใช้ full path, เก็บ log ไว้ตรวจย้อนหลัง
ปัญหานี้เกิดจาก ... เพราะ ... (กลไก) แก้ได้ด้วยคำสั่ง $ ... ตรวจผลด้วย $ ... เพื่อป้องกันไม่ให้เกิดซ้ำ ควร ...
| สิทธิ์ | บนไฟล์ | บนไดเรกทอรี |
|---|---|---|
| r (4) | อ่านเนื้อหา cat | ดูรายชื่อไฟล์ ls |
| w (2) | แก้ไขเนื้อหา | สร้าง ลบ เปลี่ยนชื่อไฟล์ข้างใน |
| x (1) | รันเป็นโปรแกรมหรือสคริปต์ | เข้าไปได้ cd และเข้าถึงไฟล์ข้างใน |
- ไฟล์ d ไดเรกทอรี l symlinkw และ x ของไดเรกทอรีที่เก็บไฟล์นั้นprojects/report.txt ต้องมีอะไรบ้างprojects/ ต้องมี x เพื่อผ่านเข้าไป และไฟล์ต้องมี w (กับ r ถ้าเปิดด้วย editor) ไม่จำเป็นต้องให้ w กับไดเรกทอรีระบบตรวจสิทธิ์ทีละชั้น ถ้าเป็น owner ใช้ชุด u ทันที ไม่ไปดู g หรือ o ต่อ
| เลข | สิทธิ์ | ใช้บ่อยกับ |
|---|---|---|
755 | rwxr-xr-x | สคริปต์ที่ทุกคนรันได้, ไดเรกทอรีทั่วไป |
750 | rwxr-x--- | สคริปต์ของทีม คนนอกห้ามแตะ |
700 | rwx------ | สคริปต์ส่วนตัว |
644 | rw-r--r-- | ไฟล์ทั่วไป |
640 | rw-r----- | ไฟล์ลับ เช่น /etc/shadow |
# สัญลักษณ์ who(u g o a) op(+ - =) perm(r w x) chmod u+x,go-w file chmod o= secret.txt # ล้างสิทธิ์ others chown admin:devops deploy.sh # X ตัวใหญ่ ใส่ x ให้ไดเรกทอรีเท่านั้น chmod -R u=rwX,g=rX,o= /srv/project
Soft link ln -s | Hard link ln | |
|---|---|---|
| ชี้ไปที่ | path ของไฟล์ | inode เดียวกับต้นฉบับ |
| ลบต้นฉบับ | ลิงก์เสีย | ยังเปิดได้ |
| ข้ามพาร์ทิชัน | ได้ | ไม่ได้ |
| ลิงก์ไดเรกทอรี | ได้ | ไม่ได้ |
ใน ls -l | l และ -> target | link count เพิ่ม |
ข้ามพาร์ทิชันไม่ได้ เพราะเลข inode นับแยกในแต่ละพาร์ทิชัน
ลิงก์ไดเรกทอรีไม่ได้ เพราะจะเกิดลูปไม่รู้จบตอน find หรือ du ไล่ต้นไม้
| fd | ชื่อ | ปกติไปที่ |
|---|---|---|
0 | stdin | คีย์บอร์ด |
1 | stdout | หน้าจอ |
2 | stderr | หน้าจอ |
stdout กับ stderr ขึ้นจอเหมือนกัน จึงดูไม่ออกว่ามาจากคนละช่อง จนกว่าจะ redirect
| รูปแบบ | ผล |
|---|---|
cmd > f | stdout ลงไฟล์ เขียนทับ |
cmd >> f | stdout ต่อท้ายไฟล์ |
cmd < f | อ่าน stdin จากไฟล์ |
cmd 2> f | เฉพาะ stderr ลงไฟล์ |
cmd > f 2>&1 | ทั้งสองช่องลงไฟล์เดียว |
cmd &> f | แบบย่อของบรรทัดบน (bash) |
cmd 2>/dev/null | ทิ้ง error |
2>&1 แปลว่า “ให้ fd 2 ไปที่เดียวกับ fd 1 ณ ตอนนี้” ลำดับจึงสำคัญ ลองสลับลำดับในแล็บหน้าถัดไปมี a.txt อยู่จริง แต่ไม่มี nofile.txt
$ ls a.txt nofile.txt > output.txt 2> error.log
output.txt ได้บรรทัด a.txt เพราะเป็น stdouterror.log ได้ข้อความ cannot access 'nofile.txt' เพราะเป็น stderr2 เพราะมีไฟล์หนึ่งหาไม่เจอ2>&1 > out ไม่เหมือน > out 2>&1 แบบแรก stderr ถูกผูกกับจอก่อน stdout จะย้ายไปไฟล์ error จึงยังขึ้นจอset -o pipefail| คำสั่ง | หน้าที่ | option ที่ควรรู้ |
|---|---|---|
grep | กรองบรรทัดตาม pattern | -i ไม่สนตัวพิมพ์ -c นับ -v กลับด้าน |
cut | ตัดคอลัมน์ | -d: ตัวคั่น -f1 ฟิลด์ |
sort | เรียงบรรทัด | -r กลับ -n ตัวเลข -h ขนาด |
uniq | ยุบบรรทัดซ้ำที่ติดกัน | -c นับจำนวน |
wc | นับ | -l บรรทัด |
head | เอา n บรรทัดแรก | -n 5 |
Pipe ส่งเฉพาะ stdout ถ้าอยากให้ error ผ่าน pipe ด้วยใช้ cmd 2>&1 | grep ...
grep 'Failed password' /var/log/auth.log \
| awk '{print $11}' \
| sort | uniq -c \
| sort -rn | head -n 5
grep | เก็บเฉพาะบรรทัดที่ล็อกอินพลาด |
awk $11 | ดึงฟิลด์ที่ 11 ซึ่งเป็น IP |
sort | จัด IP เดียวกันให้อยู่ติดกัน |
uniq -c | นับจำนวนครั้งต่อ IP |
sort -rn | เรียงตัวเลขมากไปน้อย |
head -n 5 | เอา 5 อันดับแรก |
nginx access.log ใช้สูตรเดียวกัน แค่เปลี่ยนเป็น awk '{print $1}'
$11 ใช้กับ log รูปแบบ syslog ดั้งเดิมตามสไลด์ Ubuntu 24.04 ใช้ timestamp แบบ ISO ทำให้ IP ไปอยู่ที่ $9 และบรรทัด invalid user ฟิลด์ก็เลื่อน ดึงด้วย grep -oE 'from [0-9.]+' | awk '{print $2}' จะไม่ขึ้นกับตำแหน่ง
| sed | awk | |
|---|---|---|
| มองข้อมูลเป็น | ข้อความทีละบรรทัด | ตาราง แยกฟิลด์ $1 $2 |
| เหมาะกับ | ค้นหาแทนที่ ลบ แทรกบรรทัด | กรองตามเงื่อนไข คำนวณ ทำรายงาน |
| คำสั่งหลัก | s d p i | pattern {action}, BEGIN, END, NR, NF |
| ตัวอย่างงาน | แก้ค่าใน config ทั้งไฟล์ | รวมยอดขาย, ดึงคอลัมน์ IP |
# แทนที่ทุกตัว และลบบรรทัด comment sed 's/error/ERROR/g' app.log sed '/^#/d' app.conf # กรองตามเงื่อนไข และรวมยอด awk '$3 > 20' sales.txt awk '{s+=$2*$3} END {print s}' sales.txt
| โหมด | เข้าด้วย | ใช้ทำ |
|---|---|---|
| Normal | Esc | เลื่อน ลบ คัดลอก |
| Insert | i a o | พิมพ์ข้อความ |
| Visual | v V | เลือกช่วงข้อความ |
| Command | : / | บันทึก ออก ค้นหา |
:w บันทึก :q ออก :wq บันทึกแล้วออก :q! ออกโดยไม่บันทึกdd ลบบรรทัด yy คัดลอก p วาง u undo /คำ ค้นหา
| Ctrl+O | บันทึก (Write Out) |
| Ctrl+X | ออก |
| Ctrl+W | ค้นหา |
| Ctrl+K / U | ตัดบรรทัด / วาง |
sudo find / -name "*.leases" 2>/dev/null จะพบ /var/lib/dhcp/dhcpd.leases แล้วเปิดด้วย sudo nano ต้องใช้ sudo เพราะ owner คือ root$PATHcommand not found และ exit status 127| ตัวแปร | ค่าตัวอย่าง |
|---|---|
HOME | /home/student |
USER | student |
SHELL | /bin/bash |
PWD | ไดเรกทอรีปัจจุบัน |
ดูค่า echo $PATH ดูว่าคำสั่งไหนถูกเลือก which -a myscript หรือ type myscript
| shell แบบไหน | อ่านไฟล์ |
|---|---|
| Login shell (SSH, login ครั้งแรก) | /etc/profile แล้ว ~/.bash_profile หรือ ~/.profile ซึ่งมัก source ~/.bashrc ต่อ |
| Interactive non-login (เปิดแท็บ terminal ใหม่) | /etc/bash.bashrc แล้ว ~/.bashrc |
| Non-interactive (สคริปต์, cron) | ไม่อ่าน .bashrc สคริปต์ที่รันจาก terminal ยังได้ตัวแปรที่ export จาก shell แม่ แต่ไม่ได้ alias ส่วน cron ไม่ได้ทั้งคู่ |
export ทำให้ตัวแปรส่งต่อไปยังโปรเซสลูก แต่มีผลแค่ session นั้น
~/bin ได้จากทุกที่ และมี alias ll~/.bashrc เพิ่มท้ายไฟล์
export PATH="$PATH:$HOME/bin" alias ll='ls -alh'
terminal
$ source ~/.bashrc $ echo $PATH /usr/local/bin:/usr/bin:/bin:/home/student/bin $ type ll ll is aliased to `ls -alh'
PATH=$HOME/bin เฉยๆ เพราะจะทับ PATH เดิม ทำให้ ls และคำสั่งพื้นฐานหาไม่เจอps / topR | Running |
S | Sleeping รอเหตุการณ์ ปลุกด้วย signal ได้ |
D | Sleep ที่ขัดจังหวะไม่ได้ มักรอ I/O ของดิสก์ |
T | Stopped |
Z | Zombie จบแล้วแต่พ่อยังไม่เก็บ exit status |
1 | SIGHUP | terminal ปิด |
2 | SIGINT | Ctrl+C |
9 | SIGKILL | ฆ่าทันที |
15 | SIGTERM | ค่าเริ่มต้นของ kill |
20 | SIGTSTP | Ctrl+Z |
ใช้ kill PID ก่อนเสมอ ให้โปรแกรมได้ปิดไฟล์และเก็บกวาด kill -9 ใช้เมื่อไม่ยอมจบเท่านั้น
ps aux | grep sleep top # เรียงตาม CPU pgrep -a python cmd & # รันเบื้องหลัง jobs # ดู job fg %1 # ดึงมาหน้า bg %1 # ให้รันต่อข้างหลัง kill %1 kill 4821 kill -9 4821
& แล้วปิด terminal งานจะรอดไหมไม่รอด เมื่อ terminal ปิดหรือ SSH หลุด kernel ส่ง SIGHUP ให้ shell แล้ว bash ส่ง SIGHUP ต่อให้ทุก job ของตัวเอง ค่าเริ่มต้นของ SIGHUP คือจบโปรเซส
| วิธี | ใช้เมื่อ |
|---|---|
nohup cmd > out.log 2>&1 & | รู้ล่วงหน้าว่าจะออกจากระบบ |
disown -h %1 | รันไปแล้ว เพิ่งนึกได้ |
setsid cmd | แยก session ใหม่ทั้งหมด |
tmux / screen | อยากกลับมาดูหน้าจอเดิม |
| systemd service | งานถาวรบนเซิร์ฟเวอร์ |
สไลด์อาจารย์ไม่มี nohup และ disown แต่โจทย์ F41 และ F6 ต้องใช้
#!/bin/bash # greet.sh ทักทายผู้ใช้ name="${1:-$USER}" # ไม่มี arg ใช้ $USER today=$(date +%A) # command substitution read -p "อายุ: " age echo "Hello $name, today is $today"
$ chmod +x greet.sh $ ./greet.sh alice
x=5 ห้ามมีช่องว่างรอบ = และไม่ใส่ $./script รันใน bash โปรเซสใหม่ ตัวแปรที่ตั้งจะไม่กลับมา ส่วน source script รันใน shell ปัจจุบันรัน ./my.sh apple "grape juice" kiwi
| ตัวแปร | ค่า |
|---|---|
$0 | ./my.sh |
$1 | apple |
$2 | grape juice (quote จึงเป็นค่าเดียว) |
$# | 3 ไม่นับ $0 |
"$@" | ทุกตัว แยกเป็นคำละตัว |
"$*" | ทุกตัว รวมเป็นสตริงเดียว |
$? | exit status ของคำสั่งล่าสุด |
$$ | PID ของสคริปต์ |
read -p ข้อความนำ -s ซ่อนตัวอักษร -t หมดเวลา -r ไม่แปล backslash
[ ] กับ [[ ]]-eq -ne -gt -lt -ge -le
= != -z ว่าง -n ไม่ว่าง
-e | มีอยู่ |
-f | เป็นไฟล์ปกติ |
-d | เป็นไดเรกทอรี |
-r -w -x | อ่าน เขียน รันได้ |
-s | ขนาดมากกว่า 0 |
[ ] หรือ test | [[ ]] | |
|---|---|---|
| ที่มา | POSIX ใช้ได้กับ sh ทุกตัว | keyword ของ bash, zsh |
| ตัวแปรไม่ quote | เกิด word splitting และ glob ค่าว่างหรือมีช่องว่างทำให้ error | ไม่ split ไม่ glob ปลอดภัยกว่า |
| AND / OR | -a -o หรือแยก [ ] && [ ] | && || ! ในวงเล็บได้เลย |
| Pattern / Regex | ไม่มี | == *.txt และ =~ ^[0-9]+$ |
< > | ต้อง escape ไม่งั้นกลายเป็น redirect | เทียบสตริงได้ตรงๆ |
name="my file" [ $name = "my file" ] # bash: [: too many arguments [ "$name" = "my file" ] # ถูก ต้อง quote [[ $name == my* ]] # ถูก และใช้ pattern ได้
| ค่า | ความหมาย |
|---|---|
0 | สำเร็จ |
1 | error ทั่วไป |
2 | ใช้ผิด หรือ ls หาไฟล์ไม่เจอ |
126 | เจอไฟล์ แต่ไม่มีสิทธิ์รัน |
127 | command not found |
#!/bin/bash if [ -f "$1" ]; then echo "Config found" else echo "Config not found: $1" >&2 exit 1 fi
$? ต้องอ่านทันที เพราะคำสั่งถัดไป รวมถึง echo จะเขียนทับ ใช้คู่กับ && และ || ได้ เช่น ./check.sh app.conf && ./run.sh
for f in *.txt; do
echo "$f"
done
for i in {1..5}; do ...; done
i=1
while [ $i -le 3 ]; do
i=$((i+1))
done
while read -r line; do
echo "$line"
done < list.txt
while วนขณะเงื่อนไขจริง until วนจนกว่าเงื่อนไขจะจริง
is_exec() {
local f="$1"
[ -f "$f" ] && [ -x "$f" ]
}
for f in "${1:-.}"/*; do
is_exec "$f" && echo "$f"
done
$1 $2 ของฟังก์ชันเองlocal กันชนกับตัวแปร globalreturn ส่ง status 0 ถึง 255 ส่วนค่าข้อมูลให้ echo แล้วรับด้วย $( )fruits=(apple banana cherry)
echo "${fruits[0]}" # apple
echo "${fruits[@]}" # ทั้งหมด
echo "${#fruits[@]}" # 3
echo "${!fruits[@]}" # 0 1 2
fruits+=(mango)
echo $(( 7 / 2 )) # 3 echo "scale=2; 7/2" | bc # 3.50
#!/bin/bash
ทุกสคริปต์มาจากใบงานในคลาส
#!/bin/bash
set -euo pipefail
DIR="${1:?usage: $0 <dir>}"
[ -d "$DIR" ] || { echo "no dir" >&2; exit 1; }
rm -rf -- "${DIR:?}"/*
-e | คำสั่งไหนล้มเหลว หยุดสคริปต์ทันที |
-u | อ้างตัวแปรที่ไม่ได้กำหนดค่า ให้ error |
-o pipefail | pipeline ล้มเหลวถ้ามีคำสั่งใดใน pipe ล้มเหลว |
${VAR:?msg} | ว่างหรือไม่มีค่า หยุดพร้อมข้อความ |
rm -rf "$DIR"/* อันตรายDIR ว่าง บรรทัดนี้กลายเป็น rm -rf /* ลบทั้งระบบ ถ้ารันด้วย root ป้องกันด้วย set -u และ ${DIR:?}| อย่าทำ | ทำแทน |
|---|---|
chmod -R 777 | 755 กับไดเรกทอรี 644 กับไฟล์ |
curl ... | sudo bash | โหลดมาอ่านก่อน ตรวจ checksum |
eval $input | ตรวจและกรอง input |
echo $VAR | echo "$VAR" |
| รหัสผ่านในสคริปต์ | ตัวแปรสภาพแวดล้อม หรือไฟล์สิทธิ์ 600 |
trap 'rm -f "$TMP"' EXIT ลบไฟล์ชั่วคราวเสมอแม้สคริปต์จบด้วย error
#!/bin/bash
set -euo pipefail
SRC="${1:?usage: $0 <dir>}"
DEST=/backup
LOG=/var/log/backup.log
TS=$(date +%Y%m%d_%H%M)
[ -d "$SRC" ] || { echo "$TS no source $SRC" >> "$LOG"; exit 1; }
if tar -czf "$DEST/web_$TS.tar.gz" -C "$SRC" .; then
echo "$TS OK web_$TS.tar.gz" >> "$LOG"
else
echo "$TS FAIL tar exit $?" >> "$LOG"
exit 1
fi
| option | ความหมาย |
|---|---|
-c | create สร้าง archive |
-x | extract แตกไฟล์ |
-t | list ดูรายการข้างใน |
-z | ผ่าน gzip |
-v | แสดงชื่อไฟล์ขณะทำ |
-f | ชื่อไฟล์ archive ต้องตามหลังทันที |
-C dir | เปลี่ยนไดเรกทอรีก่อนทำ |
tar -tzf web_20261104_0230.tar.gz tar -xzvf web_20261104_0230.tar.gz \ -C /tmp/restore
tar รวมไฟล์เป็นก้อนเดียวและเก็บ permission ส่วน gzip บีบอัด
/dev/sda | SATA, SCSI, USB ลูกแรก |
/dev/sdb1 | ลูกที่สอง พาร์ทิชัน 1 |
/dev/nvme0n1p2 | NVMe controller 0, namespace 1, พาร์ทิชัน 2 |
lsblk sudo fdisk -l /dev/sdb sudo blkid /dev/sdb1
| MBR | GPT | |
|---|---|---|
| มักคู่กับ | BIOS | UEFI |
| ดิสก์สูงสุด | 2.2 TB | ราว 9.4 ZB |
| primary | 4 | 128 |
| สำรองตาราง | ไม่มี | มีที่ท้ายดิสก์ |
| ext4 | มาตรฐาน มี journaling เสถียร ไม่มี snapshot |
| xfs | ไฟล์ใหญ่ งานขนานดี ย่อขนาดไม่ได้ |
| btrfs | Copy-on-Write, snapshot, checksum มี overhead |
Journaling บันทึกสิ่งที่จะเขียนก่อนเขียนจริง ไฟดับกลางทางจึงกู้กลับได้เร็ว
lsblk เห็น sdb ที่ยังไม่มีพาร์ทิชันsudo fdisk /dev/sdb แล้วกด n p 1 Enter Enter wsudo mkfs.ext4 /dev/sdb1 ต้องเป็น sdb1 ไม่ใช่ sdbsudo mkdir -p /mnt/backupsudo mount /dev/sdb1 /mnt/backupsudo blkid /dev/sdb1 คัด UUID ไปใส่ /etc/fstabsudo mount -a ไม่มี error แล้วดู df -h/etc/fstabUUID=b06b...c0b1 /mnt/backup ext4 defaults 0 2
| 1 | อุปกรณ์ ใช้ UUID เพราะชื่อ sdX เปลี่ยนได้เมื่อเพิ่มดิสก์ |
| 2 | mount point |
| 3 | ชนิด filesystem |
| 4 | options เช่น defaults noexec nofail |
| 5 | dump ใช้ 0 |
| 6 | ลำดับ fsck ราก / เป็น 1 ที่เหลือ 2 ไม่ตรวจ 0 |
mount -a ทดสอบก่อนรีบูตเสมอ/mnt/backupmount ด้วยมือมีผลถึงรีบูตเท่านั้น หลังรีบูต /mnt/backup เป็นแค่ไดเรกทอรีว่างบน root สคริปต์จึงเขียนลงดิสก์หลักจนเต็ม
df -h /mnt/backup เห็นว่าเป็น /dev/sda2 ไม่ใช่ sdb1 และ du -sh /mnt/backup เห็นขนาดที่กินไป/etc/fstab แล้ว mount -a ย้ายไฟล์กลับเข้าดิสก์ใหม่ ตรวจด้วย df -hmountpoint -q /mnt/backup || exit 1df | du | |
|---|---|---|
| ดูระดับ | ทั้ง filesystem | ไฟล์และไดเรกทอรี |
| วิธีได้ค่า | อ่าน superblock เร็ว | ไล่รวมขนาดไฟล์ ช้า |
| ตอบคำถาม | ดิสก์เต็มไหม | อะไรกินที่ |
df -h /var sudo du -sh /var/* 2>/dev/null | sort -rh | head -n 3 df -i /var # inode sudo lsof +L1 # ไฟล์ที่ลบแล้วแต่ยังเปิดอยู่
ค่าของ du กับ df ไม่เท่ากันได้ เช่น ไฟล์ที่ลบแล้วแต่โปรเซสยังเปิดอยู่ ไฟล์ที่ซ่อนใต้ mount point และ hard link
df -h ว่าง 50% แต่สร้างไฟล์ไม่ได้| สาเหตุ | ตรวจด้วย |
|---|---|
| inode หมด ไฟล์เล็กจำนวนมาก เช่น session หรือ cache | df -i IUse% 100% |
| ติด quota ของ user หรือ group | quota -u user |
เขียนผิดที่ path นั้นอยู่คนละ filesystem เช่น /tmp เป็น tmpfs | df -h /path |
| reserved blocks ext4 กันไว้ 5% ให้ root user ปกติจึงเต็มก่อน | sudo tune2fs -l /dev/sda2 | grep -i reserved |
lsof) และควร backup หรือบีบอัดแทนลบ log ที่ยังต้องใช้sudo pvcreate /dev/sdb /dev/sdc /dev/sdd
sudo vgcreate vg_main /dev/sdb /dev/sdc /dev/sdd
sudo lvcreate -n lv_data -L 100G vg_main
sudo mkfs.ext4 /dev/vg_main/lv_data
# เพิ่มดิสก์ภายหลัง
sudo pvcreate /dev/sde
sudo vgextend vg_main /dev/sde
sudo lvextend -r -L +50G /dev/vg_main/lv_data
สไลด์อาจารย์ไม่มี vgextend และ lvextend แต่ต้องใช้ตอบเรื่องขยายพื้นที่ -r สั่ง resize filesystem ให้ด้วย
somchai:x:1001:1001:Somchai P.:/home/somchai:/bin/bash
| 1 | username |
| 2 | x แปลว่า hash อยู่ใน /etc/shadow |
| 3 | UID |
| 4 | GID ของ primary group |
| 5 | GECOS ชื่อจริง ห้อง เบอร์ |
| 6 | home directory |
| 7 | login shell /usr/sbin/nologin คือห้าม login |
0 root, 1 ถึง 999 บัญชีระบบและ service, 1000+ ผู้ใช้ทั่วไป
| ไฟล์ | เก็บ | ใครอ่านได้ |
|---|---|---|
/etc/passwd | ข้อมูลบัญชี | ทุกคน (644) |
/etc/shadow | hash รหัสผ่าน อายุรหัสผ่าน | root และกลุ่ม shadow (640) |
/etc/group | กลุ่มและสมาชิก | ทุกคน |
sudo vipw เพราะ usermod จะปฏิเสธเมื่อ UID นั้นมีโปรเซสรันอยู่ ซึ่ง UID 0 มีเสมอ แล้วตรวจ awk -F: '$3==0' /etc/passwd ต้องเหลือแค่ rootเพิ่มกลุ่ม sudo แล้วยังใช้ไม่ได้ ส่วนใหญ่เพราะยังไม่ logout แล้ว login ใหม่ กลุ่มใหม่มีผลกับ session ถัดไป
-G กับ -aG และการให้ sudo แบบจำกัดsudo groupadd dev sudo useradd -m -s /bin/bash -G dev alice sudo passwd alice sudo usermod -aG sudo alice # Ubuntu sudo usermod -aG wheel alice # RHEL id alice
usermod -G testing alice แทนที่ กลุ่มรองทั้งหมดด้วย testing กลุ่มเดิมจึงหาย ต้องมี -a (append) คู่กับ -G เสมอ$ sudo visudo -f /etc/sudoers.d/web
# user host=(runas) command
%webops ALL=(root) \
/usr/bin/systemctl restart nginx
visudo ตรวจ syntax ก่อนบันทึก ถ้าแก้ /etc/sudoers ตรงๆ แล้วพิมพ์ผิด อาจไม่มีใครใช้ sudo ได้อีก
| apt | dpkg | |
|---|---|---|
| ระดับ | high level | low level |
| ดึงจาก repository | ได้ | ไม่ได้ ติดตั้งจากไฟล์ .deb |
| แก้ dependency | อัตโนมัติ | ไม่ทำ |
sudo apt update sudo apt install -y nginx sudo apt upgrade apt list --installed | grep nginx sudo dpkg -i app.deb dpkg -l | grep nginx
apt update แค่ดึงรายการ ไม่ได้อัปเกรดอะไร apt upgrade คือตัวที่อัปเกรดจริง
start | enable | |
|---|---|---|
| ทำงานตอนนี้ | ใช่ | ไม่ |
| เปิดเองหลังรีบูต | ไม่ | ใช่ สร้าง symlink |
sudo systemctl enable --now nginx # ทั้งสองอย่าง systemctl status nginx sudo systemctl restart nginx sudo systemctl reload nginx # อ่าน config ใหม่ systemctl is-enabled nginx journalctl -u nginx --since today
สถานะ active (running) inactive (dead) failed ถ้า failed ให้ดูสาเหตุที่ journalctl -u
วันในสัปดาห์ 0 และ 7 คืออาทิตย์ 1-5 คือจันทร์ถึงศุกร์
* | ทุกค่า | , | หลายค่า 1,15 |
- | ช่วง 1-5 | / | ทุกๆ */3 |
crontab -e crontab -l
crontab -r # ลบทั้งหมด ระวัง
sudo crontab -u alice -e
@reboot @daily @hourly @weekly ใช้แทน 5 ฟิลด์ได้
| สาเหตุ | แก้ |
|---|---|
PATH ของ cron มีแค่ /usr/bin:/bin | ใช้ full path เช่น /usr/bin/python3 |
| working directory คือ home ไม่ใช่ไดเรกทอรีโปรเจกต์ | cd /home/student/project && ก่อน |
ไม่โหลด .bashrc ตัวแปรที่ export ไว้หายหมด | ประกาศในสคริปต์หรือบนหัว crontab |
สคริปต์ไม่มีสิทธิ์ x | chmod u+x หรือเรียก /bin/bash script.sh |
ไฟล์ใน /etc/cron.d/ ไม่มีช่อง user หรือชื่อไฟล์มีจุด | เพิ่มช่อง user ที่ 6 ชื่อไฟล์ใช้แค่ A-Z a-z 0-9 _ - |
| output หายไป ไม่รู้ว่าพังตรงไหน | >> job.log 2>&1 |
journalctl -u cron --since today
grep CRON /var/log/syslog
journalctl -u nginx -p err -b
journalctl -f # ตามดูสด
tail -f /var/log/auth.log
sudo grep 'Failed password' /var/log/auth.log
/var/log/syslog | ข้อความทั่วไป รวมถึง cron |
/var/log/auth.log | login, sudo, ssh |
/var/log/journal/ | journal ของ systemd แบบ binary อ่านด้วย journalctl |
| หัวข้อ | ฐานจากคำถามที่ไฮไลต์ | |
|---|---|---|
| 1 | สิทธิ์ไดเรกทอรีร่วมของทีม และลิงก์สำรอง | D6 D11 D72 |
| 2 | วิเคราะห์ log ด้วย pipeline และ redirection | E7 E80 E35 |
| 3 | PATH, .bashrc และโปรเซสเบื้องหลัง | F40 F59 F41 |
| 4 | สคริปต์ตรวจ argument พร้อม exit status | G72 G74 G36 |
| 5 | สคริปต์ backup ที่รันด้วย cron | I30 I74 G41 |
| 6 | เพิ่มดิสก์ mount ถาวร และพื้นที่เต็ม | H72 H28 H42 |
| 7 | ออกแบบ LVM สำหรับข้อมูลที่โตไม่เท่ากัน | H35 |
| 8 | ตั้ง web server ตั้งแต่ผู้ใช้จนถึง log | I59 I35 I44 |
ลองเขียนเองก่อนเปิดแนวตอบ
ตัวอักษรและเลขคือเซลล์ในชีต เช่น E7 คือคอลัมน์ Pipe แถว 7 ของ Sheet1 ส่วน E18 ขึ้นไปบางตัวมาจาก Sheet1 (8)
บริษัทมี /srv/project ให้ alice และ bob ในกลุ่ม devops ใช้ร่วมกัน คนนอกกลุ่มต้องเข้าไม่ได้ ไฟล์ deploy.sh ให้เจ้าของทำได้ทุกอย่าง กลุ่มอ่านและรันได้ จงเขียนคำสั่ง อธิบายว่า bob ลบไฟล์ของ alice ได้หรือไม่ และถ้าต้องการสำรองไฟล์ที่ยังใช้ได้แม้ต้นฉบับถูกลบ ควรใช้ลิงก์แบบใด
ประกอบจาก D6, D11, D72, D18
sudo chown -R admin:devops /srv/project sudo chmod 770 /srv/project sudo chmod 750 /srv/project/deploy.sh sudo ln /srv/project/deploy.sh /srv/project/deploy_backup.sh ls -l /srv/project
770 กลุ่มเข้าได้ x สร้างไฟล์ได้ w คนนอก 0rw-r----- bob อ่านได้ แก้ไม่ได้ แต่ลบได้cp -p แทน hard link กันได้แค่การลบ แก้ผ่านชื่อใดก็เปลี่ยนทั้งคู่ soft link ไม่ช่วยเพราะจะเสียchmod 3770 sticky bit ห้ามลบไฟล์คนอื่น setgid ให้ไฟล์ใหม่อยู่กลุ่ม devopsจากไฟล์ /var/log/auth.log ให้หา IP ที่ล็อกอินล้มเหลวมากที่สุด 5 อันดับ เขียนคำสั่งบรรทัดเดียว อธิบายแต่ละขั้นของ pipeline บันทึกผลลง top5.txt และทิ้งข้อความ error ทั้งหมด อธิบายด้วยว่าถ้าตัด sort ตัวแรกออกจะเกิดอะไร
ประกอบจาก E7, E80, E6, E35, E18
{ sudo grep 'Failed password' /var/log/auth.log \
| awk '{print $11}' | sort | uniq -c \
| sort -rn | head -n 5; } > top5.txt 2>/dev/null
uniq -c นับเฉพาะบรรทัดซ้ำที่อยู่ติดกัน IP เดียวกันที่กระจายอยู่จะถูกนับแยกหลายแถว ผลผิด2>/dev/nullufw deny from IPถ้าไม่ครอบด้วย { ... ; } แล้วใส่ 2>/dev/null ท้ายบรรทัด จะทิ้งได้แค่ error ของ head
นักศึกษาวาง monitor.sh ไว้ใน ~/scripts เรียกด้วย path เต็มได้ แต่พิมพ์ชื่ออย่างเดียวได้ command not found ต่อมาพบโปรแกรมทำงานเบื้องหลังกิน CPU สูง อธิบายสาเหตุ วิธีตั้งค่าให้เรียกได้ทุกครั้งที่เปิด Bash วิธีหยุดเฉพาะโปรเซสที่มีปัญหา และถ้าต้องรันงานยาวผ่าน SSH ควรทำอย่างไร
ประกอบจาก F59, F40, F28, F41
echo 'export PATH="$PATH:$HOME/scripts"' >> ~/.bashrc source ~/.bashrc && type monitor.sh top # หา PID ที่ %CPU สูง ps -o pid,user,%cpu,cmd -p 4821 kill 4821 # SIGTERM ก่อน kill -9 4821 # ถ้ายังไม่จบ nohup ./job.sh > job.log 2>&1 &
~/scripts ไม่อยู่ใน PATH ถ้าชื่อซ้ำกันใน PATH ตัวที่อยู่ซ้ายกว่าจะถูกเลือกexport ใน terminal หายเมื่อปิด ต้องเขียนใน ~/.bashrc และเก็บ PATH เดิมkillall เพราะกระทบตัวอื่นชื่อเดียวกันเขียนสคริปต์รับชื่อไฟล์ config ทาง $1 ตรวจว่ามีอยู่จริงและเป็นไฟล์ปกติ ถ้ามีให้แสดง Config found ถ้าไม่มีให้หยุดด้วย exit status 1 อธิบายว่าเหตุใดควรใช้ [[ ]] หรือ quote ตัวแปร และระบบอื่นนำ exit status ไปใช้อย่างไร
ประกอบจาก G72, G74, G36
#!/bin/bash if [ $# -ne 1 ]; then echo "usage: $0 <config>" >&2; exit 2 fi if [[ -f $1 ]]; then echo "Config found" exit 0 else echo "Config not found: $1" >&2 exit 1 fi
-f ไม่ใช่ -e-e จริงแม้เป็นไดเรกทอรี โจทย์ต้องการไฟล์ปกติ[ -f $1 ] จะแตกเป็นหลายคำ [[ ]] ไม่ทำ word splitting./check.sh a.conf && ./app, cron หรือ CI ตัดสินจากค่านี้ผู้ใช้ student ตั้ง 2 30 * * 1-5 python3 report.py ใน crontab เพื่อให้รันวันจันทร์ถึงศุกร์ 02:30 แต่ไม่ทำงาน ทั้งที่รันเองได้ จงหาข้อผิดพลาดทั้งหมด เขียนบรรทัดที่ถูกต้องพร้อมเก็บ output และ error ลง log และบอกคำสั่งตรวจ log ของ cron วันนี้
ประกอบจาก I74, I30, I42, G4
30 2 * * 1-5 cd /home/student/project && \ /usr/bin/python3 report.py \ >> /home/student/report.log 2>&1
ใน crontab จริงต้องเขียนเป็นบรรทัดเดียว ตัดบรรทัดไว้ให้อ่านง่ายเท่านั้น
2 30 คือนาที 2 ชั่วโมง 30 ซึ่งไม่มีอยู่จริง/usr/bin:/bin python3 อยู่ใน /usr/bin จึงหาเจอ แต่ควรใช้ full path จาก which python3 เพราะคำสั่งใน /usr/local/bin หรือ ~/bin จะหาไม่เจอreport.py แบบ relative หาไม่เจอ ต้อง cd ก่อน ไฟล์ที่สคริปต์เขียนแบบ relative ก็จะไปอยู่ใน home>> ต่อท้าย 2>&1 รวม error user ทั่วไปเขียน /var/log ไม่ได้ ถ้าโจทย์บังคับต้องให้ root สร้างไฟล์และ chown ให้crontab -l และ journalctl -u cron --since todayถ้าสคริปต์ไม่มีสิทธิ์ x และห้ามแก้สิทธิ์ ให้เรียกผ่าน interpreter เช่น /bin/bash /home/student/backup.sh
เพิ่มดิสก์ใหม่ สร้างพาร์ทิชัน แล้ว mount /dev/sdb1 /mnt/backup หลังรีบูต root เต็ม ขณะที่สคริปต์ยังเขียนลง /mnt/backup วิเคราะห์สาเหตุ กู้พื้นที่คืน ตั้งค่าให้ถาวรด้วย blkid fstab df du และอธิบายความต่างของ df กับ du
ประกอบจาก H72, H28, H42, H4
df -h /mnt/backup # เห็น /dev/sda2 แปลว่ายังไม่ได้ mount sudo du -sh /mnt/backup sudo mkdir /root/hold && sudo mv /mnt/backup/* /root/hold/ sudo blkid /dev/sdb1 sudo nano /etc/fstab # UUID=xxxx /mnt/backup ext4 defaults,nofail 0 2 sudo mount -a && df -h /mnt/backup sudo mv /root/hold/* /mnt/backup/
mountpoint -q ก่อนเขียนบริษัทมีดิสก์ 3 ลูก ต้องเก็บระบบปฏิบัติการ ข้อมูลพนักงาน และ log ซึ่งโตไม่เท่ากัน เสนอการใช้ PV, VG, LV ให้ขยายได้ในอนาคต พร้อมคำสั่ง และวิธีกันไม่ให้ผู้ใช้คนเดียวใช้พื้นที่ข้อมูลจนหมด
ประกอบจาก H35, H4, H6
sudo pvcreate /dev/sdb /dev/sdc /dev/sdd sudo vgcreate vg_main /dev/sdb /dev/sdc /dev/sdd sudo lvcreate -n lv_data -L 200G vg_main sudo lvcreate -n lv_log -L 50G vg_main sudo mkfs.ext4 /dev/vg_main/lv_data sudo mkfs.ext4 /dev/vg_main/lv_log sudo lvextend -r -L +30G /dev/vg_main/lv_log sudo vgs; sudo lvs
pvcreate vgextend แล้ว lvextend -r ทำขณะ mount อยู่ได้usrquota ใน fstab ของ lv_data แล้ว quotacheck -cum /data quotaon /data edquota -u alice ตั้ง soft และ hard limit เต็มแล้วเขียนไม่ได้ ไม่กระทบคนอื่นบริษัทจะติดตั้ง nginx มี admin1 เป็นผู้ดูแล dev1 และ dev2 เป็นนักพัฒนาที่ต้องแก้ไฟล์เว็บได้แต่ไม่มีสิทธิ์ผู้ดูแลเกินจำเป็น เว็บต้องเริ่มเองตอนบูต มี backup ทุกวัน 01:00 อธิบายพร้อมคำสั่ง ตั้งแต่สร้างผู้ใช้ กลุ่ม sudo ติดตั้ง จัดการ service ตั้ง cron จนถึงตรวจ log เมื่อเว็บล่ม
ประกอบจาก I59, I4, I35, I44
sudo groupadd webdev
sudo useradd -m -s /bin/bash -G sudo admin1
sudo useradd -m -s /bin/bash -G webdev dev1
sudo useradd -m -s /bin/bash -G webdev dev2
sudo apt update && sudo apt install -y nginx
sudo systemctl enable --now nginx
sudo chown -R root:webdev /var/www/html
sudo find /var/www/html -type d -exec chmod 2775 {} +
sudo find /var/www/html -type f -exec chmod 664 {} +
sudo visudo -f /etc/sudoers.d/webdev
# %webdev ALL=(root) /usr/bin/systemctl reload nginx
sudo crontab -e
# 0 1 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
-aGvisudo -f ซึ่งตรวจ syntax ก่อนบันทึกenable คือเปิดตอนบูต --now คือเปิดทันทีsystemctl status nginx, journalctl -u nginx, /var/log/nginx/error.log, /var/log/backup.log| ใบงาน | ทักษะที่ใช้ตอบ | ข้อ |
|---|---|---|
| ค้นหา file | find -perm 640 -size +10M -mtime -3 | 1, 6 |
| softlink / hardlink | inode, ลบต้นฉบับ, ข้ามพาร์ทิชัน | 1 |
| Pipe และ sed / awk | grep sort uniq และ awk รวมยอด | 2 |
| PATH, alias, .bashrc | export ใน .bashrc | 3 |
| Process list / kill | ps kill & | 3 |
| Argument + Condition, Exit Status | -f, $?, exit | 4 |
| วนลูปสร้างไฟล์, ฟังก์ชัน | for printf -x | 4 |
| ใบงาน | ทักษะที่ใช้ตอบ | ข้อ |
|---|---|---|
| Backup / Restore | tar -czf -xzvf log | 5 |
| Crontab & Log | */3 * * * *, journalctl | 5, 8 |
| เพิ่ม disk, mount | fdisk mkfs blkid fstab | 6 |
| สร้าง user, sudo group | useradd -m usermod -aG id | 8 |
| install ssh | apt enable --now status | 8 |
ติ๊กเมื่อเขียนได้เอง เบราว์เซอร์จำไว้ให้
| ลบไฟล์ได้ไหม | ดู w+x ของไดเรกทอรี |
| hard link | ชี้ inode ข้ามพาร์ทิชันไม่ได้ |
| PATH | ซ้ายไปขวา ตัวแรกชนะ |
| ปิด terminal | SIGHUP ใช้ nohup |
[[ ]] | ไม่ split, มี pattern และ regex |
| cron | ไม่มี environment ใช้ full path |
| ว่างแต่เขียนไม่ได้ | inode, quota, คนละ filesystem |
| UID 0 | เท่ากับ root |
กด K เพื่อซ้อมด้วยแฟลชการ์ด