Sử dụng thực tế để di chuyển mô tả tập tin


16

Theo trang bash man:

Toán tử chuyển hướng

   [n]<&digit-

di chuyển bộ mô tả tệp digitsang bộ mô tả tệp nhoặc đầu vào tiêu chuẩn (bộ mô tả tệp 0) nếu nkhông được chỉ định. digitđược đóng lại sau khi được ghép đôi n.

"Di chuyển" một mô tả tập tin sang một cái khác có nghĩa là gì? Các tình huống điển hình cho thực hành như vậy là gì?

Câu trả lời:


14

3>&4-là một tiện ích mở rộng ksh93 cũng được hỗ trợ bởi bash và đó là viết tắt của 3>&4 4>&-3, hiện tại 3 điểm chỉ đến nơi 4 được sử dụng và 4 hiện đang đóng, vì vậy, cái được chỉ ra bởi 4 giờ đã chuyển sang 3.

Việc sử dụng thông thường sẽ là trong trường hợp bạn đã sao chép stdinhoặc stdoutlưu một bản sao của nó và muốn khôi phục nó, như trong:

Giả sử bạn muốn chụp stderr của một lệnh (và chỉ stderr) trong khi để stdout một mình trong một biến.

Thay thế lệnh var=$(cmd), tạo đường ống. Đầu viết của ống trở thành cmdthiết bị xuất chuẩn (mô tả tệp 1) và đầu kia được đọc bởi trình bao để điền vào biến.

Bây giờ, nếu bạn muốn stderrđi đến biến, bạn có thể làm : var=$(cmd 2>&1). Bây giờ cả fd 1 (stdout) và 2 (stderr) đều đi đến đường ống (và cuối cùng là biến), đây chỉ là một nửa so với những gì chúng ta muốn.

Nếu chúng ta làm var=$(cmd 2>&1-)(viết tắt var=$(cmd 2>&1 >&-), bây giờ chỉ có cmdstderr đi vào đường ống, nhưng fd 1 đã bị đóng. Nếu cmdcố gắng ghi bất kỳ đầu ra nào, nó sẽ trả về EBADFlỗi, nếu nó mở một tệp, nó sẽ nhận được fd miễn phí đầu tiên và tệp mở sẽ được gán cho nó stdouttrừ khi các lệnh bảo vệ chống lại điều đó! Không phải những gì chúng ta muốn.

Nếu chúng ta muốn thiết bị xuất chuẩn bị cmdbỏ lại một mình, nghĩa là chỉ đến cùng một tài nguyên mà nó đã chỉ ra bên ngoài thay thế lệnh, thì chúng ta cần bằng cách nào đó để đưa tài nguyên đó vào trong thay thế lệnh. Cho rằng chúng ta có thể thực hiện một bản sao stdout bên ngoài thay thế lệnh để đưa nó vào bên trong.

{
  var=$(cmd)
} 3>&1

Đó là một cách sạch hơn để viết:

exec 3>&1
var=$(cmd)
exec 3>&-

(cũng có lợi ích của việc khôi phục fd 3 thay vì đóng nó cuối cùng).

Sau đó, trên {(hoặc exec 3>&1) và lên đến }, cả fd 1 và 3 đều trỏ đến cùng một tài nguyên fd 1 được trỏ đến ban đầu. fd 3 cũng sẽ trỏ đến tài nguyên đó bên trong thay thế lệnh (thay thế lệnh chỉ chuyển hướng fd 1, stdout). Vì vậy, ở trên, cmdchúng tôi đã có các fds 1, 2, 3:

  1. đường ống đến var
  2. hoang sơ
  3. giống như những gì 1 điểm bên ngoài thay thế lệnh

Nếu chúng ta thay đổi nó thành:

{
  var=$(cmd 2>&1 >&3)
} 3>&1-

Sau đó, nó trở thành:

  1. giống như những gì 1 điểm bên ngoài thay thế lệnh
  2. đường ống đến var
  3. giống như những gì 1 điểm bên ngoài thay thế lệnh

Bây giờ, chúng ta đã có những gì chúng ta muốn: stderr đi đến đường ống và thiết bị xuất chuẩn không bị ảnh hưởng. Tuy nhiên, chúng tôi đang rò rỉ rằng fd 3 đến cmd.

Trong khi các lệnh (theo quy ước) giả sử fds 0 đến 2 là mở và là đầu vào, đầu ra và lỗi tiêu chuẩn, chúng không giả sử bất cứ thứ gì của các fds khác. Nhiều khả năng họ sẽ để lại fd 3 mà không bị ảnh hưởng. Nếu họ cần một bộ mô tả tệp khác, họ sẽ chỉ làm một open()/dup()/socket()...bộ mô tả tệp có sẵn đầu tiên. Nếu (giống như một tập lệnh shell nào exec 3>&1) mà họ cần sử dụng fdcụ thể, trước tiên họ sẽ gán nó cho một cái gì đó (và trong quá trình đó, tài nguyên do fd 3 của chúng tôi nắm giữ sẽ được phát hành theo quy trình đó).

Đó là một thực hành tốt để đóng fd 3 vì cmdkhông sử dụng nó, nhưng sẽ không có vấn đề gì lớn nếu chúng tôi để nó được chỉ định trước khi chúng tôi gọi cmd. Các vấn đề có thể là: rằng cmd(và có khả năng các quá trình khác mà nó sinh ra) có ít hơn một fd có sẵn cho nó. Một vấn đề nghiêm trọng hơn tiềm năng là nếu tài nguyên mà fd trỏ đến có thể bị giữ bởi một quá trình được sinh ra bởi cmdnền đó. Nó có thể là một mối quan tâm nếu tài nguyên đó là một đường ống hoặc kênh liên lạc giữa các quá trình khác (như khi tập lệnh của bạn đang được chạy script_output=$(your-script)), vì điều đó có nghĩa là quá trình đọc từ đầu kia sẽ không bao giờ thấy kết thúc của tập tin cho đến khi quá trình nền chấm dứt.

Vì vậy, ở đây, tốt hơn là viết:

{
  var=$(cmd 2>&1 >&3 3>&-)
} 3>&1

Mà, với bashcó thể rút ngắn thành:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1

Để tóm tắt lý do tại sao nó hiếm khi được sử dụng:

  1. đó là đường không chuẩn và chỉ là cú pháp. Bạn đã phải cân bằng việc lưu một vài tổ hợp phím bằng cách làm cho tập lệnh của bạn ít di động hơn và ít rõ ràng hơn đối với những người không quen với tính năng không phổ biến đó.
  2. Nhu cầu đóng fd ban đầu sau khi sao chép nó thường bị bỏ qua vì hầu hết thời gian, chúng tôi không phải chịu hậu quả, vì vậy chúng tôi chỉ làm >&3thay vì >&3-hoặc >&3 3>&-.

Bằng chứng là nó hiếm khi được sử dụng, như bạn phát hiện ra rằng nó không có thật trong bash . Trong bash compound-command 3>&4-hoặc any-builtin 3>&4-lá fd 4 đóng ngay cả sau khi compound-commandhoặc any-builtinđã trở lại. Một bản vá để khắc phục sự cố hiện đã có (2013/02/19).


Cảm ơn, bây giờ tôi biết di chuyển một fd là gì. Tôi có 4 câu hỏi cơ bản liên quan đến đoạn trích thứ 2 (và fds nói chung): 1) Trong cmd1, bạn biến 2 (stderr) thành bản sao của 3, nếu lệnh bên trong sử dụng 3 fd này thì sao? 2) Tại sao 3> & 1 và 4> & 1 hoạt động? Sao chép 3 và 4 chỉ có hiệu lực trong hai cmds đó, vỏ hiện tại có bị ảnh hưởng không? 3) Tại sao bạn đóng 4 trong cmd1 và 3 trong cmd2? Những lệnh đó không sử dụng fds đã đề cập, phải không? 4) Trong đoạn trích cuối, điều gì xảy ra nếu một fd được sao chép thành một đoạn không tồn tại (theo cmd1 và cmd2), ý tôi là 3 và 4, tương ứng?
Quentin

@Quentin, tôi đã sử dụng một ví dụ đơn giản hơn và giải thích thêm một chút với hy vọng nó sẽ đưa ra ít câu hỏi hơn câu trả lời. Nếu bạn vẫn có câu hỏi không liên quan trực tiếp đến cú pháp di chuyển fd, tôi khuyên bạn nên hỏi một câu hỏi riêng
Stéphane Chazelas

{ var=$(cmd 2>&1 >&3) ; } 3>&1-Nó không phải là một lỗi đánh máy trong đóng 1?
Quentin

@Quentin, Đó là một lỗi đánh máy trong đó tôi không nghĩ rằng tôi có ý định bao gồm nó, tuy nhiên nó làm cho không có sự khác biệt vì không có gì bên trong sử dụng niềng răng fd 1 (đó là toàn bộ các điểm sao chép nó vào fd 3: vì bản gốc 1 nếu không sẽ không thể truy cập được bên trong $(...)).
Stéphane Chazelas

1
@Quentin Khi bước vào {...}, fd 3 điểm với những gì fd 1 sử dụng để điểm và fd 1 được đóng lại, sau đó khi vào $(...), fd 1 được thiết lập để các đường ống mà thức ăn $var, sau đó cho cmd2 đó là tốt, và sau đó từ 1 tới 3 điểm gì đến, đó là bên ngoài 1. Thực tế là 1 vẫn bị đóng sau đó là một lỗi trong bash, tôi sẽ báo cáo nó. ksh93 tính năng đó đến từ đâu không có lỗi đó.
Stéphane Chazelas

4

Nó có nghĩa là làm cho nó trỏ đến cùng một nơi mà bộ mô tả tệp khác thực hiện. Bạn cần phải làm điều này rất hiếm khi, ngoài việc xử lý riêng biệt rõ ràng của bộ mô tả sai số chuẩn ( stderr, fd 2, /dev/stderr -> /proc/self/fd/2). Nó có thể có ích trong một số trường hợp phức tạp.

Hướng dẫn Script Bash nâng cao có ví dụ cấp độ nhật ký dài hơn và đoạn trích này:

# Redirecting only stderr to a pipe.
exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

Ví dụ, trong Sorcery của Source Mage, chúng tôi sử dụng nó để phân biệt các đầu ra khác nhau từ cùng một khối mã:

  (
    # everything is set, so run the actual build infrastructure
    run_build
  ) 3> >(tee -a $C_LOG >> /dev/stdout) \
    2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
     > >(tee -a $C_LOG > $VOYEUR_STDOUT)

Nó có sự thay thế quá trình bổ sung được thêm vào vì lý do ghi nhật ký (VOYEUR quyết định liệu dữ liệu sẽ được hiển thị trên màn hình hay chỉ là nhật ký), nhưng một số thông báo cần phải luôn được trình bày. Để đạt được điều đó, chúng tôi in chúng ra tệp mô tả 3 và sau đó xử lý nó một cách đặc biệt.


0

Trong Unix, các tệp được xử lý bởi các bộ mô tả tệp (số nguyên nhỏ, ví dụ đầu vào tiêu chuẩn là 0, đầu ra tiêu chuẩn là 1, lỗi tiêu chuẩn là 2; khi bạn mở các tệp khác, chúng thường được gán bộ mô tả không sử dụng nhỏ nhất). Vì vậy, nếu bạn biết các phần tử của chương trình và bạn muốn gửi đầu ra đi đến bộ mô tả tệp 5 đến đầu ra tiêu chuẩn, bạn sẽ di chuyển bộ mô tả 5 đến 1. Đó là nơi 2> errorsxuất phát và các cấu trúc muốn 2>&1sao chép lỗi vào luồng đầu ra.

Vì vậy, hầu như không bao giờ được sử dụng (tôi mơ hồ nhớ rằng sử dụng nó một hoặc hai lần trong sự tức giận trong hơn 25 năm sử dụng Unix gần như độc quyền của tôi), nhưng khi cần thiết thực sự cần thiết.


Nhưng tại sao không sao chép mô tả tệp 1 theo cách sau: 5> & 1? Tôi không hiểu việc sử dụng FD di chuyển là gì vì kernel sắp đóng nó ngay sau khi ...
Quentin

Điều đó không trùng lặp 5 thành 1, nó sẽ gửi 5 đến nơi 1 đang diễn ra. Và trước khi bạn hỏi; vâng, có một số ký hiệu khác nhau, được chọn từ nhiều loại vỏ tiền thân.
vonbrand

Vẫn không nhận được nó. Nếu 5>&1gửi 5 đến nơi 1 đang đi, thì chính xác thì 1>&5-làm gì, ngoài việc đóng 5?
Quentin
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.