誰常上機?


產生統計資料

I/O redirection 圖示

last 指令可以查詢最近一段時間, 誰曾經登入系統。 今天我們的任務是統計一下所有人使用這部主機的頻率與時間, 並畫成圖表。

第一部分我們先只統計每人登入的次數。

  1. last 看一下系統總共有多少人次的登入記錄?
  2. 我們只對本班同學有興趣: last | perl -ne 'print if /^s4113/'
  3. 把這部分的結果存起來: last | perl -ne 'print if /^s4113/' > lastlog.txt
  4. 我們只對 user name 欄位有興趣: perl -pe 's/ .*//' < lastlog.txt
  5. 排序一下, 把同一位使用者的資料集中在一起: perl -pe 's/ .*//' < lastlog.txt | sort
  6. 再數一下每個人出現的次數: perl -pe 's/ .*//' < lastlog.txt | sort | uniq -c
  7. 按照出現頻率排序, 由多到少: perl -pe 's/ .*//' < lastlog.txt | sort | uniq -c | sort -nr
  8. 取前廿名: perl -pe 's/ .*//' < lastlog.txt | sort | uniq -c | sort -nr | head -20
  9. 將最後結果存起來: perl -pe 's/ .*//' < lastlog.txt | sort | uniq -c | sort -nr | head -20 > freq.txt 產生類似這樣的 freq.txt

產生 freq.txt 的過程

這裡的 "|" 叫做 pipe, 作用是將前一個指令的輸出, 餵給下一個指令當它的輸入。 如果前一個指令本來就想印到 standard output 標準輸出裝置, 後一個指令本來就想從 standard input 標準輸入裝置 讀資料, 那麼就可以用 pipe 連接起來。 用白話文講, 如果前一個指令原本要印到螢幕上; 後一個指令原本要 "癡癡地等" 從鍵盤讀資料, 那麼 pipe 的作用正好讓前者的輸出直接變成後者的輸入。 last | perl -pe 's/ .*//' 的效果跟 last > lastlog.txt ; perl -pe 's/ .*//' < lastlog.txt 兩句合起來的效果一樣; 只不過後者會多產生一個中間過程檔案。 (註: perl -ne 'print if /^s4113/'grep '^s4113' 的效果一樣; 而 perl -pe 's/ .*//'sed 's/ .*//' 的效果一樣。 圖中採用簡寫版本。)

製作圖表

我們將使用 gnuplot 製作圖表。 打 gnuplot 之後, 進入它的環境, 現在開始與我們交談的程式不再是 bash, 而是 gnuplot。 使用 knoppix 光碟或是從 Windows 下用 XLiveCD 連線到學校主機的同學, 應該會看到 "Terminal type set to 'x11'" 的訊息。 從 Windows 下用 telnet 或 putty 連線的同學, 看到的不是 x11 而是 unknown, 這時只好下 set term dumb 用笨笨的終端機模式畫圖。

gnuplot 跟其他許多文字模式的交談式程式一樣, 也是按 ^d 離開。 先畫幾個簡單的圖, 熟悉一下 gnuplot 的命令:

  1. plot x*x-5
  2. plot sin(x)/x
  3. splot x*x-y*y
  4. plot "freq.txt"
  5. plot "freq.txt" with boxes
  6. help plot (這篇 help 很長, 裡面有很多子題 subtopcs, 閱讀時請留意下方的訊息。)

我們希望在 x 軸下方標示出 username。

  1. 先隨便亂實驗一下: set xtics ("abc" 0, "def" 1, "xyz" 2)
  2. 要重畫時, 標示才會出現: replot
  3. 轉 90 度, 字才不會疊在一起: set xtics rotate ("abc" 0, "def" 1, "xyz" 2); relpot
  4. (較新版本的 gnuplot) 如果覺得左下角的滑鼠座標礙眼, 可以叫它不要印: unset mouse
  5. 下面好像太擠了。 這是 margin 參數在管的。 先查一下: show margin 再改適當的設定值: set bmargin 5 再查看一次: show margin 最後重畫: replot

什麼, 要把所有 id 全部用手一個一個敲進去!? 機械化, 重複性的動作, 由人來做, 這樣對嗎? 請開另外一個命令列視窗, 在 bash 底下: (魔術表演, 暫時不理解沒有關係) perl -ne 'print qq("$1" ) , $.-1 , ",\\\n" if /\d+\s+(\w+)/' freq.txt > go.gpt 用編輯器進入 go.gpt, 在最上面加上一列 "set xtics (\" 並將最下面一列尾巴的 ",\" 改成 ")"。 可以猜得出來, 這裡每列最後面的 "\" 表示 "這個指令還沒打完, 但不得已要換列, 請暫時先不要處理"。 回到 gnuplot 視窗, 下 load "go.gpt" 最後 replot 大功告成。 (圖案看起來有點醜嗎? 上面故意漏了一點東西...請自己修改。)

最後要把圖存檔, 方便日後使用 (例如貼到文件裡面去)。

  1. 看一下目前的驅動程式: show term
  2. 看一下目前的輸出檔名: show output
  3. 改採 png 格式輸出: set term png
  4. 輸出到 freq.png 檔案裡面去: set output "freq.png"
  5. 重畫: replot 螢幕沒有任何變化; 但家裡多出一個 freq.png 檔。
  6. 用 file 檢查它的屬性; 用 ee 或 xli 或 xloadimage 看圖。
  7. 再將輸出切回螢幕, 以免以後畫圖都畫到檔案裡面去, 根本看不見: set term x11; set output (若是使用 windows 版, 請將 x11 改成 windows)。

上機次數統計表

統計登入次數及連線總時數

這一節, 我們的目的是要以每個人登入的次數及連線總分鐘數當做 x-y 座標, 畫出像這樣的圖:

上機次數-分鐘數統計表

首先計算每人連線總分鐘數: (魔術表演, 暫時不理解沒有關係) perl -ne '$f{$1}+=$2*60+$3 if /^(\w+).*\((\d\d):(\d\d)\)/ ; END { foreach (keys %f) { printf "$_:$f{$_}\n"; } }' lastlog.txt > time.txt

接下來在 gnuplot 當中試用一下 set label ... 指令:

set label "good" at 3,1
set label "ok" at 1,3
plot sin(x)
plot x*x
set xrange [0:5]
set yrange [0:5]
replot

可以看出: set label 單獨使用並沒有效果, 還是必須等 plot 指令執行後才會出現。 而且 gnuplot 自動調整繪圖範圍的依據, 是 plot 的函數, 而不是 label。 所以必須自己設定 xrange 與 yrange。

所以我們必須從 freq.txttime.txt 產生出 freq-time.gpt。 以下動作, 請自行分解, 把中間過程攔截下來觀察, 不要只管產生結果。 首先調整 freq.txt 兩欄的順序, 把 id 調到前面, 次數放到後面, 並且依 id 排序: perl -ne 'print "$2:$1\n" if /(\w+)\s+(\w+)/' freq.txt | sort > a 同樣地, 依 id 為 time.txt 排序: sort time.txt > b 最後把兩個檔案並排, 並且轉成 gnuplot 的指令: join -t : a b | perl -ne 'print qq(set label "$1" at $2,$3 center\n) if /(\w+):(\w+):(\w+)/' > freq-time.gpt 然後就可以進入 gnuplot, 開始畫圖:

        load "freq-time.gpt"
        plot 0
        set xrange [0:50]
        set yrange [0:2000]
        replot

Q: 如何迅速查出 y 的範圍? 一樣用 sort, 不過需要 -t 這個 option。

像這種大部分資料擠在左下角的圖, 如果改用 logscale 畫, 效果會比較好一點。 但要用 logscale, 資料裡面就不可以有 0 或負數。 perl -ne 'print unless /,0 /' freq-time.gpt > freq-time-nozero.gpt

        load "freq-time-nozero.gpt"
        set logscale
        set xrange [12:50]
        set yrange [300:2000]
        plot 0

提醒: 這篇講義的重點不是統計次數/時間, 而是 如何處理有規律的文字檔以產生統計圖表。 它的應用場合非常廣泛, 不限於此。