ลองบนอูบุนตู
$ 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 พอ ขั้นตอนคือ
awk
sed
png
ด้วยคำสั่งแบบบรรทัดคำสั่งของ 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
มาไว้ในไดเรกทอรี่ $MIGRATEDIR
d.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.] fi
if ... 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 done
while
#!/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