Trong mã mã {{exec> / dev / null; }> / dev / null, những gì đang xảy ra dưới mui xe?


15

Khi bạn chuyển hướng một danh sách lệnh có chứa chuyển hướng exec, exec> / dev / null dường như vẫn không được áp dụng sau đó, chẳng hạn như với:

{ exec >/dev/null; } >/dev/null; echo "Hi"

"Hi" được in.

Tôi có ấn tượng rằng {}danh sách lệnh không được coi là một nhánh con trừ khi nó là một phần của đường ống, do đó, exec >/dev/nullvẫn nên được áp dụng trong môi trường shell hiện tại trong tâm trí của tôi.

Bây giờ nếu bạn thay đổi nó thành:

{ exec >/dev/null; } 2>/dev/null; echo "Hi"

không có đầu ra như mong đợi; mô tả tập tin 1 vẫn được chỉ vào / dev / null cho các lệnh trong tương lai. Điều này được thể hiện bằng cách chạy lại:

{ exec >/dev/null; } >/dev/null; echo "Hi"

mà sẽ không cho đầu ra.

Tôi đã thử tạo một kịch bản và phân loại nó, nhưng tôi vẫn không chắc chính xác những gì đang xảy ra ở đây.

Tại mỗi điểm trong tập lệnh này, điều gì đang xảy ra với bộ mô tả tệp STDOUT?

EDIT: Thêm đầu ra strace của tôi:

read(255, "#!/usr/bin/env bash\n{ exec 1>/de"..., 65) = 65
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
close(10)                               = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
fstat(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
ioctl(1, TCGETS, 0x7ffee027ef90)        = -1 ENOTTY (Inappropriate ioctl for device)
write(1, "hi\n", 3)                     = 3

Điều đó thật lạ; Tôi không thể tái tạo close(10). Bạn cũng có thể đăng toàn bộ nội dung kịch bản mà bạn đã chạy trên không?
DepressionDaniel

@DepressionDaniel Đây là toàn bộ kịch bản và strace: kịch bản strace
Joey Pabalinas

Bạn có một đi lạc ;sau }, thay đổi ý nghĩa của > /dev/nullviệc không áp dụng cho danh sách ghép {}sau khi tất cả.
DepressionDaniel

@DepressionDaniel Ah, bạn hoàn toàn chính xác! Bây giờ đầu ra là những gì tôi mong đợi; Cảm ơn bạn cho câu trả lời của bạn!
Joey Pabalinas

Câu trả lời:


17

Hãy theo dõi

{ exec >/dev/null; } >/dev/null; echo "Hi"

từng bước một.

  1. Có hai lệnh:

    a. { exec >/dev/null; } >/dev/null, theo dõi bởi

    b. echo "Hi"

    Shell thực thi đầu tiên lệnh (a) và sau đó là lệnh (b).

  2. Việc thực hiện { exec >/dev/null; } >/dev/null tiền thu được như sau:

    a. Đầu tiên, shell thực hiện chuyển hướng>/dev/null và ghi nhớ để hoàn tác nó khi lệnh kết thúc .

    b. Sau đó, shell thực thi{ exec >/dev/null; } .

    c. Cuối cùng, shell chuyển đầu ra tiêu chuẩn trở lại vị trí cũ. (Đây là cơ chế tương tự như trongls -lR /usr/share/fonts >~/FontList.txt - chuyển hướng chỉ được thực hiện trong khoảng thời gian của lệnh mà chúng thuộc về.)

  3. Khi lệnh đầu tiên được thực hiện, shell sẽ thực thi echo "Hi". Đầu ra tiêu chuẩn là bất cứ nơi nào trước lệnh đầu tiên.


Có một số lý do đằng sau tại sao 2a được thực hiện trước 2b? (từ phải sang trái)
Joey Pabalinas

5
Chuyển hướng phải được thực hiện trước lệnh mà chúng áp dụng, không? Làm thế nào họ có thể làm việc khác?
AlexP

Aha, không bao giờ nghĩ về nó theo cách đó! Đầu tiên là hai câu trả lời tuyệt vời; cho nó một chút trước khi tôi quyết định một, nhưng tôi đánh giá cao cả hai lời giải thích!
Joey Pabalinas

Thật không may, tôi chỉ có thể chọn một câu trả lời, vì vậy tôi sẽ chọn câu trả lời này vì nó ít kỹ thuật hơn và do đó tôi nghĩ rằng nó sẽ có thể giúp ngay cả những người dùng ít hiểu biết về công nghệ. Tuy nhiên @DepressionDaniel đã có một câu trả lời tuyệt vời không kém ở đây cung cấp một lời giải thích sâu sắc hơn.
Joey Pabalinas

14

Để không sử dụng lớp vỏ phụ hoặc quy trình con, khi đầu ra của danh sách ghép {}được nối >, lớp vỏ sẽ lưu bộ mô tả STDOUT trước khi chạy danh sách ghép và khôi phục nó sau. Do đó, exec >trong danh sách ghép không mang hiệu ứng của nó vượt qua điểm mà bộ mô tả cũ được khôi phục là STDOUT.

Chúng ta hãy xem phần có liên quan của strace bash -c '{ exec >/dev/null; } >/dev/null; echo hi' 2>&1 | cat -n:

   132  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   133  fcntl(1, F_GETFD)                       = 0
   134  fcntl(1, F_DUPFD, 10)                   = 10
   135  fcntl(1, F_GETFD)                       = 0
   136  fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
   137  dup2(3, 1)                              = 1
   138  close(3)                                = 0
   139  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   140  fcntl(1, F_GETFD)                       = 0
   141  fcntl(1, F_DUPFD, 10)                   = 11
   142  fcntl(1, F_GETFD)                       = 0
   143  fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
   144  dup2(3, 1)                              = 1
   145  close(3)                                = 0
   146  close(11)                               = 0
   147  dup2(10, 1)                             = 1
   148  fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
   149  close(10)                               = 0

Bạn có thể thấy cách, trên dòng 134, bộ mô tả 1( STDOUT) được sao chép vào một bộ mô tả khác với chỉ mục ít nhất 10(đó là cái gì F_DUPFD; nó trả về bộ mô tả có sẵn thấp nhất bắt đầu từ số đã cho sau khi sao chép vào bộ mô tả đó). Cũng xem cách, trên dòng 137, kết quả của open("/dev/null")(mô tả 3) được sao chép vào mô tả 1( STDOUT). Cuối cùng, trên dòng 147, bản STDOUTlưu cũ trên bộ mô tả 10được sao chép lại vào bộ mô tả 1( STDOUT). Hiệu ứng ròng là để cách ly sự thay đổi STDOUTtrên dòng 144(tương ứng với bên trong exec >/dev/null).


Vì FD 1 bị ghi đè bởi FD 3 trên dòng 137, tại sao dòng 141 không được điểm 10 đến / dev / null?
Joey Pabalinas

@JoeyPabalinas Dòng 141 đang sao chép FD 1 (nghĩa là thiết bị xuất chuẩn) sang bộ mô tả có sẵn tiếp theo sau 10 , hóa ra là 11, như bạn có thể thấy trong giá trị trả về từ lệnh gọi hệ thống đó. 10 chỉ được mã hóa cứng thành bash để việc lưu mô tả của bash sẽ không can thiệp vào các mô tả một chữ số mà bạn có thể thao tác trong tập lệnh của mình thông qua exec.
DepressionDaniel

Vì vậy, fcntl (1, F_DUPFD, 10) sẽ luôn đề cập đến STDOUT cho dù FD 1 hiện đang trỏ đến đâu?
Joey Pabalinas

@JoeyPabalinas Không chắc câu hỏi của bạn là gì. FD 1 STDOUT. Họ là những điều tương tự.
DepressionDaniel

Đã thêm đầu ra strace đầy đủ vào bài viết gốc của tôi.
Joey Pabalinas

8

Sự khác biệt giữa { exec >/dev/null; } >/dev/null; echo "Hi"{ exec >/dev/null; }; echo "Hi"là chuyển hướng kép thực hiện dup2(10, 1);trước khi đóng fd 10, đây là bản sao của bản gốc stdout, trước khi chạy lệnh tiếp theo ( echo).

Nó xảy ra theo cách đó bởi vì chuyển hướng bên ngoài thực sự che phủ chuyển hướng bên trong. Đó là lý do tại sao nó sao chép lại stdoutfd gốc sau khi hoàn thành.


+1 để giải thích sự khác biệt một cách dễ dàng. Câu trả lời của AlexP thiếu lời giải thích này.
Kamil Maciorowski
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.