這個 gist 主要是用以補充課堂上的 Demo 過於臨時倉促而可能沒有讓大家仔細觀察體驗,而這可能才是這堂課最重要的東西我竟然在有人發問之後才想到臨場發揮。
我個人覺得學習 Linux 不必想著一開始就記得所有指令,有什麼需求能夠在網路上搜尋到解決方案就可以了,常用的自然而然就會記起來。因此,以下的 demo 即使不一定能完全看懂,嘗試去猜或者理解,感受一下就行!!
看不太懂很正常也沒關係,可以照著步驟執行看看,體會操作 terminal 的感覺,這些指令都很安全沒有惡意也不會炸掉你的電腦,也可以動手改點參數、數字觀察會發生什麼事!!
回想一下情境,不過現在我們準備的題目是寫一個程式讀入整數 n, 要輸出 n 個整數和的兩倍。我們已經有一亂數產生測資的 Python script gen.py(其實厲害的人都用 "testlib.h" 生測資)和一個官解的程式(中國人愛稱標程?)sol.c. 假如我們想生 10 筆,甚至是 100 筆測資呢?總不可能慢慢打吧。
因此讓我們打開 terminal, 首先可以執行 pwd
(Print Working Directory) 或 echo $PWD
($
代表變數,而 $PWD
是一個 shell 會自己定義的環境變數,他總是會是當下的 working directory) 確定當前的 working directory 與 shell 顯示的是否相同,接著可以利用 ls
常看目錄裡的檔案,cd
至放有 gen.py, sol.c 的目錄。或是 mkdir <名字>
建立一個資料夾,cd
進去後用任何編輯器 (such as vim
...) 建立 gen.py, sol.c. 最後可以 cat
這兩個檔案確定他們的內容。
編譯並測試一下 sol.c:
gcc sol.c -o sol # 平常 IDE 也是呼叫 `gcc` 幫我們編譯 source codes 為 binary file
./sol
# 輸入一些簡單測資,像是:2 87 69
可以試跑看看生測資的 script:
python3 gen.py
如果想要把生測資的結果存起來,可以 redirect python3
的 stdout 至檔案:
python3 gen.py > in.txt
這時理論上 terminal 不會有任何輸出,ls
之後可以發現倒是多了 in.txt 這個檔案,可以 cat
他一下。想要得到我們 C 程式的執行結果,可以把測資 redirect 至其 stdin:
./sol < in.txt
當然也可以把結果存入檔案:
./sol < in.txt > out.txt
之後執行以下指令就可以一次生十筆測資:
for i in {0..9}; do
python3 gen.py > in$i.txt
done
這時候再 ls
一下會發現沒意外的話目錄多了十個檔案。如果想要生一千筆當然也不是問題。
現在請自己動手修改一下範例,試看看利用迴圈把 in0.txt, in1.txt, ..., in9.txt 的標準答案輸出為 out0.txt, out1.txt, ..., out9.txt.
現在你是一個 tester, 要幫我測試我出的題目是否正確。所以請寫一個讀入整數 n, 輸出接下來 n 個整數和的兩倍的程式(顯然會跟我的長得不太一樣)我姑且稱之 test.c 。寫好並編譯你的版本的程式為 ./test 後,如何比較你的輸出有沒有跟我的答案一致?你可以先想想、試試再看下去。
for i in {0..9}; do
./test < in$i.txt > test$i.txt
diff test$i.txt out$i.txt # You can also use `cmp` command
done
如果不在乎測試程式的輸出,甚至可以這樣寫:
for i in {0..9}; do
cmp <(./test < in$i.txt) out$i.txt # You can use `diff` command as well
done
如果我們生好測資之後,只想知道答案的範圍、長相之類的而甚至不想留測資呢?我們可能會這樣:
python3 gen.py > tmp.txt
./sol < tmp.txt
rm tmp.txt
很顯然 shell 會循序執行指令,也就是說,./sol
會等到 python3
跑完才被執行。我們可以利用工廠流水線 (|
這根也被稱作管道 pipe) 的原理,把 python3
的 stdout 直接接到 ./sol
的 stdin 上,當 python3
print 一行 ./sol
就可以 scan 一行。如此一來,顯然流水線的效率較佳:
python3 gen.py | ./sol
你可以把兩種做法分別寫成 shell script 並用迴圈個跑十遍,如 sequential.sh, pipeline.sh, 然後執行 chmod
讓他們可以被執行,並用 time
測量所需的時間:
chmod +x *.sh # Add eXecution right to all .sh files
time ./sequential.sh
time ./pipeline.sh
我這裡自己測 pipeline 大約都 0.67~0.68s, sequential 則是 0.7~0.72s. 當然測資越大越多差異會越明顯。
但使用 pipeline 的話就不能保留 python3 gen.py
生成的測資嗎?這時,有個叫做 tee
的指令就派上用場了。他之所以叫 tee
的原因是因為可以想像他在流水線中就像一個 "Capital" T 或是說管道的分岔,可以把輸入存到一個檔案再傳給流水線的下一個人。因此,其實我們生測資及答案的 shell commands 可以如此改進:
for i in {0..9}; do
python3 gen.py | tee in$i.txt | ./sol > out$i.txt
done
Pipeline 在 Computer Science 中很重要的觀念之一,現代 CPU 不小的平行部分包括指令管線化的設計。
grep
, sed
, find
, awk
等等也都是很好用的指令喔 o'_'o
最近這幾天我都在忙邏實作業所以就拿來舉例,最終我們要把 Verilog codes 丟到系上工作站 simulate. 每次 lab 都有四、五個題目,每次都手打指令真的太苦了,假如有錯還要重來。因此我很早就寫成 shell scripts, 每次只要更新迴圈內容就可以一行跑完所有題目的 simulation.
不過系上工作站是使用 csh
而非 bash
, 語法有一定差異所以只是跟大家分享可以參考參考。
#!/bin/tcsh
rm err.txt > /dev/null # We don't care the error message occured if err.txt is not existed
foreach i (Ping_Pong_Counter FIFO_8 Multi_Bank_Memory Round_Robin_FIFO_Arbiter Parameterized_Ping_Pong_Counter)
ncverilog Lab3_Team30_$i.v Lab3_Team30_${i}_t.v +access+r || echo "- $i" >> err.txt; # write error to other file since `ncverilog` would print a lot
end
if (-e err.txt) then
echo "Error!";
cat err.txt;
else
echo "AC!!";
endif
其他場合也很常用到 shell script 喔。