重導向與 dup/dup2
/ dup -> dupe /
想像一台機器伸出三根管子,標著「輸入」、「輸出」、「錯誤」。預設情況下它們通往鍵盤與螢幕。重導向就是把其中一根管子改接到別處——讓輸出進入一個檔案而非螢幕——卻不改動機器本身。dup 與 dup2 就是做這個改接的水管工具。
具體來說,重導向之所以可行,是因為有描述符表。程式寫往標準輸出,靠的是寫往檔案描述符 1;它不知道、也不在乎 fd 1 接到了什麼。所以若你在程式執行前就安排好 fd 1 指向某個檔案,它的所有輸出就會悄悄落進那個檔案。dup(oldfd) 在最小的空格裡複製一個描述符,兩者都指向同一個開啟檔案結構(所以共用偏移量)。dup2(oldfd, newfd) 是精確版:它讓 newfd 指向與 oldfd 相同的開啟檔案,並在 newfd 原本已開啟時先把它關掉——這是重導向的主力。經典做法:開啟目標檔案(假設它落在 fd 5),接著呼叫 dup2(5, 1) 使 fd 1 現在指向該檔案,然後 close(5);從此每一次寫往標準輸出都進入那個檔案。shell 在 fork() 與 exec() 之間做的正是這件事,以實作「> out.txt」。
為何重要:這是 shell 重導向(> 與 <)背後的機制,也是管線的基礎。深層的理念是:程式寫往「什麼」(一個固定的描述符號碼)與那個描述符通往「哪裡」(可從外部重新安排)之間的分離。因為程式只用 fd 0、1、2,周圍的 shell 或父行程就能把它們接到檔案、管線或其他程式,而程式本身渾然不覺——這正是 Unix 工具能如此自由組合的原因。
/* 讓標準輸出進入 out.txt,就像 shell 為「> out.txt」所做的那樣 */ int fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); dup2(fd, 1); /* fd 1(標準輸出)現在指向 out.txt */ close(fd); /* 那個多餘的描述符不再需要了 */
做完 dup2(fd, 1) 後,每個 printf 和寫往標準輸出的 write 都落進 out.txt——程式渾然不覺。
dup2(oldfd, newfd) 會在 newfd 原本已開啟時先關掉它,再使它成為複本——這兩者共用一個開啟檔案描述、因而共用一個檔案偏移量,這跟各自呼叫兩次 open() 不同。重導向之所以可行,全因程式針對的是一個描述符號碼,而非一個固定的目的地。