Showing posts with label find(1). Show all posts
Showing posts with label find(1). Show all posts

Thursday, May 6, 2010

FreeBSD: programming -->getopt



commands

เพื่อนๆ หลายท่านคงเคยใช้งานโปรแกรม แบบที่ต้องกำหนด option ด้วย เช่น
% find -n -x pattern filename
% grep -v R-dep


ซึ่งโปรแกรมลักษณะนี้ อำนวยความสะดวกให้ผู้ใช้มาก ในแง่ที่ว่า สามารถเพิ่มคุณลักษณะวิเศษไปจากเดิมได้ นิดหน่อย โดยไม่ต้องสั่งโปรแกรมเดิมมาทำงานซ้ำ

n, x หรือ v ในตัวอย่างนั้น เรียกว่า option การกำหนด option ให้กับโปรแกรมที่เขียนขึ้น แม้จะทำได้เองโดยง่าย แต่ในโลกของ unix แล้ว การใช้บริการของ system functions นั้นเป็นสิ่งที่ ต้องทำ เพราะอย่างน้อย ก็ ลดภาระ ของผู้พัฒนาโปรแกรม และ ทำให้ ตัดความกังวล เรื่อง ความปลอดภัยของระบบไปได้มาก

ความปลอดภัย เป็นเรื่องที่ล้อเล่นไม่ได้ น่ะ และในยามที่เรามีกฏหมายเกี่ยวกับคอมพิวเตอร์มาใช้งานแล้วเช่นนี้ หากโปรแกรมที่เราเขียนขึ้น มีช่องโหว่ว ก็ลำบากมากอยู่ ถ้าใช้กันผ่าน network

ในโอกาสพิเศษเช่นนี้ จึงใคร่ขอนำเสนอเรื่องสั้นๆ ไม่ยาวนัก แต่พอให้เกิดปัญญา มาตกลงปรองดองกันได้ ระหว่าง การเขียน code ขึ้นมาเอง กับการใช้บริการของ system functions (ผ่าน library)

function ที่ว่านี้ เรียกว่า getopt(3) ซึ่งเมื่อเปิด manual page ใน section ที่ 3 ดูก็ทราบรายละเอียดได้ว่า
  1. ต้อง link กับ standard C library (-lc) ในตอน compile ตัวอย่างคือ
    % cc -o programme programme.c -lc
  2. function นี้กำหนด prototype ไว้แล้วใน unistd.h header files ทำให้เราต้องรวมบรรทัด
    #include <unistd.h>
    เข้าไปด้วย


ตัวอย่าง ที่เขียนขึ้นจริงๆกับ โปรแกรมเรื่องสั้นภาค ๒ ที่เพิ่งส่งไปเมื่อวันอังคารที่ผ่านมานี้ ขอตัดมาให้ดูแต่เพียงบางส่วนเท่านั้น คือ
@ |getopt(3)|, one of many functions from system services
@<Getoption@>=
sflag = 0;
interactive=0;
while ((ch = getopt(argc, argv, "is:d:")) != -1) { <----
  switch (ch) {
   case 'i':@/
      interactive = 1;@/
      break;@/
   case 's':
      if ((fp = fopen(optarg, "r")) == NULL) {
       fprintf(stderr,"%s: %s: %s\n",programme, optarg, strerror(errno));
       status = OPEN_FILE;
      }
      sflag = 1;
      strcpy(filename,optarg); /* for later used */
      break;
   case 'd':
      if ((fp = fopen(optarg, "r")) == NULL) {
       fprintf(stderr,"%s: %s: %s\n",programme, optarg, strerror(errno));
       status = OPEN_FILE;
       }
      strcpy(filename,optarg); /* for later used */
      break;
   case '?':@/
   default:@/
      fprintf(stderr,"usage: %s -[[i]s|[i]d] data-filename\n%s\n", programme,
      strerror(errno));
      status = USAGE;
      break;
   }
  }
โดยที่ตรงบรรทัด while นั่นแหละ พอเดาออกน่ะครับว่า options ที่ใช้คือ i,s,d อันนี้ไม่ได้หมายถึง index for sustainable development ของการพัฒนาอย่างยั่งยืน อะไรหรอกน่ะ แต่มันเผอิญมาตรงกัน เท่านั้นเอง

กล่าวคือ option ของโปรแกรมที่ใช้งานจริงจริง เมื่อเรียกใช้ ก็จะอยู่ในรูป
% programme -i -sfilename | -dfilename
หรือ
% programme -isfilename | -idfilename
หรือ
% programme -sfilename | -dfilename
จาก manual page ทราบว่า เรามีภาระเพียงกำหนด argument ที่ 3 ให้กับ system function นี้ ซึ่ง มีรูปแบบ ที่แน่นอน ไว้ให้แล้ว พร้อมกับเขียน code มาเพื่อรองรับ option ที่กำหนดไว้ ก็เท่านั้นเอง

และในตัวอย่าง มะไฟ ก็เพียงเลือกเอาว่า จะใช้ option i,s,d โดยที่ ๒ อันหลังนั้นต้องมีชื่อแฟ้มตามมาด้วยเสมอ กล่าวคือ ต้องอยู่ในรูป
% programme -sfilename
หรือ
% programme -dfilename
ย่างนี้เป็นต้นเสมอ จะใช้แต่ option ใด option หนึ่งโดดๆ ไม่มีชื่อแฟ้มตามมาด้วย ไม่ได้

ใน manual page นั้น เขาบอกไว้ชัดเจนว่า option ที่มี argument ตามมานั้น ให้ตามหลัง option นั้นด้วย : เสมอ เพื่อแยกจาก option อื่นที่ไม่มี argument ตามมาด้วย ส่วนที่ว่า argument ที่ตามมาด้วยนั้น จะเขียนติดต่อกันไปเลย อย่างในตัวอย่างที่ยกมาให้ดู หรือ จะมี space ตามเพื่อความสวยงาม ก็ได้ ไม่ว่ากัน

ในตัวอย่างที่ยกมานั้น option s และ option d มี : ตามอยู่ด้วย ทำให้ทราบว่า ทั้ง ๒ option นั้นมี argument ตามมาแน่นอน ซึ่งก็คือชื่อของแฟ้มเอกสาร ที่จะเปิดเพื่ออ่านข้อมูลนั่นเอง

ก็หวังว่าคงพอเข้าใจบ้าง

และเมื่อลองเทียบ ความเรียบง่ายนี้ กับ codes ที่ต้องเขียนเองขึ้นมา โดยขอให้ไปดูจากหนังสือ The C Programming Language ในบทที่ ๕ section ที่ 10 Command-line Arguments ท่านก็จะพบว่า ยุ่งเหยิงมากกว่ากันเยอะเลย เอาแค่การตีความ precedence ของ pointers & operator ก็แทบจะพูดได้ว่า

โชคดีแล้ว ที่ไม่ต้องเขียนเอง

การใช้บริการของ system function ผ่าน library ทั้งหลายที่มีให้ จึงนับว่าดีที่สุด

ขอยกคุณงามความดีนี้ ถวายเป็นราชพลี ถวายเป็นปฏิบัติบูชา ในวาระ ฉัตรมงคลสมัย อันประเสริฐยิ่งนี้

อันใดที่บกพร่อง มะไฟ ฅนยาก ขอน้อมรับไว้แต่ผู้เดียว

Friday, July 4, 2008

shell(2)

find

ตั้งใจจะจบเรื่อง shell ไว้เท่าที่เขียนไป แต่มานึกดูอีกที หากขาดรายละเอียดของทั้ง ไฟ, find, และ เสร็จ, sed ที่ใช้ประกอบการบรรยาย ก็เหมือนว่ากินข้าว แล้วไม่ดื่มน้ำตาม ก็ด้วยเหตุนี้แล จึงมีภาคนี้เป็นลำดับถัดมา

find(1) ที่ใช้ค้นหาเอกสารนั้น มีรูปแบบง่ายๆ อย่างที่ยกมานั่นคือ
# find  /usr/ports  -name work  -type d  -print
ซึ่งมีความหมายตรงไปตรงมาคือ ให้หา จาก /usr/ports เอกสารชื่อ work ที่เป็น directory ส่วนที่เหลือนั้นบอกว่า ให้พิมพ์ออกมาเมื่อหาเจอ,  -print
ผลลัพธ์ก็คือ, สำหรับ goffice2,
/usr/ports/converters/fribidi/work
/usr/ports/converters/psiconv/work
/usr/ports/deskutils/glabels/work
/usr/ports/deskutils/planner/work
/usr/ports/devel/goffice/work
/usr/ports/devel/libgsf-gnome/work
/usr/ports/devel/goffice04/work
/usr/ports/editors/abiword/work
/usr/ports/editors/gnome2-office/work
/usr/ports/graphics/dia/work
/usr/ports/graphics/libwmf/work
/usr/ports/math/gnumeric/work
/usr/ports/textproc/wv/work

ซึ่งเรียกกัน หรือ รู้กันว่า คือ temporary working directory, ${WRKDIR}, และจะพิมพ์ออกมาทาง standard output,  stdout ทีละบรรทัด ทีละบรรทัดไป และนี่แหละคือ ไฟ, find(1)

ถ้าเป็นเช่นนี้ คงไม่ใช่ shell ที่เราต้องการ เพราะสิ่งที่เราต้องการคือ, ยกมาจำเพาะผลลัพธ์ บางส่วน,

cd /usr/ports/converters/fribidi && make package
cd /usr/ports/converters/psiconv && make package
....
cd /usr/ports/textproc/wv && make package


ก็แล้วเราจะเปลี่ยนให้ /usr/ports/converters/fribidi/work เป็น cd /usr/ports/converters/fribidi && make package ได้ยังไง ซึ่งก็นี่แหละคือหน้าที่ของ เสร็จ, sed(1) ไงละ

sed
คำสั่งเสร็จที่ใช้ในตัวอย่างที่ยกมาคือ
# sed  -e 's,/usr,cd /usr,'  -e 's,/work, \&\& make package,'
ซึ่งขอยกรูปแบบทั่วไปที่ใช้ตอนนี้มาให้เห็นคือ
# sed   -e  'sDELIMITtextDELIMITnew textDELIMIT'
โดยที่ input ของเสร็จ จะเป็น output ของ ไฟ เพราะส่งกันมาด้วย ท่อ, pipe อีกทีหนึ่ง ก็ต้องเป็นยังงี้แหละ เมื่อ ไฟ ส่งมา ๑ บรรทัด เสร็จ เขาก็จะ process ไป ๑ ที แล้วก็ส่งผลลัพธ์ไปที่ stdout

คราวนี้คงง่ายแล้วใช่ไหม ในการที่จะอ่านสิ่งที่ ได้บอกไว้ตั้งแต่ ports(1) โน้น โดยที่เราเลือกที่จะใช้ตัว คอมม่า เป็นตัว DELIMIT เพื่อแก้ไขตัว text ให้ได้ตามใจ
อ้อ เราต้องแก้ ๒ หนนะ ก็เลยต้องมี สีลบออง ๒ ลบอี sed  -e 's, , ,'  -e 's, , ,' โดยลบอีแรก เราแก้จาก /usr เป็น cd /usr และ ลบอีหลัง เราแก้จาก /work เป็น && make package แต่ สืบเนื่องจากตัว && นั้น มีความหมายพิเศษ เราจึง ยกความหมาย มันออกไปไว้ที่อื่นชั่วคราว ด้วยการใช้ ตัว \ ไงครับ

เสร็จ เอ้ยจบ find & sed และย้ำว่า โปรดศึกษา find(1) & sed(1) เพิ่มเติมด้วย

ขอย้ำว่า เรื่องของ sh ที่นำเสนอนี้ เป็นแต่เพียงสาระเบื้องต้นเท่านั้น และยังหยาบมากอยู่ เพราะยังขาด ในส่วนของการตรวจสอบข้อบกพร่อง ยังขาดการตรวจสอบ return code ของแต่ละคำสั่ง และยังขาดอีก ฯลฯ ขอให้ผู้ใฝ่รู้จงหาความรู้เพิ่มเติมเอาเองจาก sh(1)

ขอให้ประสบความสำเร็จ และ โชคดี

Saturday, June 28, 2008

shell(1)

จากข้อเขียนก่อน ขอขมวดถึง sh script ที่สร้างขึ้นมาจากการลง goffice2 ดังนี้

#!/bin/sh
set -x

cd /usr/ports/converters/fribidi && make package
cd /usr/ports/converters/psiconv && make package
cd /usr/ports/deskutils/glabels && make package
cd /usr/ports/deskutils/planner && make package
cd /usr/ports/devel/goffice && make package
cd /usr/ports/devel/libgsf-gnome && make package
cd /usr/ports/devel/goffice04 && make package
cd /usr/ports/editors/abiword && make package
cd /usr/ports/editors/gnome2-office && make package
cd /usr/ports/graphics/dia && make package
cd /usr/ports/graphics/libwmf && make package
cd /usr/ports/math/gnumeric && make package
cd /usr/ports/textproc/wv && make package

ขอทบทวนไวยากรณ์ของแต่ละบรรทัดพอให้ตามเรื่องราวได้โดยไม่หลงสักพอประมาณ
บรรทัดแรก อันนี้ จำไว้อย่างเดียว จำไว้เลย shell script ต้องเริ่มอย่างนี้
บรรทัดที่ ๒ จะทำให้การทำงานของแต่ละคำสั่ง แสดงผลออกมา ทุกรายคำสั่งย่อย
บรรทัดที่ ๓ ว่างๆ เพื่อความสวยงาม อันนี้เป็นความชอบของแต่ละคนๆไป
บรรทัดที่เหลือ ที่เริ่มด้วย cd /usr ... && make package นั้นคือ โครงสร้างทั่วไป ที่ควรศึกษาและจำไว้ คือ
expression1  &&  expression2
หรือที่เรารู้จักกันในรูป expression1  AND  expression2 ซึ่งทั้ง ๒ นิพจน์ ต้องเป็นจริงเท่านั้น
นั่นคือสาระเล็กๆน้อยๆจาก sh script ที่เขียนขึ้น ทีนี้มาดูว่าเราเขียนขึ้นมาได้อย่างไร เพราะที่บอกไว้ใน ports(1) แล้วว่า ...ถ้าเราไปไล่สั่ง # make package ไปเสียทุกที่ มันก็จบ นั้นมันดูง่ายๆ แต่เราจะไปรู้ได้อย่างไรว่า มัน อยู่ ณ ที่ใดใน ports tree ตั้งหมื่นกว่าตัวนั้น และนี่เองคือที่มาของ ผู้ช่วย อย่าง find(1)
ผลลัพธ์ของ find ดูแปลกดี เพราะไม่ว่าจะสั่ง make run-depends-list ก็พบว่ามีไม่มาก
ท่านอาจจะแย้งเอาได้ แต่ถ้าคิดว่า บาง package นั้น ต้องการอันอื่น เพิ่มขึ้นมา เป็นการจำเพาะ ก็สมเหตุสมผลอยู่
อีกประการหนึ่ง ผลลัพธ์ของ find(1) นั้นที่จริงจะเป็นอย่างนี้ สำหรับ gnome2-office
/usr/ports/converters/fribidi/work
/usr/ports/converters/psiconv/work
/usr/ports/deskutils/glabels/work
/usr/ports/deskutils/planner/work
/usr/ports/devel/goffice/work
/usr/ports/devel/libgsf-gnome/work
/usr/ports/devel/goffice04/work
/usr/ports/editors/abiword/work
/usr/ports/editors/gnome2-office/work
/usr/ports/graphics/dia/work
/usr/ports/graphics/libwmf/work
/usr/ports/math/gnumeric/work
/usr/ports/textproc/wv/work
เราจะเปลี่ยน หรือ แปลงสภาพ ที่เห็นอยู่นี้ ให้เป็น sh script ข้างบนนั้นได้ยังไง คำตอบก็คือ sed(1) ที่ปรากฏในช่วงท้าย ของบรรทัด find นั้น ซึ่งขอให้ศึกษาเอง เพราะไม่ยากเลย
เรื่องของ shell ก็ขอยุติลงแต่เพียงนี้ หวังว่าท่านคงได้สาระบ้าง แลหากมีอันใดบกพร่อง มะขาม ขอน้อมรับไว้แต่ผู้เดียว หากจะมีความดีบ้าง ก็ยกให้กับ ไหมฟ้า ผู้ดูแลในปัจจุบัน และ กุสุมา ผู้ล่วงลับไปแล้ว ซึ่งหากปราศจาก สองท่านผู้ที่เอ่ยนามมานี้ ก็มิอาจมี มะขาม มาเขียนหนังสือได้ในวันนี้

Monday, June 23, 2008

shell

ขอเอ่ยถึงเรื่องของ shell สักเล็กน้อย ไม่งั้นไม่ได้ชื่อว่าเล่น unix แลเมื่อเอ่ยถึง shell แล้วก็หนีไม่พ้น Bourne shell (sh)

sh เป็นเครื่องมือสำหรับผู้บริหารระบบ ในการจัดการงานต่างๆ แม้ว่าปัจจุบันนี้ จะมีเครื่องมือช่วย ในลักษณะของ graphics ให้มากมายก็ตาม แต่ในยามคับขัน ที่ต้องกู้ระบบแล้ว ความรู้พื้นฐานเหล่านี้ กลับช่วยชีวิตผู้คนไว้ได้มากกว่า

sh เป็นทั้ง command interpreter ที่ท่านต้องใช้ เมื่อท่านเข้าสู่ single user mode และ programming language ที่สามารถทำให้ท่านเขียน script สั้นๆเพื่อ แบ่งเบา งานซ้ำซากสำหรับท่านได้

ข้อเขียนวันนี้ ขอนำเสนอในแง่หลัง ทั้งนี้เพื่อใช้ประกอบกับเรื่องของ ports(1) ที่ได้เสนอไปก่อนหน้านี้

sh เป็นแฟ้มเอกสารพื้นๆ จึงสะดวกที่ท่านสามารถใช้ vi หรือ plain text editors ยอดนิยมของท่าน มาเตรียมขึ้นโดยง่าย ในการเขียน sh script นั้นมีหลักยึดง่ายๆเลย คือ ให้เขียนไปทีละบรรทัด ทดสอบไปทีละบรรทัด ก็ดูง่ายๆ ตรงไปตรงมาดี โดยเริ่มต้นที่

#!/bin/sh


ขอยกตัวอย่างง่ายๆอันเป็นที่นิยมยกมาคือ Hello, world ซึ่งโดยเนื้อแท้แล้วก็ไม่มีอะไร นอกจากสั่งให้ข้อความดังกล่าว ปรากฏที่หน้าจอ ฟังก์ชั่นนั้นคือ echo ก็ลองเรียก editor ยอดนิยมของท่านมา แล้วเตรียมเอกสารชื่อ myhello โดยมีรายละเอียดในแฟ้มนั้นดังนี้ ตามนี้เลยนะ, สีแดงๆ ไม่ต้องระบายให้หรอก

#!/bin/sh

echo Hello, world

เมื่อเสร็จแล้วให้สั่งต่อไปอีก ตามนี้นะ
# chmod +x myhello
พอเสร็จแล้วก็สั่งต่อไปซี น่านะ
# ./myhello
คงไม่ต้องบอกหรอกว่าผลลัพธ์คืออะไร

ความจริงแล้ว ฟังก์ชั่น echo นั้นไม่มีอะไรมากเลย เราเขียนอะไรตามหลังคำนั้น เขาก็เขียนอันนั้นออกมาที่ standard output แต่ก็ใช่ว่า อักขระ ทุกตัวจะสามารถแสดงออกมาได้ ถึงแม้จะเป็นอย่างนั้น หากเราต้องการ แสดงอักขระนั้นๆ ก็ย่อมทำได้ โดย ปิดคุณสมบัติ ของอักขระนั้นๆเสีย เราใช้ escape character ปิด คุณสมบัติพิเศษต่างๆ
ตัว escape character นั้นได้แก่ \
ค่อยๆเรียนรู้ไปทีละตัวว่า อะไรแสดงได้โดยตรง อะไรต้องการ escape character
ลองพิมพ์ข้อความต่อไปนี้บนหน้าจอของท่านดูซิ

# echo #\!/bin/sh
# echo set -x
# echo

คงคุ้นๆกันอยู่กระหมั่งหนา และโปรดสังเกตุด้วยว่า ! นั้นต้องใช้ escape character เข้ามาช่วย เพื่อให้แสดงผลเป็น ! จริงๆ เพราะความหมายแท้จริงของเขาใน shell นั้นคือ negate
ทีนี้ แทนที่เราจะให้แสดงผลที่หน้าจอ เราก็ให้เก็บไว้ในแฟ้มใดแฟ้มหนึ่ง โดยวิธีที่ง่ายๆ สำหรับแฟ้มเล็กๆ (หรือแม้กระทั่งแฟ้มใหญ่ๆ ก็ยังคงใช้วิธีนี้ได้เช่นกัน)

# echo #\!/bin/sh > pkgs
# echo set -x >> pkgs
# echo >> pkgs

หากท่านอยากทราบว่า ข้อความดังกล่าว จะมีอยู่ในแฟ้ม pkgs หรือไม่ ก็สามารถทำได้ง่ายๆ ด้วยคำสั่ง cat โดยมิถึงกับต้องเรียก editor แต่อย่างไรเลย

# cat  pkgs

อนึ่ง หากท่านต้องการให้แฟ้มนี้ สามารถ ทำงานได้ ท่านต้องกำหนด permission ให้เป็น +x ด้วยคำสั่ง chmod ดังนี้

# chmod   +x  pkgs

เพียงเท่านี้ ท่านก็สามารถสร้าง shell ขึ้นมาใช้งานได้ง่ายๆ
ทิ้งท้ายด้วยคำถามชวนคิด จากเอกสารเรื่อง ports(1) ขอถามว่าคำสั่ง find ... นั้น แท้จริงทำอะไร ใครตอบได้ถูกต้อง มีรางวัลให้ก็แล้วกัน

Thursday, June 19, 2008

ports(1)

ขออนุญาตเพิ่มเติมในเรื่องของ ports สักหน่อยเถอะ เพราะรู้สึกว่า ไม่พอใจกับสิ่งที่เขาทำไว้หนะ ก็แหม คน ที่ไม่ใช่ ฅน ก็ยังงี้แหละ

เรื่องที่ไม่พอใจก็ส่วนที่สร้าง packages ที่เราได้จากการลง port นั้นๆ เท่านี้แหละ ที่ไม่ค่อยจะพอใจน่ะ เพราะเหตุว่า เวลาที่อยากเก็บ packages ทั้งหมดที่ได้จากคำสั่ง

# make install

ซึ่งพอเราสั่ง

# make package-recursive

แล้วมันมักจะทำ หรือ สร้าง packages มาหมดทุกตัว โดยไม่สนใจว่า packages อื่นที่เหลือนั้น สร้างมาก่อนหน้านี้แล้ว แบบว่า มันสร้างซ้ำอีกหนะ

อันนี้แหละ ที่ไม่พอใจ เพราะไม่อยากเสียเวลาไปสร้างซ้ำอีก มันนานนะ ไม่มีอะไรเลย

แล้วจะแก้ยังไง

จากความรู้ที่ว่า เมื่อเราลง ports นั้น มันจะสร้าง work directory ขึ้นมาด้วย และจะยังคงอยู่ ตราบเท่าที่เราไม่ลบทิ้งด้วยคำสั่ง

# make clean

อีทีนี้ ถ้าเราไปไล่สั่ง # make package ไปเสียทุกที่ มันก็จบ

เอาละ พอได้ไอเดียแบบนี้ ก็ลงมือทำเลย ง่ายแล้ว แต่ต้องเป็น root น่ะ ตามนี้น่ะ

# touch pkgs
# chmod +x pkgs
# echo #\!/bin/sh > pkgs
# echo set -x >> pkgs
# echo >> pkgs
# find /usr/ports -name work -type d -print | \
   sed  -e   's,/usr,cd /usr,'  -e   's,/work, \&\& make package,' >> pkgs

จากนั้น เราก็ลำบากเพียงสั่ง

# ./pkgs

สิ่งที่ต้องการ ก็บรรลุผลสมดั่งใจ
แล้วก็ อย่าลืมสั่ง clean ด้วยละ ไม่งั้นตัว work directory มันจะยังคงอยู่ และจะทำให้งวดต่อไป เราเองแหละที่ต้องทำซ้ำไปเสียเอง ส่วนที่ว่าจะสั่งให้เป็น make clean อย่างไร ไปคิดเอาเองน่ะ

ตัว shell script ที่อยู่ในแฟ้ม pkgs นั้นจะเปลี่ยนไป ตัวอย่างในกรณีที่ลง webmin ซึ่งต้องการ p5-Authen-SSLea แต่ว่าได้ลงไปแล้วโดย ports อื่น ผลที่ได้จึงมีเพียงเท่านี้


#!/bin/sh
set -x

cd /usr/ports/security/p5-Authen-PAM && make package
cd /usr/ports/sysutils/webmin && make package


ขอยกความดีใดๆ อันจะเกิดขึ้น แม้เพียงเล็กน้อยจากข้อเขียนนี้ให้กับ ไหมฟ้า ผู้ซึ่งดูแล มะขาม ก่อนเข้ารับการผ่าตัด ระหว่างการผ่าตัด หลังการผ่าตัด จนกระทั่งกลับมาทำงานได้ โดยไม่เห็นแก่เหน็ดเหนื่อยแม้แต่น้อย

ขอได้รับการขอบคุณอย่างมาก จาก มะไฟ ไว้ ณ ที่นี้ด้วย

View My Stats