1. 程式人生 > >coreutils-4.5.1 head.c原始碼分析03

coreutils-4.5.1 head.c原始碼分析03

今天真是把head.c看懂了。
今天天真冷,我網上購了電熱取暖器,沒送到。但邊聽音樂,邊讀程式碼,感覺也很愜意。
看程式碼不能著急,要慢慢看,也許就像有人講的,郝培強講的,一開始要慢,開始慢,後面才能越看越快。是的。看程式碼,開始要慢。不要著急。
head有幾個選項
-n 行數
-c 位元組數
-q 不列印檔名
-v 列印檔名
先把命令用法搞明白。這是重要。

 ./head -q -n 3 head.c
/* head -- output first part of file(s)
   Copyright (C) 89, 90, 91, 1995-2002 Free Software Foundation, Inc.
 ./head -v -n 3 head.c
==> head.c <==
/* head -- output first part of file(s)
   Copyright (C) 89, 90, 91, 1995-2002 Free Software Foundation, Inc.
 ./head -v -n 3 head.c cat.c
==> head.c <==
/* head -- output first part of file(s)
   Copyright (C) 89, 90, 91, 1995-2002 Free Software Foundation, Inc.


==> cat.c <==
/* cat -- concatenate files and print on the standard output.
   Copyright (C) 88, 90, 91, 1995-2002 Free Software Foundation, Inc.

 可以把ubuntu命令列的結果複製到gvim,真是幸福。
我以為,要在本地編譯命令,我是進入到src資料夾下,
再sudo make一把,然後執行本地命令,注意前面的./
然後,再慢慢看。


static void
write_header (const char *filename)
{
  static int first_file = 1;

  printf ("%s==> %s <==\n", (first_file ? "" : "\n"), filename);
  first_file = 0;
}
這個函式控制是否列印檔名,但此處,first_file這個靜態變數感覺有些不明白,每次用之前設定為1,用完又改為0,感覺是多此一舉。
int
main (int argc, char **argv)
{

  have_read_stdin = 0;

  print_headers = 0;

  if (1 < argc && argv[1][0] == '-' && ISDIGIT (argv[1][1]))
    {
    }
   while ((c = getopt_long (argc, argv, "c:n:qv", long_options, NULL)) != -1)
    {處理選項,基本是同一套路,不過,要注意的是,此處是設定全域性變數,然後函式中根據全域性變數進行處理。
    }
   
  if (header_mode == always
      || (header_mode == multiple_files && optind < argc - 1))
    print_headers = 1;

  if (optind == argc)
    exit_status |= head_file ("-", n_units, count_lines);

  for (; optind < argc; ++optind)
    exit_status |= head_file (argv[optind], n_units, count_lines);

  if (have_read_stdin && close (STDIN_FILENO) < 0)
    error (EXIT_FAILURE, errno, "-");

  exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
可以看出,可以一次傳多個檔名,也就是說,head命令要能一次處理多個檔案。如
./head h1 h2 h3 h4
這也符合unix習慣,通過shell展開*之類萬用字元,具體的命令 head要能處理多個檔案。
  for (; optind < argc; ++optind)
    exit_status |= head_file (argv[optind], n_units, count_lines);
再分析head_file
根據-n -c先項,來決定是按行顯示還是按字元個數顯示,其中按字元個數顯示較簡單,程式碼也少些。
static int
head_bytes (const char *filename, int fd, uintmax_t bytes_to_write)
{
  char buffer[BUFSIZE];
  int bytes_read;
  size_t bytes_to_read = BUFSIZE;
 SET_BINARY2 (fd, fileno (stdout));
我把錯誤處理刪除了
  while (bytes_to_write)
    {
      if (bytes_to_write < bytes_to_read)
    bytes_to_read = bytes_to_write;
      bytes_read = safe_read (fd, buffer, bytes_to_read);
      if (bytes_read == 0)
    break;
      if (fwrite (buffer, 1, bytes_read, stdout) == 0)
    error (EXIT_FAILURE, errno, _("write error"));
      bytes_to_write -= bytes_read;
    }
  return 0;
}
這段程式碼較精練,大意就是:
讀bytes_to_write個字元
當沒讀完時
    按緩衝區大小進行讀
    讀到的位元組數寫入bytes_read
    如果讀到0個字元,就表示到了檔案末尾。
    再將讀到的bytes_read個字元寫到標準輸出
    bytes_to_write -= bytes_read
所以這段程式碼很精練,其中safe_read我沒再細究了。

按行讀的函式
head_lines 我沒有看懂,其中有一個錯誤處理:
 while (bytes_to_write < bytes_read)
    if (buffer[bytes_to_write++] == '\n' && --lines_to_write == 0)
      {
        /* If we have read more data than that on the specified number
           of lines, try to seek back to the position we would have
           gotten to had we been reading one byte at a time.  */
        if (lseek (fd, bytes_to_write - bytes_read, SEEK_CUR) < 0)
          {
        int e = errno;
        struct stat st;
        if (fstat (fd, &st) != 0 || S_ISREG (st.st_mode))
          error (0, e, _("cannot reposition file pointer for %s"),
             filename);
          }
        break;
      }
作者呼叫lseek不知是幹什麼?其中當讀到的字元是'\n'時,進行行的計數,這個很關鍵。我喜歡留點兒尾巴。慢慢看,讀程式碼也是一種生活方式。
其中對

  while ((c = getopt_long (argc, argv, "c:n:qv", long_options, NULL)) != -1)
    {
的處理,getopt_long的原始碼一直沒看明白。我讀sed時,讀到這兒,就直想吐,你看懂了嗎?哈哈哈,下次吧。