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

Cài cmd1 nối cmd2

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

  • Nối đầu ra một lệnh vào đầu vào lệnh khác
  • Giải thích vì sao cha phải đóng cả hai đầu
  • Mở rộng thành ba lệnh với hai pipe
  • Xử lý SIGPIPE khi bên đọc đóng

ls -l | wc -l là hai chương trình nối bằng một ống. Bài này cài chính cái mà shell làm khi thấy dấu |: pipe để tạo ống, dup2 để nối đầu ra lệnh này vào đầu vào lệnh kia.

#Ý tưởng

# Khi ban go trong shell:
ls -l | wc -l

# Shell lam:
#   1. Tao mot pipe.
#   2. fork con 1 (ls): doi STDOUT cua no vao dau GHI pipe.
#   3. fork con 2 (wc): doi STDIN cua no vao dau DOC pipe.
#   4. ls in ra stdout = vao pipe -> wc doc tu pipe = stdin cua no.

  ls  --stdout-->  [ pipe ]  --stdin-->  wc

/* Diem then chot: ket hop dup2 (Bai 41.2) voi pipe (Bai 43.1).
   dup2 doi stdout/stdin cua tung con tro toi mot dau cua pipe,
   nen ls va wc noi voi nhau ma khong biet, chung chi ghi stdout
   va doc stdin nhu binh thuong. */

#Nối hai lệnh

Cài ls -l | wc -l
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    int fd[2];
    pipe(fd);

    if (fork() == 0) {                  /* con 1: ls */
        dup2(fd[1], STDOUT_FILENO);     /* stdout -> dau ghi pipe */
        close(fd[0]);
        close(fd[1]);                   /* da dup2, dong ban goc */
        execlp("ls", "ls", "-l", NULL);
        _exit(127);
    }

    if (fork() == 0) {                  /* con 2: wc */
        dup2(fd[0], STDIN_FILENO);      /* stdin <- dau doc pipe */
        close(fd[0]);
        close(fd[1]);
        execlp("wc", "wc", "-l", NULL);
        _exit(127);
    }

    close(fd[0]);                       /* CHA dong CA HAI dau */
    close(fd[1]);
    wait(NULL);
    wait(NULL);
    return 0;
}
terminal
gcc pipe_lenh.c -o pipe_lenh && ./pipe_lenh
7
# Bang voi lenh shell that
ls -l | wc -l
7

#Cha phải đóng cả hai

Cha giữ đầu ghi
/* fork ca hai con... */ close(fd[0]); /* cha dong dau doc */ /* QUEN close(fd[1]): cha van giu dau ghi */ wait(NULL); /* wc khong thay EOF -> treo -> wait cho mai */
Cha đóng cả hai
/* fork ca hai con... */ close(fd[0]); close(fd[1]); /* cha dong CA HAI */ wait(NULL); wait(NULL); /* wc thay EOF dung cach -> in ket qua -> xong */

#Ba lệnh, hai pipe

cmd1 | cmd2 | cmd3
/* Vi du: ls -l | grep .c | wc -l
   Ba lenh, HAI pipe. */
int p1[2], p2[2];
pipe(p1);           /* noi cmd1 -> cmd2 */
pipe(p2);           /* noi cmd2 -> cmd3 */

if (fork() == 0) {                  /* cmd1: ls */
    dup2(p1[1], STDOUT_FILENO);     /* ghi vao p1 */
    close(p1[0]); close(p1[1]);
    close(p2[0]); close(p2[1]);
    execlp("ls", "ls", "-l", NULL); _exit(127);
}
if (fork() == 0) {                  /* cmd2: grep */
    dup2(p1[0], STDIN_FILENO);      /* doc tu p1 */
    dup2(p2[1], STDOUT_FILENO);     /* ghi vao p2 */
    close(p1[0]); close(p1[1]);
    close(p2[0]); close(p2[1]);
    execlp("grep", "grep", ".c", NULL); _exit(127);
}
if (fork() == 0) {                  /* cmd3: wc */
    dup2(p2[0], STDIN_FILENO);      /* doc tu p2 */
    close(p1[0]); close(p1[1]);
    close(p2[0]); close(p2[1]);
    execlp("wc", "wc", "-l", NULL); _exit(127);
}

close(p1[0]); close(p1[1]);         /* CHA dong HET moi dau */
close(p2[0]); close(p2[1]);
wait(NULL); wait(NULL); wait(NULL);

#SIGPIPE

terminal
# yes gui mai, head doc 3 dong roi dong -> yes nhan SIGPIPE
yes | head -3
y
y
y

Tự làm thử

  1. Cài ls -l | wc -l bằng pipe, fork, dup2 và so với lệnh shell thật.
  2. Quên close(fd[1]) ở cha và quan sát chương trình treo.
  3. Mở rộng thành ba lệnh ls | grep | wc với hai pipe.
  4. Viết vòng lặp nối N lệnh với N-1 pipe, N lấy từ đối số.
  5. Chứng minh SIGPIPE: ghi vào pipe sau khi đóng đầu đọc.
  6. Bỏ qua SIGPIPE và xử lý EPIPE thay vì bị giết.

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

  • Nối lệnh là pipe cộng dup2: mỗi con đổi stdout hoặc stdin của nó tới một đầu ống, giữa fork và exec.
  • Sau dup2, con đóng cả hai đầu gốc của pipe; giữ đường nối qua stdin/stdout.
  • Cha phải đóng cả hai đầu, nếu không bên đọc không thấy EOF và treo.
  • N lệnh cần N-1 pipe; mỗi tiến trình đóng mọi đầu nó không dùng, đây là chỗ dễ sai nhất.
  • Ghi vào ống không còn ai đọc sinh SIGPIPE; bỏ qua nó và xử lý EPIPE cho chương trình mạng.