Làm thế nào để một chương trình biết nếu thiết bị xuất chuẩn được kết nối với thiết bị đầu cuối hoặc đường ống?


12

Tôi gặp sự cố khi gỡ lỗi chương trình segfaulting vì ouput ngay trước segfault là thứ tôi cần, nhưng điều này sẽ bị mất nếu tôi dẫn đầu ra vào một tệp. Theo câu trả lời này: /unix//a/17339/22615 , điều này là do bộ đệm đầu ra của chương trình tuôn ra ngay lập tức khi được kết nối với một thiết bị đầu cuối nhưng chỉ tại một số điểm nhất định khi được kết nối với đường ống. Một vài câu hỏi ở đây:

  • Làm thế nào để một chương trình xác định thiết bị xuất chuẩn của nó được kết nối với cái gì?

  • Làm thế nào để lệnh "script" tạo ra hành vi giống như khi chương trình ghi vào một thiết bị đầu cuối?

  • Điều này có thể đạt được mà không cần lệnh script?


Một câu hỏi liên quan là unix.stackexchange.com/q/513926/5132 .
JdeBP

Câu trả lời:


23

Nói nếu một bộ mô tả tập tin trỏ đến một thiết bị đầu cuối

Một chương trình có thể cho biết nếu một bộ mô tả tệp được liên kết với một thiết bị tty bằng cách sử dụng isatty()chức năng C tiêu chuẩn (thường nằm bên dưới một ioctl()cuộc gọi hệ thống cụ thể vô hại sẽ trả về lỗi khi fd không trỏ đến thiết bị tty) .

Các [/ testtiện ích có thể làm điều đó với mình -tđiều hành.

if [ -t 1 ]; then
  echo stdout is open to a terminal
fi

Truy tìm hàm libc trên hệ thống GNU / Linux:

$ ltrace [ -t 1 ] | cat
[...]
isatty(1)                                      = 0
[...]

Truy tìm hệ thống theo dõi:

$ strace [ -t 1 ] | cat
[...]
ioctl(1, TCGETS, 0x7fffd9fb3010)        = -1 ENOTTY (Inappropriate ioctl for device)
[...]

Nói nếu nó chỉ vào một đường ống

Để xác định xem fd có được liên kết với ống / fifo hay không, người ta có thể sử dụng lệnh fstat()gọi hệ thống , trả về cấu trúc có st_modetrường chứa loại và quyền của tệp được mở trên fd đó. Các S_ISFIFO()C macro tiêu chuẩn có thể được sử dụng trên đó st_modelĩnh vực để xác định xem fd là một ống / FIFO.

Không có tiện ích tiêu chuẩn nào có thể làm được fstat(), nhưng có một số cách triển khai statlệnh không tương thích có thể thực hiện được. zshNội dung statdựng sẵn stat -sf "$fd" +modetrả về chế độ dưới dạng chuỗi đại diện có ký tự đầu tiên đại diện cho loại ( pđối với đường ống). GNU statcó thể làm tương tự với stat -c %A - <&"$fd", nhưng cũng stat -c %F - <&"$fd"phải báo cáo loại một mình. Với BSD stat: stat -f %St <&"$fd"hoặc stat -f %HT <&"$fd".

Nói nếu nó có thể tìm kiếm

Các ứng dụng thường không quan tâm nếu thiết bị xuất chuẩn là một đường ống. Họ có thể quan tâm rằng nó có thể tìm kiếm được (mặc dù thường không quyết định có đệm hay không).

Để kiểm tra xem fd có thể tìm kiếm được không (ống, ổ cắm, thiết bị tty không thể tìm kiếm, các tệp thông thường và hầu hết các thiết bị khối thường là), người ta có thể thử một lseek()cuộc gọi hệ thống tương đối với độ lệch bằng 0 (rất vô hại). ddlà một tiện ích tiêu chuẩn có giao diện lseek()nhưng nó không thể được sử dụng cho thử nghiệm đó, vì việc triển khai sẽ hoàn toàn không gọi lseek()nếu bạn yêu cầu bù 0.

Các shell zshksh93shell đã tích hợp sẵn các toán tử tìm kiếm:

$ strace -e lseek ksh -c ': 1>#((CUR))' | cat
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
ksh: 1: not seekable
$ strace -e lseek zsh -c 'zmodload zsh/system; sysseek -w current -u 1 0 || syserror'
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
Illegal seek

Vô hiệu hóa bộ đệm

Các scriptlệnh sử dụng một cặp giả thiết bị đầu cuối để bắt đầu ra của một chương trình, do đó chương trình stdout (và stdin và stderr) sẽ là một thiết bị giả thiết bị đầu cuối.

Khi thiết bị xuất chuẩn là thiết bị đầu cuối, nhìn chung vẫn có một số bộ đệm, nhưng nó dựa trên dòng. printf/ putsvà đồng sẽ không viết bất cứ điều gì cho đến khi một ký tự dòng mới được xuất ra. Đối với các loại tệp khác, bộ đệm là theo khối (của một vài kilo byte).

Có một số tùy chọn để vô hiệu hóa bộ đệm được thảo luận trong một số câu hỏi và hỏi đáp ở đây (tìm kiếm unbuffer hoặc stdbuf , không thể chuyển hướng cắt đầu ra đưa ra một vài cách tiếp cận) bằng cách sử dụng thiết bị đầu cuối giả như có thể được thực hiện bởi socat/ script/ expect/ unbuffer(một expecttập lệnh) / zsh's zptyhoặc bằng cách tiêm mã vào tệp thực thi để vô hiệu hóa bộ đệm như được thực hiện bởi GNU hoặc FreeBSD stdbuf.


1
Câu trả lời tuyệt vời, cảm ơn bạn rất nhiều vì điều này!
mowwwalker

Một cách tiếp cận khác dành riêng cho Linux là truy cập /procthư mục và cho mỗi /proc/<integer>/thư mục xem /proc/<integer>/fd/và tìm mô tả tệp có cùng số inode trong pipefs serverfault.com/q/48330 / 3611 Tuy nhiên, điều đó chỉ hữu ích trong các tập lệnh khi người ta không thể sử dụng các tòa nhà được mô tả trong câu trả lời của Stephane, và là một cách giải quyết hơn là giải pháp thích hợp IMHO
Sergiy Kolodyazhnyy

Trên BSD, lseeksẽ thành công trên các thiết bị đầu cuối và các thiết bị ký tự khác, và chỉ cần đặt lại / đặt bộ đếm được tăng lên trên mỗi lần đọc thành công (). Tôi không biết liệu điều này làm cho họ "có thể tìm kiếm".
mosvy

@mowwwalker Nếu câu trả lời này giải quyết được vấn đề của bạn, vui lòng dành chút thời gian và chấp nhận nó bằng cách nhấp vào dấu kiểm bên trái. Điều đó sẽ đánh dấu câu hỏi đã được trả lời và là cách cảm ơn được thể hiện trên các trang web Stack Exchange.
món tráng miệng
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.