Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 41.224 phút đọc

dup, dup2, và chuyển hướng

Sau bài này bạn sẽ làm được

  • Giải thích dup và dup2 làm gì
  • Chuyển hướng stdout và stderr bằng dup2
  • Cài lại chuyển hướng ra tệp bằng open cộng dup2
  • Không rò rỉ mô tả tệp

Khi bạn gõ cmd > file trong shell, đầu ra của cmd đi vào tệp thay vì màn hình. Cơ chế đằng sau chỉ là hai lời gọi: dup2 và close. Bài này chạy được trên MinGW, nên các bản ghi là thật.

#dup và dup2

Chữ ký
#include <unistd.h>

int dup(int oldfd);
/* nhan ban oldfd, tra ve fd MOI NHO NHAT con trong,
   ca hai fd tro toi CUNG mot tep */

int dup2(int oldfd, int newfd);
/* nhan ban oldfd vao DUNG newfd (dong newfd truoc neu dang mo),
   ca hai tro toi cung mot tep */
Nhân bản mô tả tệp
dup và dup2 tạo một mô tả tệp thứ hai trỏ tới cùng một tệp mở như cái đầu. Ghi qua cái nào cũng vào cùng tệp, và chúng chia sẻ cùng vị trí đọc ghi. Đóng một cái không ảnh hưởng cái kia.

#Chuyển hướng ra tệp

cmd > file, bằng open + dup2
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(void) {
    int fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);

    dup2(fd, STDOUT_FILENO);        /* moi ghi vao stdout gio vao out.txt */
    close(fd);                      /* khong can fd goc nua, stdout da tro dung cho */

    printf("Dong nay vao file\n");  /* printf ghi vao stdout = out.txt */
    return 0;
}

#stdout và stderr riêng

redir.c, tách hai luồng ra hai tệp
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(void) {
    int out = open("ra.txt",  O_WRONLY | O_CREAT | O_TRUNC, 0644);
    int err = open("loi.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);

    dup2(out, STDOUT_FILENO);       /* stdout -> ra.txt */
    dup2(err, STDERR_FILENO);       /* stderr -> loi.txt */
    close(out);
    close(err);

    fprintf(stdout, "dong nay vao ra.txt\n");
    fprintf(stderr, "dong nay vao loi.txt\n");
    fflush(NULL);
    return 0;
}
terminal
gcc -O2 -Wall -Wextra redir.c -o redir.exe && ./redir.exe
cat ra.txt
dong nay vao ra.txt
cat loi.txt
dong nay vao loi.txt

#Cơ chế của shell

Chuyển hướng trong con, sau fork trước exec
/* Shell chay "ls > out.txt" nhu the nay: */
pid_t pid = fork();
if (pid == 0) {
    /* CON: thiet lap chuyen huong TRUOC khi exec */
    int fd = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    dup2(fd, STDOUT_FILENO);        /* stdout cua con -> out.txt */
    close(fd);

    execlp("ls", "ls", NULL);       /* ls in ra stdout = out.txt */
    _exit(127);
}
waitpid(pid, NULL, 0);

/* Diem then chot: chuyen huong xay ra trong CON, GIUA fork va exec.
   - Con thua huong bang fd cua cha (nho fork).
   - Con doi stdout cua RIENG no sang tep.
   - Con exec thanh ls; ls in ra stdout, nhung stdout gio la tep.
   - CHA khong bi anh huong: stdout cua cha van la terminal.

   Do la ly do Unix tach fork va exec: khoang GIUA hai buoc do
   la cho shell thiet lap chuyen huong, pipe, nhom tien trinh.
   Xem Bai 43.2 va du an Mini Shell (60.4). */
Chuyển hướng trong cha
/* Doi stdout cua CHA truoc khi fork */ dup2(fd, STDOUT_FILENO); fork(); /* Bay gio CA cha va con deu co stdout tro toi tep. Shell mat duong ra terminal, moi lenh sau deu ghi vao tep. Hong. */
Chuyển hướng trong con
/* Doi stdout chi trong CON */ if (fork() == 0) { dup2(fd, STDOUT_FILENO); /* chi con bi anh huong */ execlp(...); _exit(127); } /* Cha giu nguyen stdout, tiep tuc nhac lenh binh thuong. */

Tự làm thử

  1. Chuyển hướng stdout ra một tệp bằng dup2 và xác nhận printf vào tệp.
  2. Chuyển hướng stdout và stderr ra hai tệp khác nhau.
  3. Cài cmd > out.txt 2>&1 và giải thích thứ tự dup2.
  4. Viết chương trình fork cộng chuyển hướng cộng exec để chạy ls ghi vào tệp.
  5. Chứng minh chuyển hướng trong cha làm hỏng shell còn trong con thì không.
  6. Dùng lseek để viết tail -n N đọc N dòng cuối của một tệp.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • dup và dup2 tạo mô tả tệp thứ hai trỏ tới cùng tệp mở; dup2 cho chọn số đích.
  • Chuyển hướng ra tệp là ba bước: open, dup2 vào STDOUT_FILENO, close fd gốc.
  • 2>&1 là dup2 sao chép đích hiện tại của stdout sang stderr; thứ tự quan trọng.
  • Shell chuyển hướng trong con, giữa fork và exec, để không ảnh hưởng chính shell.
  • lseek di chuyển vị trí đọc ghi; không dùng được với ống, socket, terminal.