Showing posts with label stderr. Show all posts
Showing posts with label stderr. Show all posts

Thursday, June 21, 2012

UNIX Programming

จาก standard I/O library ที่ได้เกริ่นนำมา คงพอให้ความคิด เกี่ยวกับการทำงานกับ แฟ้มเอกสาร ที่
  • ต้องเปิด
  • ต้องอ่าน/เขียน
  • ปิดแฟ้ม
นอกไปจากตอน I ที่เราทำงานตรงๆกับ terminal ซึ่ง file pointer นั้นเชื่อมโยงกับ terminal มาให้แล้ว ก็พอเห็นความแตกต่าง และ เนื้องานที่เพิ่มขึ้นมาบ้าง  มา เรามาไปกันต่อในเรื่องที่ยังค้างคาอยู่อีกนิด แต่ต้องทิ้งห่าง และ แยกออกมาพิเศษ

(๒.๒) Error Handling -- stderr and exit
 ในช่วงแรก ของตอน II นี้นั้น โปรแกรม wc.c เขาจะตรวจว่า สามารถเปิดแฟ้มเอกสารได้หรือไม่ หากไม่ได้ ก็จะแจ้งเตือน ผ่านทาง stdard error, stderr ซึ่ง ยังไงเสีย stdard error นี้ก็จะเชื่อมโยงกับ terminal เสมอ ไม่มีทาง กำหนดให้เป็นอย่างอื่น ไปได้ ทั้งนี้ก็เพื่อให้ผู้ใช้งาน ทราบ หรือ รับรู้ได้ทันทีว่า เกิดความผิดพลาดขึ้นแล้ว นั่นเอง

ความผิดพลาดนี้ จะแจ้งเตือนผู้ใช้งาน ผ่านทางประโยค fprintf(stderr,"...") ซึ่งนับว่าง่ายที่สุดแล้วในเบื้องต้นนี้

อนึ่ง โปรแกรมสามารถส่งสัญญาณแจ้งเตือนถึง ความผิดพลาด ในทางอื่นได้ โดยการใช้ฟังก์ชั่น exit เพื่อ หยุดการทำงาน ของโปรแกรม  และ ค่าที่ส่งให้กับฟังก์ชั่น exit นั้น จะเป็นตัวที่ส่งผ่านให้ โปรแกรมอื่นใดก็แล้วแต่ ที่เรียกโปรแกรมนี้ มารับไปตรวจสอบ ถึง ความสำเร็จ หรือ ล้มเหลว กันอีกที

โดยข้อตกลงพื้นฐาน ค่าที่ส่งผ่านให้กับ exit จะมีค่าเป็น 0 เพื่อบอกว่า ไม่มีความผิดพลาดเกิดขึ้น  และ ค่าที่ไม่เป็น 0 จะแสดงถึงสัญญาณบ่งชี้ถึง สถานการณ์ที่ไม่ปกติ

ฟังก์ชั่น exit จะเรียก ฟังก์ชั่น fclose  เพื่อกวาดล้าง buffer ออกให้หมด จากนั้น จะเรียกฟังก์ชั่น _exit  ซึ่ง ทำให้โปรแกรม หยุดการทำงานทันที โดยไม่แยแสว่า buffer จะมี หรือ ไม่มีข้อมูลอยู่

ฟังก์ชั่น exit ระบุไว้ใน stdlib.h ดังนั้น ก่อนใช้งานนี้ ท่านสมควรประกาศด้วยประโยค

#include <stdlib.h>

ในตอนแรกเริ่มของแฟ้มเอกสารที่ต้องใช้ฟังก์ชั่นนี้ด้วย เพื่อมิให้เกิดปัญหา ในตอน compile ขึ้นมา

อนึ่ง เพื่อเพิ่มพูนความรู้เรื่องฟังก์ชั่นต่างๆใน stdandard I/O library ขอให้ท่านอ่าน manual ในเรื่องนี้ด้วย  โดยใช้คำสั่งนี้

% man 3 stdio

หากท่านไม่สามารถอ่านจากเครื่องได้ ก็สามารถหาอ่านได้จาก เอกสารอ้างอิง ๑ หรือ เอกสารอ้างอิง ๒ ซึ่งมีอยู่ใน appendix ด้วยกันทั้งคู่

ในตอน II นี้ ขอยุติลงไว้แต่เพียงเท่านี้ ตอนหน้าเราจะกล่าวถึง process และ signal ในที่สุด ก็เป็นอันจบเรื่อง การเขียนโปรแกรม ในสภาพแวดล้อมของ FreeBSD (หรือ UNIX ในเทอมทั่วๆไป)

ขอขอบคุณ ที่สละเวลาอันมีค่ามาอ่าน

Wednesday, June 20, 2012

UNIX Programming

ทั้ง ๒ entries ที่ผ่านมา คือ program arguments  และ stdin/stdout  นั้น ถือเอาเป็น ตอนที่ I น่ะครับ นั่นเป็นเรื่องของ terminal ล้วนๆ ที่ไม่ต้องลำบากในการ “เปิด” และ “ปิด” อะไรให้ยุ่งยากแต่อย่างใดเลย  ใน entry นี้จะเป็นเรื่องที่ พิเศษ มาอีกนิดหนึ่ง  เชิญครับ

“STANDARD I/O LIBRARY”
standard I/O library (ต่อไปจะเรียกแต่สั้นๆว่า library) เป็นการรวบรวม routines ต่างๆ ไว้ที่เดียวกัน เพื่อให้เกิด ประสิทธิภาพ ของงานบริการด้าน I/O สำหรับโปรแกรมภาษา C และเพื่อให้สามารถใช้งานที่ไหนก็ได้  library นี้มีอยู่ในทุกๆระบบที่สนับสนุนภาษา C ดังนั้นโปรแกรมจึงสามารถโยกย้ายจากระบบหนึ่ง ไปยังอีกระบบอื่นได้โดยไม่ต้องแก้ไขเปลี่ยนแปลงอะไรเลย

“(๒.๑) File Access

โปรแกรมที่ยกมาให้ดูในตอน I นั้นทุกโปรแกรม อ่านข้อมูลจาก stdin และ เขียนผลลัพธ์ลงสู่ stdout ซึ่งได้นิยามเอาไว้ดีแล้ว และ โปรแกรม สามารถติดต่อได้โดยตรง    ในลำดับถัดมาเราจะเขียนโปรแกรมที่ต้องเข้าถึงแฟ้มเอกสาร ที่ยังไม่มีการติดต่อ กับโปรแกรม มาก่อนเป็นที่เรียบร้อยแล้ว เหมือนดั่งในกรณี stdin/stdout นั้น      ตัวอย่างง่ายๆ ได้แก่โปรแกรม wc ซึ่งนับจำนวนอักขระ จำนวนคำ จำนวนบรรทัดในแฟ้มเอกสารต่างๆ  เช่น

% wc x.c y.c

ก็จะพิมพ์ จำนวนบรรทัด คำ และอักขระในแฟ้มทั้งสอง และ จำนวนรวมแต่ละอย่างออกมา เป็นต้น

ปัญหาคือ เราจะให้แฟ้มเหล่านั้นถูกอ่านได้อย่างไรกัน นั่นคือจะเอาชื่อแฟ้ม ไปเชื่อมต่อกับประโยคทาง I/O ที่อ่าน-เขียนข้อมูลจริงๆ ได้อย่างไรกัน

กฏที่วางเอาไว้นั้น ง่ายมาก คือ ก่อนที่แฟ้มจะถูกอ่าน หรือ เขียน แฟ้มเอกสารต้องได้รับ การเปิด ก่อน ด้วยฟังก์ชั่นมาตรฐานใน library นี้ที่ชื่อ fopen.   ฟังก์ชั่นนี้ , fopen, จะเอาชื่อแฟ้มมา, ในที่นี้คือ x.c y.c ,  ดำเนินการบันทึกเป็นเบื้องต้นไว้ แล้วติดต่อ ถกเถียงกับระบบปฏิบัติการ  และ ได้ค่า ชื่อในวงการ มา ซึ่งจะใช้ชื่อนี้แหละ สำหรับการอ่าน และ การเขียนในลำดับต่อๆไป

ชื่อในวงการ นี้จะเป็น pointer, เรียกกันว่า file pointer, ที่ชี้ไปยัง structure ที่มีรายละเอียดของแฟ้ม เช่น ตำแหน่งของ buffer  ตำแหน่งปัจจุบันของอักขระใน buffer  แฟ้มนี้จะเปิดสำหรับอ่าน หรือ เปิดสำหรับเขียน และอื่นๆ  ซึ่งผู้ใช้งาน ไม่จำเป็นต้องรู้เลยแม้แต่น้อยนิดเกี่ยวกับ structure นี้ เพราะแฟ้ม stdio.h ได้ให้นิยามของ structure เอาไว้แล้ว ภายใต้ชื่อ FILE

สิ่งที่จำเป็นต้องระบุไว้ ก็มีเพียง การประกาศใช้งาน FILE เท่านั้น ดังตัวอย่าง

FILE *fp, *fopen();

ซึ่งบอกว่า fp เป็น pointer ไปยัง FILE และ fopen เป็นฟังก์ชั่นที่คืนค่า pointer มายัง FILE  ขอให้ทราบเอาง่ายๆว่า FILE นั้นเป็น ชนิดๆหนึ่ง คล้ายๆกับ int นั่นเอง อย่าคิดมาก

การเรียกใช้งานของฟังก์ชั่น fopen ก็ตรงไปตรงมา ดังนี้

    fp = fopen(name, mode);

โดยที่ name เป็นชื่อแฟ้มเอกสารที่เราต้องการทำงานด้วย   และ   mode นั้นคือลักษณะการทำงานกับแฟ้มเอกสารนั้นว่า เราจะ  อ่าน("r") หรือ เขียน("w") หรือ เขียนต่อท้าย("a") ทั้ง name และ mode เป็นอักขระ

ถ้าแฟ้มที่เราต้องการเปิดเพื่อ เขียน หรือ เขียนต่อท้าย นั้นยังไม่มี แฟ้มก็จะถูกสร้างขึ้นมา  การเปิด แฟ้มเอกสารที่มีอยู่แล้ว เพื่อ เขียน นั้น มีผลให้ สาระของแฟ้มเอกสารนั้น ถูกเขียนทับไป  และความพยายาม ที่จะอ่านแฟ้ม ที่ไม่มีอยู่เลย ยังผลให้ เกิด ความผิดพลาด ขึ้นมา   ในกรณีที่เกิด ความผิดพลาด ขึ้น ฟังก์ชั่น fopen จะคืนค่า NULL ซึ่งเป็น null pointer มาให้ผู้ใช้งาน และค่านี้ ได้รับ การนิยาม ไว้แล้ว ในแฟ้ม stdio.h

ในลำดับถัดมา ที่จำเป็นต้องทำคือ การอ่าน หรือ การเขียน หลังจากแฟ้มเอกสารได้รับ การเปิด ขึ้นมาแล้ว    ในการนี้ สามารถกระทำได้หลายทาง แต่ที่ง่ายที่สุดคือ  getc  และ  putc   โดยที่ getc จะคืนค่าถัดมาจากแฟ้มที่อ่าน มาให้, ฟังก์ชั่นนี้ ต้องการ file pointer เพื่อบอกว่า จะเอาจากแฟ้มไหน ดังนี้แล้ว

    c = getc(fp);

ก็จะนำค่าจากแฟ้มเอกสารที่อ้างอิงถึงโดย file pointer fp นั้น มาไว้ยัง c  และฟังก์ชันนี้ จะคืนค่า EOF ในกรณีที่อ่านไป จนถึงตำแหน่งสุดท้าย ของแฟ้มเอกสารแล้ว    putc  ก็จะกลับกันกับ  getc  กล่าวคือ

    putc(c, fp);

จะนำค่าอักขระ c ไปเก็บไว้ยังแฟ้ม ที่ชี้ไปโดย fp  แล้วคืนค่า c กลับมา    ทั้ง ๒ ฟังก์ชั่น จะคืนค่า  EOF ในกรณีที่เกิดความผิดพลาดขึ้น

อนึ่ง เมื่อโปรแกรมเริ่มทำงานนั้น แฟ้ม ๓ แฟ้มจะ ถูกเปิด ให้โดย อัตโนมัติทันที และ file pointer ก็จะกำหนดให้ ในทันที โดยอัตโนมัติ ด้วยเช่นกัน คือ  standard input, standard output และ standard error และ file pointer ของทั้ง ๓ นี้คือ stdin, stdout, stderr ตามลำดับ  และโดยปกติแล้ว ทั้ง ๓ file pointer นี้จะต่อตรงกับ terminal เสมอ แต่ก็อาจจะ redirect ไปยังที่อื่น หรือ pipe ต่อไปยังโปรแกรมอื่น อีกก็ได้  stdin, stdout, stderr นั้นเป็นวัตถุ ที่นิยามไว้แล้ว ในชนิด FILE * จึงสามารถใช้ได้กับวัตถุอื่นใด ที่เป็นชนิด FILE * ได้

เอาล่ะ เมื่อได้ความรู้มาเท่านี้แล้ว เรามาลองยกร่างโปรแกรมง่ายๆ ที่ชื่อ wc กันดูบ้าง ดังนี้

ถ้าโปรแกรมนี้ มี arguments มันก็จะ process arguments ไปจนหมด ถ้าไม่มี ก็จะรับจาก standard input  ซึ่งโดยลักษณะนี้ ทำให้โปรแกรม สามารถเรียกใช้งาน แบบเดี่ยวๆ ได้ หรือ สามารถใช้ร่วม เป็นส่วนหนึ่ง ของ process ใหญ่ได้    โปรแกม wc มีรายละเอียด, แฟ้มชื่อ wc.c,  และ Makefile ดังนี้

/*
 * wc.c
 */ 

#include <stdio.h>

int main(argc, argv)
int argc;
char *argv[];
{
  int c, i, inword;
  FILE *fp, *fopen();
  long linect, wordct, charct;
  long tlinect = 0, twordct = 0, tcharct = 0;

  i = 1;
  fp = stdin;
  do {
    if (argc > 1 && (fp=fopen(argv[i], "r")) == NULL) {
      fprintf(stderr, "wc: can't open %s\n", argv[i]);
      continue;
    }
    linect = wordct = charct = inword = 0;
    while((c = getc(fp)) != EOF) {
      charct++;
      if (c == '\n')
    linect++;
      if (c ==  ' ' || c == '\t' || c == '\n')
    inword = 0;
      else if (inword == 0) {
    inword = 1;
    wordct++;
      }
    }
    printf("%7ld %7ld %7ld", linect, wordct, charct);
    printf(argc > 1 ? " %s\n" : "\n", argv[i]);
    fclose(fp);
    tlinect += linect;
    twordct += wordct;
    tcharct += charct;
  } while (++i < argc);
  if (argc > 2)
    printf("%7ld %7ld %7ld total\n", tlinect, twordct, tcharct);
  return(0);
}



ฟังก์ชั่น fprintf ก็เหมือนกับฟังก์ชั่น printf เพียงแต่เราระบุ file pointer ชนิด FILE ไว้เป็น argument แรกก่อนเขาอื่น เท่านั้นเอง 

ฟังก์ชั่น fclose ก็เป็นฟังก์ชั่น ที่ตรงข้ามกับ fopen  มันทำหน้าที่ ตัดการเชื่อมต่อ ระหว่าง file pointer และ ชื่อข้างนอก ที่สร้างขึ้นมา โดยฟังก์ชั่น fopen นั้น และ ปลดปล่อย file pointer นั้น เพื่อให้ระบบ สามารถมี file pointer ไว้ใช้งานได้พอเพียง 

ขณะเดียวกัน fclose ก็จะกวาดล้าง buffer ที่ฟังก์ชั่น putc ได้รวบรวมเอาไว้นั้น ออกมาด้วยพร้อมกันเลย ฟังก์ชั่น fclose นี้ จะถูกเรียกมาโดยอัตโนมัติ ในทุกโปรแกรมที่ หยุดการทำงาน

และนี่คือ Makefile ของแฟ้มโปรแกรมที่ใช้ชื่อว่า wc.c

CC=clang
CFLAGS= -g
PROG= wc
SRCS= wc.c

NO_MAN=

.include <bsd.prog.mk>


อนึ่ง หากท่านสนใจในเรื่องของ Makefile ลองเข้าไปอ่านดูได้ที่ /usr/share/mk/bsd.README และแฟ้มอื่นๆ ที่เกี่ยวข้องได้ครับ

ขออนุญาตพัก ตอน II ช่วงแรกไว้ ที่นี่ก่อนครับ  ขอขอบคุณ ที่สละเวลามาอ่าน

View My Stats