ลองบนอูบุนตู
$ sudo vi /etc/password
... user1:x:1101:1001::/home/user1:/bin/bash ...
หรือผ่านคำสั่ง usermod
$ sudo usermod -s /bin/bash user1
$ export GREP_OPTIONS='--color=auto'$ wget -O country.txt "http://api.hostip.info/get_html.php?ip=$IP"$ echo $(head -1 /dev/urandom | od -N 2 | awk '{ print $2 }')ลูกน้องเอาธัมบ์ไดรฟ์มาให้หาไวรัส
ผลปรากฎว่าพบไฟล์ที่เป็นนามสกุล exe เป็นจำนวนมากภายใต้ไดเรกทอรี่ย่อยเป็นร้อย
ทางแก้คือสแกนแล้วเก็บชื่อไฟล์ไว้ ตัวอย่างของเนื้อไฟล์ที่ถูกสแกนเก็บไว้ เช่น
PhotoShop 7.0/• วิธีทำตัวเยลลลี่ •_files/• วิธีทำตัวเยลลลี่ •_files.exe: W32.Autoit.Obfus FOUND PhotoShop 7.0/• วิธีทำตัวหนังสือชอล์ค •_files/truehitsstat_files/truehitsstat_files.exe: W32.Autoit.Obfus FOUND ...
จะเห็นว่ามีรูปแบบที่เราจะตัดโดยใช้คำสั่ง cut ได้คือตั้งแต่เครื่องหมาย : เป็นต้นไป
คำสั่งที่ใช้คือ
cut -d: -f1
-d: คือใช้ : เป็นตัวแบ่ง
-f1 คื่อเมื่อแบ่งแล้ว เราจะเอาสดมถ์ที่ 1 มาใช้งาน
แต่เราจะแก้ไขเนื้อไฟล์ให้เหมาะสมเล็กน้อย คือตัดท่อนล่างของไฟล์ออก ให้เหลือเฉพาะชื่อไฟล์ที่ติดไวรัส
แล้วจึงเอาเนื้อไฟล์นั้นมาเป็นข้อมูลเข้า เพื่อจะมาลบไฟล์ที่ติดไวรัสจริง ๆ
แต่มีปัญหาเพิ่มคือ ชื่อไฟล์ประกอบด้วยช่องว่างจำนวนมาก ไม่สามารถใช้คำสั่ง for i in `cat file` ได้
ค้นกูเกิลดู พบว่าเขาใช้คำสั่ง while read VARIABLE
สรุปคำสั่งทั้งหมดมาเป็นขั้นตอนดังนี้
สมมุติว่าค้นหาไวรัสในธัมบ์ไดรฟ์ในไดเรกทอรี่ /media/disk
ไปที่ที่ทำงาน
$ cd /media/disk
สแกนไวรัส แล้วเก็บผลไว้ที่ ~/virus.txt
$ clamscan -i -r * > ~/virus.txt
-i คือให้แสดงเฉพาะไฟล์ที่ติดไวรัส
-r คือให้ขุดลึกลงไปในไดเรกทอรี่ย่อยด้วย
แก้ไขไฟล์เล็กน้อย โดยตัดรายงานส่วนท้ายออก
ยกตัวอย่างส่วนที่ตัดออกคือ
$ vi ~/virus.txt
----------- SCAN SUMMARY ----------- Known viruses: 203664 Engine version: 0.92 Scanned directories: 97 Scanned files: 1234 Infected files: 252 Data scanned: 378.68 MB Time: 130.603 sec (2 m 10 s)
ทีนี้ก็ถึงเวลาลบจริง ๆ แล้ว ***ใช้ด้วยความระมัดระวังนะครับ***
ถ้ายังไม่แน่ใจ อาจแทนที่คำสั่ง rm ด้วยคำสั่ง ls ลองดูก่อน
$ cat ~/virus.txt | cut -d: -f1 | while read FILE; do rm "$FILE"; done
เสร็จแล้วครับ
หมายเหตุ
$ sudo aptitude install clamav $ sudo freshclam
vfat เขาจะเมานต์ด้วยค่าปริยายด้วยรหัสอักขระภาษาอังกฤษ ทำให้อ่านชื่อไฟล์ภาษาไทยไม่รู้เรื่อง ซึ่งจริง ๆ แล้วต้องตั้งค่ารหัสอักขระเป็น utf-8runauto.. เราจะลบไฟล์นี้ตรง ๆ ไม่ได้ ต้องใช้คำสั่งว่า$ rm runaut~1 -rfที่มา
เครื่องลูกข่ายวินโดวส์ติดไวรัส Win32/Heur ซึ่งใช้ clamav สแกนไม่พบ
ลองค้นกูเกิลดู พบ ubuntuclub แนะนำ AVG
ก่อนอื่นก็ไปดาวน์โหลดที่นี่ http://free.avg.com/us-en/download?prd=afl
แสดงตัวอย่างด้วยบรรทัดคำสั่งคือ
ดาวน์โหลดรุ่นฟรีมาใช้
$ wget http://download.avgfree.com/filedir/inst/avg85flx-r290-a2950.i386.deb $ sudo dpkg -i avg85flx-r290-a2950.i386.deb
สั่งให้รัน daemon
$ sudo /etc/init.d/avgd start
สั่งอัปเดตข้อมูลไวรัส
$ sudo avgupdate
เนื่องจากรุ่นที่เรานำมาใช้งานเป็นรุ่นฟรี จึงไม่สามารถลบไฟล์ไวรัสได้ จึงใช้วิธีสั่งสแกนและรายงานไว้ในไฟล์ หลังจากนั้นจึงอ่านชื่อไฟล์จากรายงานขึ้นมาลบ
สมมุติว่าให้ค้นที่ /media/disk และให้รายงานไว้ที่ไฟล์ ~/virus.txt
$ avgscan -r ~/virus.txt /media/disk
เตรียมการสำหรับการลบ โดยการแก้ไขไฟล์ ~/virus.txt โดยตัดส่วนหัวและส่วนท้ายให้เรียบร้อย ให้เหลือแต่ชื่อไฟล์ที่ติดไวรัส
$ vi ~/virus.txt
/media/disk/filename1 Virus XXX /media/disk/filename2 Virus YYY
สั่งลบ (*** ใช้ด้วยความระมัดระวังนะครับ ***)
$ cat ~/virus.txt | awk -F' ' '{print $1}' | while read FILE; do rm "$FILE"; doneหลัง -F เคาะสองวรรค (AVG ใช้ช่องว่างสองช่องคั่นชื่อไฟล์และไวรัส ดังนั้นถ้าชื่อไฟล์มีช่องว่างสองช่อง คำสั่งนี้จะใช้งานไม่ได้)
เสร็จแล้วครับ
$ sudo aptitude install lame cdda2wav
สคริปต์มีดังนี้$ sudo vi /usr/local/bin/d.audio2mp3
#!/bin/bash
# Rip audio disc to mp3
#
# USAGE: $0 prefix
# exam1: $0 T2
# -> T2-01-Title1.mp3
# T2-02-Title2.mp3
# ...
# in current dir
#
# Requist: aptitude install lame cdda2wav
if [ $1 ]; then PREFIX="$1-"; fi
DEV='/dev/cdrom'
TMP="/tmp/$USER/`basename $0`_$RANDOM"
mkdir -p $TMP
pushd $TMP
#to wave
cdda2wav -L 1 -D $DEV -B
#to mp3
for i in *wav; do
NUM=`echo ${i%.*} | cut -d_ -f 2`-
TITLE=`grep 'Tracktitle=' ${i%.*}.inf | cut -d\' -f2`
lame -h -V 2 $i $PREFIX$NUM$TITLE.mp3
done
popd
mv $TMP/*mp3 .
rm -rf $TMP
ทำให้รันได้$ sudo chmod 777 /usr/local/bin/d.audio2mp3
เสร็จแล้วicedax แล้วใช้คำสั่ง$ CDDA_DEVICE=/dev/sr0 cdda2mp3
bash + inkscape เพราะง่ายดีXXXX เพื่อให้สะดวกในการใช้คำสั่ง grep
โค๊ดที่สร้างขึ้น ทำแบบง่าย ๆ คือรันตัวเลขตั้งแต่ 1-500 แต่รุ่นนี้เป็นรุ่นทดสอบ ทำแค่ 1-10 พอ ขั้นตอนคือ
awksedpng ด้วยคำสั่งแบบบรรทัดคำสั่งของ inkscape เอง ด้วยพารามิเตอร์ -e$ vi runcard#!/bin/bash
FILE='card.svg'
QUAN=10
MARKER='XXXX'
for i in $(seq 1 $QUAN); do
TNUM=`echo $i | awk '{ gsub ("0","๐"); gsub ("1","๑"); gsub ("2","๒"); gsub ("3","๓"); gsub ("4","๔"); gsub ("5","๕"); gsub ("6","๖"); gsub ("7","๗"); gsub ("8","๘"); gsub ("9","๙"); print }'`
sed -e "s/$MARKER/$TNUM/g" $FILE > tmp.svg
inkscape -d 300 -e tmp$i.png tmp.svg
done
$ chmod 755 runcard
เวลาใช้งานก็สั่งรันชื่อไฟล์ runcard เฉย ๆ จะได้ไฟล์ tmp1.png จนถึง tmp10.png ก็สามารถนำไฟล์เหล่านี้ไปพิมพ์งานได้ตามต้องการ

ต้องการโอนข้อมูลผู้ใช้ไปเครื่องใหม่
ถ้าเราคัดลอกไฟล์ /etc/passwd /etc/shadow /etc/group /etc/gshadow ไปทับเครื่องใหม่แบบตรง ๆ จะเกิดปัญหาเรื่องผู้ใช้ของระบบจะติดไปด้วย ซึ่งอาจมีค่า UID และ GID ที่ไม่ตรงกัน
ค้นกูเกิลได้วิธีการจากที่นี่ครับ Move or migrate user accounts from old Linux server to a new Linux server
เขาใช้หลักการที่ว่า UID ของผู้ใช้ทั่วไป จะมีค่ามากกว่า 1000 (ของ RedHat คือ 500)
และใช้ awk เป็นตัวกรอง
--- ข้อเขียนต่อจากนี้ไป ควรทดสอบกับเครื่องทดสอบ ก่อนใช้งานจริง---
ขั้นตอนตามต้นฉบับก็ไม่มากเท่าไหร่ แต่กลัวว่าเวลาย้ายจริงจะพิมพ์พลาด เลยเอามาเขียนเป็นสคริปต์เพื่อช่วยลดความผิดพลาดตอนพิมพ์บนบรรทัดคำสั่ง รวมทั้งเป็นการศึกษาการเขียนสคริปต์ของ bash ร่วมกันแล้วกันนะครับ
ตั้งชื่อว่า d.migrate-groupuser ผมใส่ไว้ใน /root
(ห้ามใส่ในพาธการค้นหาของระบบเด็ดขาด เพราะต้องมีการแก้ไขค่าก่อนใช้งานจริง)
ตอนใช้งานก็เปลี่ยนค่าตัวแปร TARGETMACHINE ให้เป็นชื่อเครื่องใหม่ที่เราจะโอนไป แล้วก็สั่งรันได้เลย
โปรแกรมจะทำงานดังนี้
/etc/{passwd,shadow,group,gshadow} และบีบอัดไดเรกทอรี่ /home และ /var/spool/mail มาไว้ในไดเรกทอรี่ $MIGRATEDIRd.rollback-groupuser เอาไว้สั่งทำย้อนกลับที่เครื่องใหม่เช่นกัน$MIGRATEDIR ไปยังเครื่องใหม่หลังจากนั้น เราก็สั่งรันสคริปต์ d.import-groupuser ที่เครื่องใหม่ได้เลย
*** ใช้ด้วยความระมัดระวัง ***
เริ่มเลย
# vi /root/d.migrate-groupuser
#!/bin/bash
#PREREQUISITE:
# 1. INSTALL PACKAGE: openssh-client
# 2. EDIT THIS FILE, CHANGE VARIABLE "TARGETMACHINE" TO REAL TARGET
#THEN RUN AS root
MIGRATEDIR="/root/migrategroupuser" #MIGRATE DIR
UGIDLIMIT=1000 #UID&GID OF USER DATA: DEBIAN=1000, REDHAT=500
# EDIT TARGETMACHINE
TARGETMACHINE="newserver" #COPY TO THIS MACHINE
TARGETDIR="/root/importgroupuser" #COPY DATA TO THIS DIR
if [ "$1" != "OK" ]; then
PROG=`basename $0`
cat << EOF
*** DON'T PLACE THIS SCRIPT IN SYSTEM SEARCH PATH
*** USE WITH CARE
*** EDIT TARGETMACHINE VARIABLE THEN RUN AS ROOT
Move or migrate user accounts from old Linux server to a new Linux server
FROM: http://www.cyberciti.biz/faq/howto-move-migrate-user-accounts-old-to-new-server/
USE WITH CARE, PLEASE BACKUP OLD DATA, RUN AS ROOT
- COPY FILTERED /etc/{passwd,group,shadow,gshadow} TO $MIGRATEDIR WITH EXT .mig
- BACKUP /home, /var/spool/mail TO $MIGRATEDIR .tar.gz
- TRANSFER ALL FILES IN $MIGRATEDIR TO root@$TARGETMACHINE:$TARGETDIR WITH scp
USAGE:
$PROG OK
('OK' is safety argument)
DON'T FORGET TO EDIT TARGETMACHINE VARIABLES
EOF
exit 1
fi
if [ ! `which scp` ]; then
echo "Please install 'openssh-client' first."
exit 1
fi
mkdir -p $MIGRATEDIR
echo "
Copy /etc/{password,group,shadow,gshadow} to $MIGRATEDIR ..."
for i in /etc/{passwd,group,shadow,gshadow}; do
j=`basename $i`
awk -v LIMIT=$UGIDLIMIT -F: '($3>=LIMIT) && ($3!=65534)' $i > $MIGRATEDIR/$j.mig
done
echo "
gzip /home ..."
tar -zcpf $MIGRATEDIR/home.tar.gz /home/*
echo "
gzip /var/spool/mail ..."
tar -zcpf $MIGRATEDIR/mail.tar.gz /var/spool/mail/*
#------------------------------------------------
echo "
Generate import script ..."
IMPORTPROG="d.import-groupuser"
cat > $MIGRATEDIR/$IMPORTPROG << VIRTUAL_EOF
#!/bin/bash
IMPORTDIR="/root/importgroupuser" #COPY DATA TO THIS DIR
BACKUPDIR="/root/backupgroupuser" #BACKUP OLD DATA
if [ "\$1" != "OK" ]; then
PROG=\`basename \$0\`
cat << EOF
Move or migrate user accounts from old Linux server to a new Linux server
FROM: http://www.cyberciti.biz/faq/howto-move-migrate-user-accounts-old-to-new-server/
USE WITH CARE, PLEASE BACKUP OLD DATA, RUN AS ROOT
- BACKUP /etc/{passwd,group,shadow,gshadow} TO \$BACKUPDIR/etc
- BACKUP /home /var/spool/mail TO \$BACKUPDIR/tar
- ADD NEW GROUP-USER DATA FROM \$IMPORTDIR TO /etc
- ADD NEW /home AND /var/spool/mail TO /
USAGE:
\$PROG OK
('OK' is safety argument.)
EOF
exit 1
fi
if [ ! -d \$IMPORTDIR ]; then
echo "\$IMPORTDIR not exist, program aborted"
exit 1
fi
echo "
Backup old data and add migrate data to /etc/{passwd,group,shadow,gshadow} ..."
mkdir -p \$BACKUPDIR
pushd \$BACKUPDIR
ls | while read FILE; do
mv "\$FILE" "\$FILE.bak"
done
mkdir -p {etc,tar}
for i in {passwd,group,shadow,gshadow}; do
cp /etc/\$i etc
cat \$IMPORTDIR/\$i.mig >> /etc/\$i
done
echo "
Backup /home and /var/spool/mail in \$BACKUPDIR/tar ..."
tar -zcpf tar/home.tar.gz /home/*
tar -zcpf tar/mail.tar.gz /var/spool/mail/*
popd
echo "
Extract imported data in \$IMPORTDIR to /home and /var/spool/mail ..."
pushd /
echo "
gunzip \$IMPORTDIR/home.tar.gz to / ..."
tar -zxf \$IMPORTDIR/home.tar.gz
echo "
gunzip \$IMPORTDIR/mail.tar.gz to / ..."
tar -zxf \$IMPORTDIR/mail.tar.gz
popd
echo "
Import finished."
echo
echo "Please delete these files to finish the work:"
echo " \$BACKUPDIR"
echo " \$IMPORTDIR/d.import-groupuser"
echo " \$IMPORTDIR/d.rollback-groupuser"
echo
VIRTUAL_EOF
chmod 700 $MIGRATEDIR/$IMPORTPROG
#------------------------------------------------
echo "Generate rollback script ..."
ROLLBACKPROG="d.rollback-groupuser"
cat > $MIGRATEDIR/$ROLLBACKPROG << VIRTUAL_EOF
#!/bin/bash
BACKUPDIR="/root/backupgroupuser" #BACKUP OLD DATA
if [ "\$1" != "OK" ]; then
PROG=\`basename $0\`
cat << EOF
Move or migrate user accounts from old Linux server to a new Linux server
FROM: http://www.cyberciti.biz/faq/howto-move-migrate-user-accounts-old-to-new-server/
USE WITH CARE, PLEASE BACKUP OLD DATA, RUN AS ROOT, NO WARNING
- COPY BACKUP DATA IN \$BACKUPDIR/etc TO /etc
- *** REMOVE OLD /home AND /var/spool/mail ***
- COPY BACKUP DATA IN \$BACKUPDIR/tar TO /home and /var/spool/mail
- ROLLBACK \$BACKUPDIR/*.bak
USAGE:
\$PROG OK
('OK' is safety argument.)
EOF
exit 1
fi
if [ ! -d \$BACKUPDIR/etc ]; then
echo "\$BACKUPDIR/etc not exist, program aborted"
exit 1
fi
if [ ! -d \$BACKUPDIR/tar ]; then
echo "\$BACKUPDIR/tar not exist, program aborted"
exit 1
fi
echo "
Copy \$BACKUPDIR/etc to /etc ..."
pushd \$BACKUPDIR
cp etc/* /etc
popd
echo "
*** Remove /home and /var/spool/mail *** ..."
rm -rf /home /var/spool/mail
echo "
Copy \$BACKUPDIR/tar to /home and /var/spool/mail ..."
pushd /
tar -zxf \$BACKUPDIR/tar/home.tar.gz
tar -zxf \$BACKUPDIR/tar/mail.tar.gz
popd
echo "
Remove last rollback data ..."
pushd \$BACKUPDIR
rm -rf etc tar
for i in *.bak; do
mv \$i \${i%.bak}
done
echo "
Rollback finished."
VIRTUAL_EOF
chmod 700 $MIGRATEDIR/$ROLLBACKPROG
#------------------------------------------------
echo "
Transfer data to root@$TARGETMACHINE:$TARGETDIR, enter $TARGETMACHINE root password:"
echo "COMMAND RUN: scp -r $MIGRATEDIR root@$TARGETMACHINE:$TARGETDIR"
scp -r $MIGRATEDIR/* root@$TARGETMACHINE:$TARGETDIR
echo "
Finished.
Next, run $MIGRATEDIR/$IMPORTPROG at $TARGETMACHINE
(*** Use $ROLLBACKPROG to undo the job, BUT DO USE WITH CARE ***)
"
เปลี่ยนสถานะให้รันได้
# chmod 700 /root/d.migrate-groupuser
ทดลองรัน
เริ่มที่เครื่องเก่า
# /root/d.migrate-groupuser OK
Copy /etc/{password,group,shadow,gshadow} to /root/migrategroupuser ...
gzip /home ...
tar: Removing leading `/' from member names
gzip /var/spool/mail ...
tar: Removing leading `/' from member names
Generate import script ...
Generate rollback script ...
Transfer data to root@newserver:/root/importgroupuser, enter newserver root password:
COMMAND RUN: scp -r /root/migrategroupuser root@newserver:/root/importgroupuser
root@newserver's password: <<<---NEWSERVER_ROOT_PASSWORD
d.import-groupuser 100% 1668 1.6KB/s 00:00
d.rollback-groupuser 100% 1291 1.3KB/s 00:00
group.mig 100% 90 0.1KB/s 00:00
gshadow.mig 100% 0 0.0KB/s 00:00
home.tar.gz 100% 61MB 10.2MB/s 00:06
mail.tar.gz 100% 10KB 10.4KB/s 00:00
passwd.mig 100% 1287 1.3KB/s 00:00
shadow.mig 100% 2937 2.9KB/s 00:00
Finished.
Next, run /root/migrategroupuser/d.import-groupuser at newserver
(*** Use d.rollback-groupuser to undo the job, BUT DO USE WITH CARE ***)
ย้ายไปทำที่เครื่อง newserver
# /root/importgroupuser/d.import-groupuser OK
Backup old data and add migrate data to /etc/{passwd,group,shadow,gshadow} ...
~/backupgroupuser ~
Backup /home and /var/spool/mail in /root/backupgroupuser/tar ...
tar: Removing leading `/' from member names
tar: Removing leading `/' from member names
~
Extract imported data in /root/importgroupuser to /home and /var/spool/mail ...
/ ~
gunzip /root/importgroupuser/home.tar.gz to / ...
gunzip /root/importgroupuser/mail.tar.gz to / ...
~
Import finished.
Please delete these files to finish the work:
/root/backupgroupuser
/root/importgroupuser/d.import-groupuser
/root/importgroupuser/d.rollback-groupuser
เสร็จแล้ว เครื่องใหม่จะมีชื่อผู้ใช้งานและข้อมูลของผู้ใช้ครบตามเครื่องเก่าทุกประการ
หากสำเร็จเป็นที่พอใจแล้ว ควรลบไฟล์ต่าง ๆ ตามคำแนะนำนะครับ
TIME_WAIT ตามความเร็วของเน็ตที่มีอยู่จริง)
# vi /usr/local/sbin/d.cron-check-apt-proxy
#!/bin/bash
TIME_WAIT='60'
function sub_wait() {
sleep $TIME_WAIT
echo `date +%F-%R-%s`
}
function update_apt_proxy() {
aptitude update
echo `date +%F-%R-%s`
}
T1=`sub_wait` &
T2=`update_apt_proxy` &
wait
if [ "$T1" \< "$T2" ]; then
echo "apt-proxy update longer than $TIME_WAIT seconds, restart apt-proxy."
/etc/init.d/apt-proxy restart
fi
# chmod 755 /usr/local/bin/d.cron-check-apt-proxy
ตั้ง crontab ให้รันทุกชั่วโมง# crontab -e
... #CHECK apt-proxy EVERY 60 MIN 0 * * * * /usr/local/sbin/d.cron-check-apt-proxy ...เสร็จแล้ว ลองใช้ดูก่อน แล้วจะรายงานผลต่อไปตรับ update
สคริปต์คัดลอกผู้ใช้จากระบบปัจจุบันไปยังไดเรคทอรี่ที่ติดตั้งลินุกซ์อีกตัวหนึ่ง
มีประโยชน์สำหรับติดตั้งลินุกซ์หลายตัว และต้องการให้ผู้ใช้เหมือนกับระบบปัจจุบัน ตัวอย่างการใช้งานเช่น
ข้อกำหนดคือ
/etc/passwd, /etc/group และ /etc/shadowสคริปต์มีดังนี้
$ sudo vi /usr/local/sbin/transfer_users.sh
#!/bin/bash
function usage() {
cat <<EOF
Usage: $0 DESTINATION
Transfer users from current linux system to DESTINATION directory that have another linux system.
Run as root.
EOF
exit 1
}
DEST=$1
if [ ! "$UID" == "0" ]; then
echo "Please run as root."
usage
fi
if [ ! -d "$DEST" ]; then
echo "DESTINATION directory not found."
usage
fi
if [ ! -d "$DEST/etc" ]; then
echo "DESTINATION/etc directory not found."
usage
fi
US=`ls /home`
TMP=/tmp/${RANDOM}.txt
#SORT ON UID
for i in $US; do
UUID=`grep ":/home/${i}:" /etc/passwd | cut -d: -f3`
echo "${UUID}:${i}" >> $TMP
done
#PROCESS EACH USER
for i in `cat $TMP | sort`; do
UUID=`echo $i | cut -d: -f1`
U=`echo $i | cut -d: -f2`
PASSWDLINE=`grep ":/home/${U}:" /etc/passwd`
#/etc/group
GNUM=`echo $PASSWDLINE | cut -d: -f4`
GROUPLINE=`grep $GNUM /etc/group`
GNAME=`echo $GROUPLINE | cut -d: -f1`
OLDGROUP=`grep $GNAME $DEST/etc/group`
if [ "$OLDGROUP" == "" ]; then
echo $GROUPLINE >> $DEST/etc/group
elif [ "$OLDGROUP" != "$GROUPLINE" ]; then
sed -i "s/$OLDGROUP/$GROUPLINE/g" $DEST/etc/group
fi
#/etc/passwd
OLDPASS=`grep ":/home/${U}:" $DEST/etc/passwd`
if [ "$OLDPASS" == "" ]; then
echo $PASSWDLINE >> $DEST/etc/passwd
elif [ "$OLDPASS" != "$PASSWDLINE" ]; then
sed -i "s/$OLDPASS/$PASSWDLINE/g" $DEST/etc/passwd
fi
#/etc/shadow
SHADOWLINE=`grep "${U}:" /etc/shadow | grep -v '*'`
OLDSHADOW=`grep "${U}:" $DEST/etc/shadow | grep -v '*'`
if [ "$OLDSHADOW" == "" ]; then
echo $SHADOWLINE >> $DEST/etc/shadow
elif [ "$OLDSHADOW" != "$SHADOWLINE" ]; then
sed -i "s#$OLDSHADOW#$SHADOWLINE#g" $DEST/etc/shadow
fi
#GROUP MEMBERS
for j in `groups $U | cut -d: -f2`; do
for k in `echo $j`; do
if [ "$k" == "$GNAME" ]; then
continue
fi
OLDLINE=`grep "${k}:" $DEST/etc/group`
if ! echo $OLDLINE | grep $U ; then
if [ "${OLDLINE: -1}" == ":" ]; then
sed -i "s/$OLDLINE/${OLDLINE}${U}/g" $DEST/etc/group
else
sed -i "s/$OLDLINE/${OLDLINE},${U}/g" $DEST/etc/group
fi
fi
done
done
#/HOME
if [ ! -d "$DEST/home/$U" ]; then
mkdir -p "$DEST/home/$U"
fi
chown -R ${U}:${GNAME} $DEST/home/$U
done
#for i in passwd group shadow; do
# cp $DEST/etc/$i $DEST/etc/${i}-
#done
rm $TMP
ตัวอย่างเช่น เราติดตั้งลินุกซ์อีกอันไว้ที่ /dev/sdaXX
$ sudo mount /dev/sdaXX /mnt/tmp $ sudo /usr/local/sbin/transfer_users.sh /mnt/tmp
ผู้ใช้ทั้งหมดใน /home/* จะถูกคัดลอกไปยัง /mnt/tmp/home/ ตามต้องการ
ทำสคริปต์บล๊อกผู้ใช้ Drupal จากบรรทัดคำสั่ง (bash)
$ vi drupal_blockuser.sh
#!/bin/bash
#FROM: https://drupal.org/node/118759
if [ "$#" == "0" ]; then
echo "Script to block drupal users by uid"
echo "Usage: $0 UID1 UID2 UID3 ..."
exit 1
fi
SITE=http://www.example.com #NO END SLASH
NAME=drupal_admin_user
PASS=drupal_admin_password
STATUS=0 #0:block,1:unblock
OPLOGIN="Log%20in"
OPSUBMIT="Save"
TMP=/tmp/$RANDOM.txt
COOKIES=/tmp/$RANDOM.txt
#LOGIN
wget -q -o /dev/null -O /dev/null \
--keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
--post-data="name=${NAME}&pass=${PASS}&op=${OPLOGIN}&form_id=user_login" \
"${SITE}/?q=user/login"
#DO BLOCK
while (( "$#" )); do
ID=$1
wget -q -o /dev/null -O $TMP \
--keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
"${SITE}/?q=user/${ID}/edit"
let "LINE=`grep -n 'value="user_profile_form"' $TMP | cut -d: -f1`-1"
TOKEN=`sed -n -e "${LINE}p" $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
USER1=`grep 'id="edit-name"' $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
EMAIL=`grep 'id="edit-mail"' $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
wget -q -o /dev/null -O /dev/null \
--keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
--post-data="status=${STATUS}&op=${OPSUBMIT}&name=${USER1}&mail=${EMAIL}&form_token=${TOKEN}&form_id=user_profile_form" \
"${SITE}/?q=user/${ID}/edit"
echo "USER:\"$USER1\" --- EMAIL:\"$EMAIL\" --- Blocked."
shift
done
rm $TMP
rm $COOKIES
$ chmod 700 ./drupal_blockuser.sh
วิธีใช้งานก็สั่ง
$ ./drupal_blockuser.sh UID1 UID2 UID3 ...
อย่าลืมแก้ตัวแปร SITE, NAME, PASS ให้เข้ากับงานเรา
ปรับปรุงสคริปต์ให้สามารถบล๊อกโดยอัตโนมัติ
$ vi drupal_auto_blockuser.sh
#!/bin/bash
# AUTO BLOCK DRUPAL USER SCRIPT, CHECK SPAM FROM google.co.th
# FROM: https://drupal.org/node/118759
SITE=http://www.example.com # NO TRAILING SLASH
NAME=drupal_admin_user
PASS=drupal_admin_password
UIDFILE="site_last_uid.txt"
SPAMDATA="spamdata.txt"
SLEEP=10 #REDUCE SERVER LOAD
STATUS=0 #0:block,1:unblock
OPLOGIN="Log%20in"
OPSUBMIT="Save"
TMP=/tmp/$RANDOM.txt
COOKIES=/tmp/$RANDOM.txt
GGCOOKIES=/tmp/$RANDOM.txt
AGENT="Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20130712 Firefox/25.0"
QURL="https://www.google.co.th/search?q="
# LOGIN
wget -q -o /dev/null -O /dev/null \
--keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
--post-data="name=${NAME}&pass=${PASS}&op=${OPLOGIN}&form_id=user_login" \
"${SITE}/?q=user/login"
# PREVIOUS USER ID
PREVUID=`cat $UIDFILE`
let "STARTUID=${PREVUID}+1"
# GET LAST USER ID
wget -q -o /dev/null -O $TMP \
--keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
"${SITE}/?q=admin/user/user"
LASTUID=`grep -m1 'class="form-item" id="edit-accounts-' $TMP | awk -F'class="form-item" id="edit-accounts-' '{ print $2 }' | cut -d\- -f1`
#DO CHECK
for ID in `eval echo {$STARTUID..$LASTUID}`; do
wget -q -o /dev/null -O $TMP \
--keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
"${SITE}/?q=user/${ID}/edit"
LINE0=`grep -n 'value="user_profile_form"' $TMP | cut -d: -f1`
# PREVENT BLOCKED USER ERROR
if ! [ "$LINE0" ]; then
continue
fi
let "LINE=${LINE0}-1"
TOKEN=`sed -n -e "${LINE}p" $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
USER1=`grep 'id="edit-name"' $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
EMAIL=`grep 'id="edit-mail"' $TMP | awk -F'value="' '{ print $2 }' | cut -d\" -f1`
# QUERY FOR SPAM
wget -q -o /dev/null -O $TMP \
--keep-session-cookies --save-cookies $GGCOOKIES --load-cookies $GGCOOKIES \
-U "$AGENT" "${QURL}${EMAIL}"
ISSPAM=0
KEYWORD=""
#***********************
### BEWARE THIS WHILE LOOP, VARIABLE $ISSPAM IS LOCAL, USE REDIRECT INSTEAD OF PIPE ###
#cat $SPAMDATA | while read TXT; do
# if [ "$TXT" ]; then
# if grep -m1 "$TXT" $TMP > /dev/null; then
# ISSPAM=1
# break
# fi
# fi
#done
#***********************
while read TXT; do
if [ "$TXT" ]; then
if grep -m1 "$TXT" $TMP > /dev/null; then
ISSPAM=1
KEYWORD=$TXT
break
fi
fi
done < <(cat $SPAMDATA )
if [ "$ISSPAM" != "0" ]; then
#DO BLOCK
wget -q -o /dev/null -O /dev/null \
--keep-session-cookies --save-cookies $COOKIES --load-cookies $COOKIES \
--post-data="status=${STATUS}&op=${OPSUBMIT}&name=${USER1}&mail=${EMAIL}&form_token=${TOKEN}&form_id=user_profile_form" \
"${SITE}/?q=user/${ID}/edit"
echo "UID:\"$ID\", USER:\"$USER1\", EMAIL:\"$EMAIL\", KEYWORD:\"$KEYWORD\" --- Blocked."
sleep $SLEEP
else
echo "--- UID:\"$ID\", USER:\"$USER1\", EMAIL:\"$EMAIL\", not found as spam."
fi
sleep $SLEEP
done
# SAVE LAST UID
echo $LASTUID > $UIDFILE
rm $TMP
rm $COOKIES
rm $GGCOOKIES
$ chmod 700 drupal_auto_blockuser.sh
ใส่ค่า spam keyword
$ vi spamdata.txt
did not match any documents Spam ไม่ตรงกับเอกสารใด
ใส่ค่า uid ของผู้ใช้คนสุดท้ายที่ไม่ต้องการตรวจ (สมมุติว่าเป็นผู้ใช้คนที่ 1000)
$ vi site_last_uid.txt
1000
$ ./drupal_auto_blockuser.sh
ครั้งต่อไปก็แค่สั่งรัน โดยไม่ต้องปรับแต่งอะไรอีก เว้นแต่มี spam keyword เพิ่มเติมก็ไปแก้ไฟล์ spamdata.txt
ลองใช้สคริปต์กับพาร์ติชั่นที่ขนาดไม่เท่ากันแล้วปรากฎว่าใช้ไม่ได้ เพราะ NTFS เก็บข้อมูลหลายอย่างมากกว่าแค่จุดเริ่มต้นและขนาด (ดูที่ NTFS Partition Boot Sector)
วิธีที่ได้ผลกว่าคือ ฟอร์แมตไดร์ฟไว้ก่อน -> เก็บ boot sector ไว้ 72 ไบต์ -> ทำ ntfsclone -> เอา boot recort ที่เก็บไว้มาเขียนทับ
ตัวอย่างเช่น จะคัดลอก ไดร์ฟ /dev/sda1 ไปยัง /dev/sdb1 ขั้นตอนจะเป็นดังนี้
$ sudo mkfs.ntfs -f /dev/sdb1 $ sudo dd if=/dev/sdb1 of=sdb1.img bs=72 count=1 $ sudo ntfsclone -O /dev/sdb1 /dev/sda1 $ sudo dd if=sdb1.img of=/dev/sdb1
*** สคริปต์ด้านล่างนี้ ล้าสมัยแล้ว ***
แก้ปัญหาเวลาใช้ ntfsclone ในการ restore พาร์ติชั่น NTFS มาลงในฮาร์ดดิสก์ลูกใหม่ ซึ่งจุดเริ่มต้นและขนาดอาจไม่เท่าของเดิม
วิธีการคือใช้ข้อมูลจากตาราง Master Boot Record ปัจจุบัน มาเขียนทับ boot record ของพาร์ติชั่น NTFS ที่ต้องการ โดยใช้เชลล์สคริปต์
$ vi ntfs_fix_boot_sector.sh
#!/bin/bash
function usage() {
cat <<EOF
Fix NTFS boot record:
Usage: $0 DEVICE
Example: $0 /dev/sda1
EOF
exit 1
}
if [ ! "$1" ]; then
usage
fi
PART=$1
if [ ! -b "$PART" ]; then
echo -e "$PART not found. Exit.\n"
usage
fi
BSF="`echo $PART | tr '/' '_'`.img"
BSFB=${BSF}.bak
BSFD="${BSF}_`date +%F`.bak"
function reverse_byte () { #reverse_byte HEXSTR
local S=$1 #HEXSTR
local B
local C
while [ "$S" ]; do
B=${S:(-2)}
S=${S:0:-2}
C="${C}\\x${B}"
done
echo $C
}
function replace_byte () { #replace_byte OFFSET LENGTH NUMBER
local O=$1 #OFFSET
local L=$2 #LENGTH
local N=$3 #NUMBER
let NL=${L}*2
XN=`printf "%0${NL}x" $N`
RXN=`reverse_byte $XN`
#echo "printf $RXN | dd of=$BSF bs=1 seek=$O count=$L conv=notrunc"
printf $RXN | sudo dd of=$BSF bs=1 seek=$O count=$L conv=notrunc > /dev/null 2>&1
}
sudo dd if=$PART of=$BSF bs=512 count=1 > /dev/null 2>&1
TMP="/tmp/$0_${RANDOM}.txt"
HDD=$PART
while [ "`echo ${HDD:(-1)} | tr '0123456789' ' '`" == " " ]; do
HDD=${HDD:0:-1}
done
sudo fdisk -l $HDD > $TMP
if ! cat $TMP | grep $PART | grep NTFS > /dev/null 2>&1; then
echo -e "$PART is not NTFS partition. Exit.\n"
sudo rm $TMP
sudo rm $BSF
usage
fi
if [ ! -f "$BSFB" ]; then
cp $BSF $BSFB
else
cp $BSF $BSFD
fi
echo "Fixing $PART ..."
HEADS=`cat $TMP | grep 'sectors/track' | cut -d, -f1 | cut -d\ -f1`
SECTORS=`cat $TMP | grep 'sectors/track' | cut -d, -f2 | cut -d\ -f2`
START=`cat $TMP | grep "${PART} " | awk -F' ' '{ print $2 }'`
END=`cat $TMP | grep "${PART} " | awk -F' ' '{ print $4 }'`
if [ "$START" == "*" ]; then
START=`cat $TMP | grep "${PART} " | awk -F' ' '{ print $3 }'`
END=`cat $TMP | grep "${PART} " | awk -F' ' '{ print $4 }'`
fi
let LENGTH=$END-$START
OFFSET_HEADS=26
OFFSET_SECTORS=24
OFFSET_START=28
OFFSET_LENGTH=40
LEN_HEADS=1
LEN_SECTORS=1
LEN_START=4
LEN_LENGTH=4
for i in HEADS SECTORS START LENGTH; do
A="OFFSET_${i}"
B="LEN_${i}"
replace_byte ${!A} ${!B} ${!i}
done
echo "Fix with heads=$HEADS, sectors/track=$SECTORS, start=$START, length=$LENGTH"
sudo dd if=$BSF of=$PART > /dev/null 2>&1
sudo rm $TMP
cat <<EOF
Command used:
sudo dd if=$BSF of=$PART
Revert with command:
sudo dd if=$BSFB of=$PART
EOF
ตัวอย่าง สมมุติว่าพาร์ติชั่นที่ต้องการเป็น /dev/sda1 คำสั่งคือ
$ ntfs_fix_boot_record.sh /dev/sda1
ได้ผลลัพธ์คือ
Fixing /dev/sda1 ... Fix with heads=255, sectors/track=63, start=2048, length=62914559 Command used: sudo dd if=_dev_sda1.img of=/dev/sda1 Revert with command: sudo dd if=_dev_sda1.img.bak of=/dev/sda1
bash: เกร็ดคำสั่ง find
(ศึกษาเพราะต้องการเอาไฟล์ในคลังของ apt-proxy เฉพาะไฟล์ใหม่ ๆ เลยต้องการลบไฟล์เก่า ๆ ทิ้ง เพื่อให้ขนาดคลังแพ็กเกจเล็กลง)
เริ่มเลยครับ
ต้องการค้นหาไฟล์ชื่อ *Doc*
$ find /PATH/TO/FILE -name '*Doc*'
ค้นหาและลบไฟล์
$ find /PATH/TO/FILE -name '*Doc*' -exec rm {} \;
ค้นหาไฟล์ที่เก่ากว่า 5 วันลงไป
$ find /PATH/TO/FILE -mtime +5
ค้นหาไฟล์เก่าตั้งแต่ 5 วันขึ้นมา
$ find /PATH/TO/FILE -mtime -5
ค้นหาไฟล์เก่ากว่า 1 ปีลงไป และลบไฟล์เหล่านั้นทิ้ง
$ find /PATH/TO/FILE -mtime +365 -exec rm {} \;
ค้นหาไฟล์ที่มีขนาด 0 byte และลบไฟล์
$ find /PATH/TO/FILE -type f -size 0 -exec rm {} \;
*** ใช้ด้วยความระมัดระวังนะครับ ***
วันนี้แค่นี้ก่อนครับ
อ้างอิง
ขออนุญาตเขียนแบบกองโจรนะครับ โดยมือใหม่ เพื่อมือใหม่ครับ
เอามาจาก tldp: BASH Programming - Introduction HOW-TO
ศึกษาเพิ่มเติมได้จาก tldp: Bash Guide for Beginners
และในเชิงลึก จาก tldp: Advanced Bash-Scripting Guide
เชลล์สคริปต์ พูดง่าย ๆ ก็คือการนำคำสั่งในเชลล์ของลินุกซ์มาเรียงต่อกันให้ทำงานตามที่เราต้องการ โดยเพิ่มโครงสร้างการวนรอบ และฟังก์ชั่นต่าง ๆ เติมเข้ามา เพื่อให้การทำงานได้ตามที่เราต้องการ ซึ่งจะเหมาะมากกับงานแบบ batch หรืองานแบบ schedule
ฉะนั้นการที่จะเขียนโค๊ดให้ได้ดี จึงต้องศึกษาจดจำคำสั่งต่าง ๆ ของเชลล์ให้ได้เท่าที่เราต้องการใช้งาน (จำหมดคงไม่ไหว)
คำสั่งต่าง ๆ สามารถดูได้ที่ gnu.org: Bash Reference Manual
สำหรับเดเบียน หากต้องการใช้งาน bash แบบเต็มรูป (ไม่อั้นความสามารถ) อาจต้องปรับแต่งเล็กน้อย
เปลี่ยนให้เชลล์ของเราเป็น bash แทน sh ใช้คำสั่ง
$ chsh -s /bin/bash
สำหรับเอดิเตอร์ ถ้าใช้ vi ควรติดตั้ง vim-full และอย่าลืมแก้ไขไฟล์ vimrc ให้แสดงสีด้วย เพื่อให้ดูโค๊ดได้ง่ายขึ้น
$ sudo aptitude install vim-full $ vi ~/.vimrc
syntax on
:wq
สมมุติตั้งชื่อสคริปต์ว่า hello.sh
$ vi hello.sh
#!/bin/bash echo Hello World
:wq
อย่าลืมเปลี่ยนสถานะเพื่อให้สคริปต์สามารถรันได้
$ chmod 755 hello.sh
เริ่มรัน
$ ./hello.sh Hello World
เรียบร้อยแล้ว
บรรทัดแรก เรียกว่า hash-bang เป็นการบอกให้เชลล์รู้ว่า โค๊ดที่เราเขียนนี้จะถูกประมวลผลด้วยโปรแกรมอะไร ในที่นี้คือ /bin/bash
บรรทัดที่สอง เป็นการสั่งให้พิมพ์ Hello World ออกทางจอภาพ
จากตัวอย่างข้างบน ผมเขียนอธิบายโดยละเอียดโดยใช้เอดิเตอร์ vi แต่เพื่อให้กระชับเข้า จะขอละเลยการใช้เอดิเตอร์ โดยจะเขียนเฉพาะโค๊ดอย่างเดียวครับ
#!/bin/bash tar -cvzf /tmp/my-backup.tgz /home/USER/
บรรทัดที่สองให้เปลี่ยนคำว่า USER เป็นชื่อเรา
เป็นการสั่งให้ใช้คำสั่ง tar ทำการสำรองข้อมูลพร้อมบีบอัดข้อมูลในไดเรคทอรี่ของบ้านเราไปสู่ไฟล์ชื่อ /tmp/my-backup.tgz
ใช้สัญญลักษณ์ > ใสการเปลี่ยนทิศ
ข้อมูลมาตรฐานในเชลล์จะมีอยู่ 4 ชนิด คือข้อมูลเข้า(stdin), ข้อมูลแสดงผล(stdout), ข้อมูลข้อผิดพลาด(stderr), และแฟ้มข้อมูล(file)
ในทางปฏิบัติ เราสามารถเปลี่ยนทิศทางของข้อมูลเหล่านี้ไปมาได้ โดยมีมาตรฐานคือ 1 จะหมายถึงข้อมูลแสดงผล(stdout) และ 2 จะหมายถึงข้อมูลความผิดพลาด(stderr)
เช่น
$ ls -l > ls-l.txt
จะเปลี่ยนการแสดงผลของคำสั่ง ls -l ไปเก็บไว้ที่ไฟล์ชื่อ ls-l.txt ดังนั้นคำสั่งตามตัวอย่างนี้จะไม่แสดงอะไรออกมาทางจอภาพ แต่จะเก็บไว้ที่ไฟล์แทน หากเราต้องการดูผล สามารถใช้คำสั่งแสดงผลของไฟล์ได้คือ
$ cat ls-l.txt
$ grep da * 2> grep-errors.txt
ตัวอย่างนี้เป็นการค้นหาข้อความ da ในทุกไฟล์ (*) และหากเกิดข้อผิดพลาดขึ้น จะนำข้อความผิดพลาดไปเก็บไว้ที่ไฟล์ชื่อ grep-errors.txt
$ grep da * 1>&2
เป็นการค้นหาข้อความ da ในทุกไฟล์ (*) โดยนำการแสดงผลไปใส่ไว้ใน stderr แทนการแสดงผลปกติ แต่ในกรณีนี้เราป้อนคำสั่งทางแป้นพิมพ์ stdout และ stderr คือจอภาพเหมือนกัน จึงไม่เห็นความแตกต่าง แต่หากคำสั่งนี้ไปอยู่ในสคริปต์ที่เรากำหนดให้ stderr เป็นไฟล์ error-log การแสดงผลก็จะถูกเปลี่ยนทิศไปตามนั้น
$ grep da * 2>&1
เป็นการค้นหาข้อความ da ในทุกไฟล์ (*) โดยหากเกิดข้อผิดพลาดขึ้น จะแสดงผลข้อผิดพลาดออกมาทาง stdout ซึ่งในที่นี้คือจอภาพเหมือนกัน
$ rm -f $(find /home/USER -name core) &> /dev/null
คำสั่งนี้เป็นการค้นหาไฟล์ในไดเรคทอรี่ /home/USER ที่มีชื่อว่า core (find /home/USER -name core)
เมื่อพบแล้วก็จัดการลบทิ้งโดยไม่เตือน (rm -f)
โดยโยกการแสดงผลทั้งหมด (ทั้ง stderr และ stdout - ใช้สัญญลักษณ์ &>) ไปยังไฟล์ชื่อ /dev/null ซึ่งเป็นไฟล์พิเศษ หมายความว่ายกเลิกการแสดงผลทั้งหมด
(คำสั่งนี้ค่อนข้างอันตราย เพราะลบโดยไม่เตือน โปรดทดลองด้วยความระมัดระวังครับ)
ไปป์เป็นการส่งต่อผลลัพธ์จากคำสั่งหนึ่งไปเป็นค่านำเข้าของอีกคำสั่งหนึ่ง
$ ls -l | sed -e "s/[aeio]/u/g"
ตัวอย่างนี้จะนำเอาผลลัพธ์ที่ได้จากคำสั่ง ls -l ส่งต่อไปให้คำสั่ง sed -e "s/[aeio]/u/g"
ซึ่งจะแปลงการแสดงผลจากอักขระ a หรือ e หรือ i หรือ o ไปเป็นอักขระ u ทั้งหมด
เราอาจเขียนคำสั่งเทียบเท่าได้ดังนี้
$ ls -l > temp.txt $ sed -e "s/[aeio]/u/g" temp.txt $ rm temp.txt
จะเห็นว่าการทำไปป์ ลดขั้นตอนไปมาก คงเหลือเพียงบรรทัดเดียว
$ ls -l | grep "\.txt$"
ตัวอย่างนี้จะส่งผลลัพธ์จากคำสั่ง ls -l ต่อไปให้คำสั่ง grep "\.txt$" คือให้แสดงเฉพาะไฟล์ที่มีนามสกุลเป็น .txt เท่านั้น
มีค่าเท่ากับคำสั่ง ls แบบใส่พารามิเตอร์กรอง
$ ls -l *.txt
หมายเหตุ
รูปแบบ "\.txt$" เป็นรูปแบบของ Regular Expression ซึ่งใช้มากในเชลล์สคริปต์ มีความหมายว่า "ที่ต้องลงท้ายด้วย .txt"
ตัวแปรในเชลล์สคริปต์ ไม่มีชนิดข้อมูล คือเราสามารถใช้ตัวแปรแทนตัวเลขหรืออักขระใด ๆ ก็ได้
โดยในขั้นตอนกำหนดค่า ไม่ต้องใช้เครื่องหมายใด ๆ นำหน้า แต่ตอนอ้างถึง ต้องใช้เครื่องหมาย $ นำหน้าตัวแปร
#!/bin/bash STR="Hello World!" echo $STR
ให้ผลลัพธ์เหมือนตัวอย่างที่ 2.1
ข้อควรระวังคือ
=$ จะหมายถึงการแสดงผลข้อความว่า STR เฉย ๆ#!/bin/bash OF=/tmp/my-backup-$(date +%Y%m%d).tgz tar -cvzf $OF /home/USER/
ให้ผลลัพธ์คล้ายตัวอย่าง 2.2 แต่เพิ่มการใช้ตัวแปรลอยในคำสั่ง $(date +%Y%m%d) ซึ่งมีผลทำให้ชื่อไฟล์ข้อมูลสำรองมีวันที่ต่อท้ายชื่อด้วย
ตัวแปรในเชลล์สคริปต์ทุกตัว จะเป็นตัวแปรรวม (Global) คือทุก ๆ ส่วนของโปรแกรมจะเห็นเหมือนกันหมด
แต่ในกรณีที่เราต้องการให้เห็นเฉพาะในฟังก์ชั่นที่เราต้องการ เราสามารถกำหนดให้ตัวแปรเป็นตัวแปรท้องถิ่นได้ด้วยคำสั่ง local
เช่น
#!/bin/bash
HELLO=Hello
function hello {
local HELLO=World
echo $HELLO
}
echo $HELLO
hello
echo $HELLO
สคริปต์นี้ตัวแปร HELLO ในโปรแกรมหลัก กับในฟังก์ชั่นจะเป็นตัวแปรคนละตัวกัน
มีรูปแบบคือ
if [EXPRESSION]; then
CODE IF 'EXPRESSION' IS TRUE.
[elif [EXPRESSION-ELIF]; then
CODE IF 'EXPRESSION-ELIF' IS TRUE.]
[else
CODE IF NOTHING IS TRUE.]
fiif ... then#!/bin/bash
if [ "foo" = "foo" ]; then
echo expression evaluated as true
fi
โค๊ดนี้จะเป็นจริงเสมอ ดังนั้นข้อความ "expression evaluated as true" จะถูกพิมพ์ออกมาเสมอ
if ... then ... else#!/bin/bash if [ "foo" = "foo" ]; then echo expression evaluated as true else echo expression evaluated as false fi
โค๊ดนี้จะเป็นจริงเสมอ ดังนั้นข้อความ "expression evaluated as true" จะถูกพิมพ์ออกมาเสมอ
#!/bin/bash
T1="foo"
T2="bar"
if [ "$T1" = "$T2" ]; then
echo expression evaluated as true
else
echo expression evaluated as false
fi
ตัวอย่างนี้จะเป็นเท็จเสมอ
สังเกตุการใช้ตัวแปรในการเปรียบเทียบ ควรให้ตัวแปรอยู่ในเครื่องหมายคำพูดเสมอ เพื่อป้องการการผิดพลาดจากการแทนค่าที่ซับซ้อน หรือการที่มีช่องว่างในค่าตัวแปร
คำสั่ง for มีลักษณะคล้าย for ในภาษาไพธอน มีรูปแบบเป็น
for VAR in SCOPE; do
COMMAND
doneคำสั่ง while มีรูปแบบเป็น
while [CONDITION]; do
COMMAND
doneถ้าเงื่อนไข CONDITION เป็นจริง ก็จะทำคำสั่ง COMMAND คำสั่ง until รูปแบบตรงกันข้ามกับ while โดยมีรูปแบบเป็น
until [CONDITION]; do
COMMAND
doneคือจะทำคำสั่ง COMMAND จนกว่าเงื่อนไข CONDITION จะเป็นจริง
for
#!/bin/bash
for i in $( ls ); do
echo item: $i
doneเป็นการนำคำสั่ง ls ไปเป็นตัวแปรชั่วคราวในการกำหนดขอบเขตให้กับตัวแปร i ในคำสั่ง for ในที่นี้จะทำการแสดงผลว่า item: FILENAME ...
for อีกแบบ
#!/bin/bash
for i in `seq 1 10`; do
echo $i
doneเป็นการนำผลจากคำสั่ง seq 1 10 ไปกำหนดขอบเขตให้กับตัวแปร i ในคำสั่ง for อาจเขียนเลียนแบบตัวอย่าง 7.1 ได้เหมือนกันดังนี้
#!/bin/bash
for i in $( seq 1 10 ); do
echo $i
donewhile
#!/bin/bash
COUNTER=0
while [ $COUNTER -lt 10 ]; do
echo The counter is $COUNTER
let COUNTER=COUNTER+1
doneเป็นการแสดงค่าตัวแปร COUNTER ที่เพิ่มขึ้นทีละ 1 จาก 0 ถึง 9 โปรดสังเกตุการใช้ตัวแปรเก็บค่าตัวเลข, การเปรียบเทียบตัวเลขโดยใช้ตัวเปรียบเทียบ -lt (less than) และการกำหนดเพิ่มค่าให้กับตัวแปรแบบตัวเลขโดยใช้คำสั่ง let
until
#!/bin/bash
COUNTER=20
until [ $COUNTER -lt 10 ]; do
echo COUNTER $COUNTER
let COUNTER-=1
doneจะแสดงตัวเลขตั้งแต่ 20 ลดลงทีละ 1 จนถึง 10
ในการใช้งานเชลล์สคริปต์แบบจริงจัง เราจำเป็นต้องเขียนฟังก์ชั่นเพื่อประโยชน์ในการเรียกใช้งานแบบซ้ำ ๆ เพื่อให้ประหยัดการเขียนโค๊ด และให้โค๊ดดูง่าย
มีรูปแบบเป็น
function FUNCTION_NAME {
COMMAND
}หรือ
FUNCTION_NAME () {
COMMAND
}โปรแกรมจะเว้นไม่ถูกเรียกทำงานในช่วงตั้งแต่ชื่อฟังก์ชั่นจนกระทั่งจบบล๊อก { COMMAND }
เรานิยมวางฟังก์ชั่นไว้ที่ต้นโปรแกรม เพื่อให้สามารถถูกเรียกจากโค๊ดหลักได้
#!/bin/bash
function quit {
exit
}
function hello {
echo Hello!
}
hello
quit
echo fooตัวอย่างนี้ บรรทัดที่ 10 คือคำสั่ง echo foo จะไม่ถูกเรียกใช้ เนื่องจากโปรแกรมจะหลุดสู่เชลล์ในบรรทัดที่ 9 คือคำสั่ง quit
#!/bin/bash
function quit {
exit
}
function ex {
echo $1
}
ex Hello
ex World
quit
echo fooจากตัวอย่าง จะเห็นการส่งผ่านข้อความเข้าไปในฟังก์ชั่น ex ด้วยตัวแปร $1
ในทำนองเดียวกัน ถ้ามีการส่งผ่านตัวแปรหลายตัว ก็จะใช้รูปแบบเป็น $2, $3, ...
โดยเรียกใช้งานด้วยรูปแบบ ex VAR1 VAR2 VAR3 ... ตามลำดับ
select ในการสร้างหัวข้อให้เลือก#!/bin/bash
OPTIONS="Hello Quit"
select opt in $OPTIONS; do
if [ "$opt" = "Quit" ]; then
echo done
exit
elif [ "$opt" = "Hello" ]; then
echo Hello World
else
clear
echo bad option
fi
doneตัวอย่างนี้จะสร้างหัวข้อ 1) และ 2) จากตัวแปร OPTIONS เพื่อมาให้เลือก โดยจะวนรอบถามไปเรื่อย ๆ จนกว่าจะพบคำสั่ง exit ให้ออกจากการวนรอบ
#!/bin/bash if [ -z "$1" ]; then echo usage: $0 directory exit fi SRCD=$1 TGTD="/var/backups/" OF=home-$(date +%Y%m%d).tgz tar -cZf $TGTD$OF $SRCD
บรรทัดที่ 2 จะตรวจว่ามีการใส่พารามิเตอร์ให้กับโปรแกรมหรือไม่ (if [ -z "$1" ] -z หมายถึงการตรวจสอบว่ามีค่าหรือไม่)
ถ้าไม่มีการใส่ค่าพารามิเตอร์ โปรแกรมจะทำคำสั่งในบรรทัดที่ 3 คือแสดงการใช้งาน ($0 คือชื่อโปรแกรมนี้) และบรรทัดที่ 4 คือออกจากโปรแกรม
แต่ถ้ามีการใส่ค่าพารามิเตอร์ถูกต้อง ก็จะทำบรรทัดที่ 6 ต่อไปจนจบ ซึ่งในที่นี้คือการบีบอัดทำสำเนาให้กับไดเรกทอรี่ที่เราให้เป็นพารามิเตอร์ ($1) ในชื่อไฟล์ว่า /var/backups/home-YYYYMMDD
read#!/bin/bash echo Please, enter your name read NAME echo "Hi $NAME!"
สังเกตุการใช้คำสั่ง read กำหนดค่าให้ตัวแปร NAME ไม่ต้องใช้เครื่องหมาย $ นำหน้าตัวแปร
อาจรอรับค่าทีละหลายตัวแปรได้ด้วย โดยคั่นแต่ละตัวแปรด้วยช่องว่าง
#!/bin/bash echo Please, enter your firstname and lastname read FN LN echo "Hi! $LN, $FN !"
10.1 การสั่งรันสคริปต์และคำสั่ง sourceการสั่งรันสคริปต์ในเชลล์ มีเกร็ดคือ
$ /bin/ls
$PATH โดยไม่สนใจไดเรคทอรี่ปัจจุบัน เช่น$ mycode
หากค้นไม่พบ จะแสดงข้อผิดพลาด
แต่หากต้องการสั่งรันสคริปต์ในไดเรคทอรี่ปัจจุบัน เราต้องใช้คำสั่งอ้างอิงคือ
$ ./mycode
เมื่อสคริปต์ถูกรันจนจบแล้ว ค่าของตัวแปรต่าง ๆ ในสคริปต์จะถูกลบไปด้วย ยกเว้นถ้าเราใช้คำสั่ง source หรือคำสั่ง .
เชลล์จะรันคำสั่งนั้นโดยถือเสมือนเป็นสภาพแวดล้อมเดียวกัน ดังนั้นค่าตัวแปรต่าง ๆ ในสคริปต์จะยังคงค้างอยู่ในเชลล์
โดยเมื่อใช้คำสั่งนี้แล้ว การค้นหาสคริปต์ เชลล์จะค้นหาจากตัวแปร $PATH ก่อน ตามด้วยไดเรคทอรี่ปัจจุบันด้วย
เช่น ถ้าสคริปต์ mycode มีเนื้อไฟล์เป็น
#!/bin/bash ABC="This is new ABC"
ทดลองรันได้ดังนี้
$ ABC="Old ABC" $ echo $ABC Old ABC $ ./mycode $ echo $ABC Old ABC $ . mycode $ echo $ABC This is new ABC
เราใช้ $((ARITHMATIC)) หรือ $[ARITHMATIC] ในการแทนค่าตัวแปร
ดังนี้
$ echo $(1+1) bash: 1+1: command not found $ echo 1+1 1+1 $ echo $((1+1)) 2 $ echo $[1+1] 2
บรรทัดเริ่มต้นของสคริปต์ หลังเครื่องหมาย #! (hash-bang) เราต้องใส่พาธของโปรแกรม bash ให้เต็ม
สำหรับเดเบียน อยู่ที่ /bin/bash อยู่แล้ว แต่หากเป็นดิสโตรอื่น อาจค้นหาว่าโปรแกรม bash อยู่ที่ไหน โดยใช้คำสั่งเหล่านี้
$ which bash $ whereis bash $ find / -name bash
หลายโปรแกรมของเชลล์มีการส่งค่าออกมา (Return value) อาจเพื่อแจ้งสถานะการรันว่ารันสำเร็จหรือไม่อย่างไร หรืออาจส่งออกเป็นค่าที่จะนำไปประมวลผลต่อก็ตาม เราสามารถใช้ตัวแปรพิเศษ $? ในการดูผลลัพธ์ของโปรแกรมได้
เช่น
#!/bin/bash cd /dada &> /dev/null echo rv: $? cd $(pwd) &> /dev/null echo rv: $?
กรณีนี้ ไดเรคทอรี่ /dada เป็นไดเรคทอรี่ที่เราแกล้งพิมพ์ผิดไว้ เพื่อดูว่าสคริปต์จะส่งออกค่าออกมาเป็นอย่างไร ซึ่งจะได้ผลออกมาเป็น 1 และ 0 ตามลำดับ คือ 1 หมายถึงมีข้อผิดพลาดในโปรแกรม และ 0 หมายถึงรันสำเร็จ ไม่มีข้อผิดพลาดใด ๆ
เราสามารถนำผลลัพธ์ของโปรแกรมมาใส่ในตัวแปร ด้วยการสั่งภายใต้เครื่องหมาย ` (grave accent)
เช่น
#!/bin/bash
DBS=`mysql -u root -e "show databases"`
for b in $DBS ;
do
mysql -u root -e "show tables from $b"
doneเป็นการนำผลลัพธ์ของคำสั่งแรกคือ mysql -u root -e "show databases" มาใส่ในตัวแปร DBS เพื่อทำเป็นขอบเขตให้กับตัวแปร b ในคำสั่ง for อีกครั้งหนึ่ง
ตามตัวอย่างจะแสดงผลทุกตารางในทุกฐานข้อมูลของ mysql
[ "$s1" = "$s2" ] หรือ [ "$s1" == "$s2" ] เป็นจริง ถ้า s1 เท่ากับ s2[ "$s1" != "$s2" ] เป็นจริง ถ้า s1 ไม่เท่ากับ s2[[ "$s1" < "$s2" ]] หรือ [ "$s1" \< "$s2" ] เป็นจริง ถ้า s1 น้อยกว่า s2[[ "$s1" > "$s2" ]] หรือ [ "$s1" \> "$s2" ] เป็นจริง ถ้า s1 มากกว่า s2[ -n "$s1" ] เป็นจริง ถ้า s1 มีค่าใด ๆ[ -z "$s1" ] เป็นจริง ถ้า s1 ไม่มีค่า#!/bin/bash
S1='string'
S2='String'
if [ "$S1"="$S2" ]; then
echo "S1('$S1') is not equal to S2('$S2')"
fi
if [ "$S1"="$S1" ]; then
echo "S1('$S1') is equal to S1('$S1')"
fi+ การบวก- การลบ* การคูณ/ การหาร% การหาเศษจากตัวหาร (remainder)-lt น้อยกว่า (<)-gt มากกว่า (>)-le น้อยกว่าหรือเท่ากับ (<=)-ge มากกว่าหรือเท่ากับ (>=)-eq เท่ากับ (==)-ne ไม่เท่ากับ (!=) sed เป็นเอดิเตอร์แบบบรรทัดคำสั่ง มีการใช้งานที่พลิกแพลงหลากหลายมาก ตัวอย่าง
$ sed 's/old/new/g' /tmp/dummy
นำเอาเนื้อไฟล์ /tmp/dummy มาแทนที่ old ด้วย new และแสดงออกทางจอภาพ
$ sed 12,18d /tmp/dummy
นำเอาเนื้อไฟล์ /tmp/dummy มาแสดงทางจอภาพ โดยเว้นไม่แสดงบรรทัดที่ 12 ถึงบรรทัดที่ 18
ดูรายละเอียดเพิ่มเติมได้ที่ gentoo: Sed by example
awk เป็นทั้งโปรแกรมและภาษาในการค้นหาข้อความในไฟล์จากรูปแบบที่เรากำหนดให้/tmp/dummy มีเนื้อไฟล์คือ
test123 test tteesstt
ตัวอย่างการใช้งานคือ
$ awk '/test/ {print}' /tmp/dummy
test123
test
ดูรายละเอียดเพิ่มเติมได้ที่ gentoo: Awk by example
grep เป็นโปรแกรมที่ใช้บ่อยในการค้นข้อความในไฟล์ และยังมีความสามารถในการสรุปผลการนับข้อความด้วย$ man grep | grep "standard" -c 8
เป็นการค้นคำว่า standard ในการแสดงผลของคำสั่ง man grep ว่ามีอยู่กี่คำ คำตอบคือ 8
ดูตัวอย่างเพิ่มเติมที่ tdlp: Examples using grep
wc ใช้ในการนับคำ, นับบรรทัด และนับจำนวนหน่วยความจำที่ถูกใช้ในไฟล์ เป็นไบต์$ wc --words --lines --bytes /tmp/dummy 3 3 22 /tmp/dummy
sort ใช้จัดเรียงข้อมูลb c a
ตัวอย่างคำสั่งคือ
$ sort /tmp/dummy a b c
คือการนำเอาเนื้อไฟล์ /tmp/dummy มาจัดเรียง และแสดงผลออกทางจอภาพ
bc เป็นเครื่องคิดเลขแบบใช้บรรทัดคำสั่ง$ echo 1+1 1+1 $ echo 1+1 | bc 2
หรือใช้แบบโต้ตอบ
$ bc -q
1 == 5
0
0.05 == 0.05
1
5 != 5
0
2 ^ 8
256
sqrt(9)
3
while (i != 9) {
i = i + 1;
print i
}
123456789
quit
tput ใช้ในการตั้งค่าหรือแสดงค่าต่าง ๆ ของเทอร์มินัล$ tput cup 10 4
เลื่อนเคอร์เซอร์ไปยังบรรทัดที่ 10 สดมภ์ที่ 4
$ tput reset
ล้างจอภาพ มีค่าเท่ากับคำสั่ง clear
$ tput cols
แสดงจำนวนสดมภ์ (ความกว้าง) ของจอเทอร์มินัล
#!/bin/bash
function listdir {
local PAT="$1"
local ROOT="$2"
for i in *; do
if [ -d "$i" ]; then
local CUR="$ROOT/$i"
pushd "$i" &>/dev/null
listdir "$PAT" "$CUR"
popd &>/dev/null
fi
done
if [ ! -z "$( ls -d $PAT 2>/dev/null )" ]; then
echo "Directory: $ROOT"
ls -d $PAT 2>/dev/null
echo
fi
}
if [ -z "$1" ]; then
echo List file in PATTERN recursive into directories.
echo Usage: $0 "PATTERN"
exit
fi
PATTERN="$1"
echo "List $PATTERN"
listdir "$PATTERN" "."ให้ผลคล้ายคำสั่ง
$ find * -name PATTERN
#!/bin/bash SRCD="/home/" TGTD="/var/backups/" OF=home-$(date +%Y%m%d).tgz tar -cZf $TGTD$OF $SRCD
#!/bin/sh
# renna: rename multiple files according to several rules
# written by felix hudson Jan - 2000
#first check for the various 'modes' that this program has
#if the first ($1) condition matches then we execute that portion of the
#program and then exit
# check for the prefix condition
if [ $1 = p ]; then
#we now get rid of the mode ($1) variable and prefix ($2)
prefix=$2 ; shift ; shift
# a quick check to see if any files were given
# if none then its better not to do anything than rename some non-existent
# files!!
if [$1 = ]; then
echo "no files given"
exit 0
fi
# this for loop iterates through all of the files that we gave the program
# it does one rename per file given
for file in $*
do
mv ${file} $prefix$file
done
#we now exit the program
exit 0
fi
# check for a suffix rename
# the rest of this part is virtually identical to the previous section
# please see those notes
if [ $1 = s ]; then
suffix=$2 ; shift ; shift
if [$1 = ]; then
echo "no files given"
exit 0
fi
for file in $*
do
mv ${file} $file$suffix
done
exit 0
fi
# check for the replacement rename
if [ $1 = r ]; then
shift
# i included this bit as to not damage any files if the user does not specify
# anything to be done
# just a safety measure
if [ $# -lt 3 ] ; then
echo "usage: renna r [expression] [replacement] files... "
exit 0
fi
# remove other information
OLD=$1 ; NEW=$2 ; shift ; shift
# this for loop iterates through all of the files that we give the program
# it does one rename per file given using the program 'sed'
# this is a sinple command line program that parses standard input and
# replaces a set expression with a give string
# here we pass it the file name ( as standard input) and replace the nessesary
# text
for file in $*
do
new=`echo ${file} | sed s/${OLD}/${NEW}/g`
mv ${file} $new
done
exit 0
fi
# if we have reached here then nothing proper was passed to the program
# so we tell the user how to use it
echo "usage;"
echo " renna p [prefix] files.."
echo " renna s [suffix] files.."
echo " renna r [expression] [replacement] files.."
exit 0
# done!#!/bin/bash
# renames.sh
# basic file renamer
criteria=$1
re_match=$2
replace=$3
for i in $( ls *$criteria* );
do
src=$i
tgt=$(echo $i | sed -e "s/$re_match/$replace/")
mv $src $tgt
doneเราใช้พารามิเตอร์ -x ต่อท้ายคำสั่งในบรรทัดแรก
#!/bin/bash -x
จะมีผลว่าเชลล์จะแสดงทุกคำสั่งที่ถูกรันออกมาทางจอภาพ
จบแล้วจ้า
ต้องการแปลงชื่อไฟล์เป็นตัวเล็ก ค้นไปค้นมาปรากฎว่ามีอยู่ในแพกเกจ bash-doc เป็นตัวอย่างอยู่แล้ว
ต้องติดตั้งก่อน
$ sudo aptitude install bash-doc
ขมายมาใช้
$ sudo cp /usr/share/doc/bash-doc/examples/scripts.v2/lowercase /usr/local/bin
$ sudo chmod 755 /usr/local/bin/lowercase
เรียกใช้ด้วยคำสั่ง lowercase $FILENAME
นอกจากตัวอย่างในการแปลงไฟล์แล้ว ยังมีโปรแกรมอรรถประโยชน์อีกเยอะแยะในแพกเกจนี้ หากสนใจสามารถติดตั้งและศึกษาดูได้ครับ
ถ้าจะแปลงเป็นตัวใหญ่ก็แค่กลับ จาก tr A-Z a-z ไปเป็น tr a-z A-Z ก็เสร็จแล้ว
ทำเป็นตัวอย่างคือ
$ sudo vi /usr/local/bin/uppercase
#! /bin/bash
#
# original from
# @(#) lowercase.ksh 1.0 92/10/08
# 92/10/08 john h. dubois iii (john@armory.com)
#
# conversion to bash v2 syntax done by Chet Ramey
Usage="Usage: $name file ..."
phelp()
{
echo "$name: change filenames to upper case.
$Usage
Each file is moved to a name with the same directory component, if any,
and with a filename component that is the same as the original but with
any lower case letters changed to upper case."
}
name=${0##*/}
while getopts "h" opt; do
case "$opt" in
h) phelp; exit 0;;
*) echo "$Usage" 1>&2; exit 2;;
esac
done
shift $((OPTIND - 1))
for file; do
filename=${file##*/}
case "$file" in
*/*) dirname=${file%/*} ;;
*) dirname=. ;;
esac
nf=$(echo $filename | tr a-z A-Z)
newname="${dirname}/${nf}"
if [ "$nf" != "$filename" ]; then
mv "$file" "$newname"
echo "$0: $file -> $newname"
else
echo "$0: $file not changed."
fi
done
$ sudo chmod 755 /usr/local/bin/uppercase
เสร็จแล้ว เรียกใช้ด้วยคำสัง uppercase $FILENAME