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ử
- Cài
ls -l | wc -lbằngpipe,fork,dup2và so với lệnh shell thật. - Quên
close(fd[1])ở cha và quan sát chương trình treo. - Mở rộng thành ba lệnh
ls | grep | wcvới hai pipe. - Viết vòng lặp nối N lệnh với N-1 pipe, N lấy từ đối số.
- Chứng minh
SIGPIPE: ghi vào pipe sau khi đóng đầu đọc. - Bỏ qua
SIGPIPEvà xử lýEPIPEthay 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à
pipecộngdup2: mỗi con đổi stdout hoặc stdin của nó tới một đầu ống, giữaforkvà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ýEPIPEcho chương trình mạng.