《Head First C 中文版》審讀筆記(四)

黃志斌發表於2013-05-11

八卦新聞:用 RSS 讀新聞

《Head First C 中文版》第 9 章 程式與系統呼叫第 416 頁八卦新聞:用 RSS 讀新聞

RSS 源是網站釋出新聞的常用方式。RSS 源其實就是一個 XML 檔案,裡面有新聞的摘要和連結。當然,你完全有能力寫一個直接從網頁讀取 RSS 檔案的 C 程式,但這涉及一些你沒有接觸過的程式設計概念。為什麼不找一個程式幫忙處理 RSS 檔案呢?

RSS Gossip 是一個 Python 小指令碼,它可以根據某個關鍵字在 RSS 源中查詢新聞。你必須先安裝 Python 才能執行這個指令碼,一旦有了 Python 和 rssgossip.py,就可以像這樣搜尋新聞:

09-00

下面就是在我的 Arch Linux 64-bit 作業系統中的執行結果:

> python2 --version
Python 2.7.4
> python --version
Python 3.3.1

> export RSS_FEED=http://rss.cnn.com/rss/edition.rss

> python2 rssgossip.py 'China'
Why China involved in Africa's health
China reduces banking lifeline to N. Korea
China's youngest comrades 

> python2 rssgossip.py -u 'China'
Why China involved in Africa's health
    http://edition.cnn.com/2013/05/09/opinion/china-africa-health/index.html?eref=edition
China reduces banking lifeline to N. Korea
    http://edition.cnn.com/2013/05/08/business/china-north-korea-banking/index.html?eref=edition
China's youngest comrades 
    http://edition.cnn.com/2013/04/30/world/asia/china-young-communists/index.html?eref=edition

請注意以下幾點:

  • 大多數 Linux 作業系統中已經預裝了 Python 了,一般來說不需要另外再安裝了。但是要注意,本書中的 rssgossip.py 指令碼需要 Python2,不能使用 Python3。而現在很多 Linux 中已經是 Python3 了。如果是這樣,就需要使用 python2 代替 python 。
  • RSS 連結是通過 RSS_FEED 環境變數傳遞給 rssgossip.py 指令碼的。
  • 書中的 RSS 連結是作者編造的,實際上並不存在,需要替換為我們自己找到的 RSS 連結。
  • rssgossip.py 可帶 -u 引數執行,將會顯示新聞的 URL。這一點很重要,在後面我們將會用到。

下面就是 rssgossip.py 源程式。也可以到文末的參考資料[2]中下載。

import urllib
import os
import re
import sys
import string
import unicodedata
import getopt
from xml.dom import minidom

def usage():
    print("Usage:\npython rssgossip.py [-uh] <search-regexp>")

try:
    opts, args = getopt.getopt(sys.argv[1:], "uh", ["urls", "help"])
except getopt.GetoptError, err:
    print str(err)
    usage()
    sys.exit(2)

include_urls = False
for o, a in opts:
    if o == "-u":
        include_urls = True
    elif o in ("-h", "--help"):
        usage()
        sys.exit()
    else:
        assert False, "unhandled option"

searcher = re.compile(args[0], re.IGNORECASE)
for url in string.split(os.environ['RSS_FEED']):
    feed = urllib.urlopen(url)
    try:
        dom = minidom.parse(feed)
        forecasts = []
        for node in dom.getElementsByTagName('title'):
            txt = node.firstChild.wholeText
            if searcher.search(txt):
                txt = unicodedata.normalize('NFKD', txt).encode('ascii', 'ignore')
                print(txt)
                if include_urls:
                    p = node.parentNode
                    link = p.getElementsByTagName('link')[0].firstChild.wholeText
                    print("\t%s" % link)
    except:
        sys.exit(1)

案例研究:在瀏覽器中開啟新聞

本書第 10 章 程式間通訊第 444 頁案例研究:在瀏覽器中開啟新聞

假設你想在瀏覽器中開啟 rssgossip.py 指令碼找到的新聞連結,你將在父程式中執行程式,在子程式中執行 rssgossip.py 。需要建立管道,把 rssgossip.py 的輸出和程式的輸入連線起來。

程式碼熟食:在瀏覽器中開啟網頁

第 446 頁程式碼熟食:在瀏覽器中開啟網頁

程式需要用機器上的瀏覽器開啟網頁,但不同作業系統與程式互動的方式不同,因此實現起來多少有些難度。

好在兼職演員已經為你寫好了這部分程式碼,它能夠在絕大多數系統上開啟網頁。但他們好像還有要事在身,所以只採用了最簡單的實現方式:

void open_url(const char *url)
{
  char launch[255];
  sprintf(launch, "cmd /c start %s", url); // 在Windows上開啟網頁。
  system(launch);
  sprintf(launch, "x-www-browser '%s' &", url); // 在Linux上開啟網頁。
  system(launch);
  sprintf(launch, "open '%s'", url);       // 在Mac上開啟網頁。
  system(launch);
}

程式碼用了三條命令,它們分別可以在 Mac、Windows 和 Linux 上開啟 URL 。每次都有兩條命令會失敗,但只要有一條成功就行了。

實際上,在我的 Linux 機器上,三條命令都失敗了。這是怎麼回事?原來, x-www-browser 一般用於 Ubuntu 等 Linux 發行版中。雖然 Ubuntu 很多人使用,而且我以前也用過,但現在我用的是 Arch Linux,它不支援 x-www-browser 。解決方案是使用 xdg-open 代替,它可用於大多數 Linux 作業系統。而且 xdg-open 不僅可以開啟網頁,而且可以開啟作業系統支援的任意檔案,這和上述程式中其它兩個作業系統的做法一致。這樣,open_url 函式也可以改名為 open_thing 函式。詳細資訊請參見文末的參考資料[3]。

連線管道

第 448 頁連線管道:練習解答

大部分程式碼已經寫好了,你只需要填寫用管道連線父子程式的那部分。為了節約空間,我們去掉了 #include 語句、error() 和 open_url() 函式。

int main(int argc, char *argv[])
{
  char *phrase = argv[1];        // 下一行的 RSS 可以根據實際需要替換。
  char *vars[] = {"RSS_FEED=http://rss.cnn.com/rss/edition.rss", NULL};
  int fd[2];                     // 這個陣列將儲存管道描述符
  if (pipe(fd) == -1) {          // 建立管道,並把描述符儲存在fd[0]和fd[1]中。
    error("Can't create pipe");  // 為了防止管道建立失敗,需要檢查pipe()的返回值。
  }
  pid_t pid = fork();
  if (pid == -1) {
    error("Can't fork process");
  }
  if (!pid) {       // 這裡你在子程式中。
    dup2(fd[1], 1); // 把標準輸出設為管道的寫入端
    close(fd[0]);   // 子程式不會讀取管道,所以我們將關閉讀取端
    if (execle("/usr/bin/python2", "/usr/bin/python2", "rssgossip.py",
               "-u", phrase, NULL, vars) == -1) { // -u 讓指令碼顯示新聞URL。
      error("Can't run script");
    }
  }
  dup2(fd[0], 0); // 從這裡開始你就在父程式中。
  close(fd[1]);   // 關閉管道的寫入端,因為父程式不需要向管道寫資料。
  char line[255];
  while (fgets(line, 255, stdin)) { // 將從標準輸入讀取資料,因為管道連到了標準輸入。
    if (line[0] == '\t')   // 如果line以Tab開頭...     //上一行的stdin也可以用fd[0]。
      open_url(line + 1);  // ...就說明它是URL。
  }
  return 0;
}

注意, 在上述程式碼中:

  • 我們使用自己的 RSS 源替換了書中虛構的 RSS 源。
  • 使用 /usr/bin/python2 替換了 /usr/bin/python 。

夜未眠:還在為錯誤程式碼煩惱?

本書第 434 頁夜未眠:還在為錯誤程式碼煩惱?

每次你在系統呼叫時都需要反反覆覆寫那些錯誤處理程式碼。還猶豫什麼!趕快使用我們的獨家祕方,我們將向你展示如何重用錯誤程式碼,從此你將告別重複程式碼:

……

首先,需要把處理程式碼放到一個單獨的 error() 函式中,然後把 return 語句換成 exit() 系統呼叫。

void error(const char *msg)
{
  fprintf(stderr, "%s: %s\n", msg, strerror(errno));
  exit(1); // exit(1)會立刻終止程式,並把退出狀態置1。
}

現在就可以把那些煩人的錯誤檢查程式碼換成:

……

警告:每次程式執行只有一次呼叫 exit() 的機會,“程式突然結束恐懼症”患者慎用。

補上 include 語句

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>

編譯程式碼並執行程式

本書第 449 頁(使用我機器上的執行結果代替了書中的):

編譯程式碼並執行程式,出現了:

> gcc news_opener.c -o news_opener
> ./news_opener 'China'
sh: cmd: 未找到命令
sh:行1: open: 未找到命令
sh: cmd: 未找到命令
sh:行1: open: 未找到命令
sh: cmd: 未找到命令
sh:行1: open: 未找到命令
已在現有的瀏覽器會話中建立新的視窗。
已在現有的瀏覽器會話中建立新的視窗。
已在現有的瀏覽器會話中建立新的視窗。
>

10-00

太棒了,程式工作了。

news_opener 程式在一個獨立的程式中執行了 rssgossip.py,並讓它顯示找到新聞的 URL 。所有本來應該傳送到螢幕上的輸出現在通過管道重定向到 news_opener 父程式,news_opener 就可以在瀏覽器中開啟新聞了。

管道是連線程式的好辦法。現在你不但能夠執行程式,控制它們的環境,而且還能獲取程式的輸出,這樣就可以實現很多功能。任何一個能夠在命令列中執行的程式你都可以在 C 程式碼中呼叫並控制它。

參考資料

  1. 《Head First C 中文版》
  2. GitHub: dogriffiths/rssgossip
  3. Stack Overflow: linux - Compatibility of x-www-browser
  4. Linux manual page: fork(2)
  5. Linux manual page: pipe(2)
  6. Linux manual page: exit(3)
  7. Python Programming Language - Official Website

相關文章