Làm thế nào để phát hiện nếu tập lệnh shell của tôi đang chạy qua một đường ống?


252

Làm cách nào để phát hiện từ trong tập lệnh shell nếu đầu ra tiêu chuẩn của nó được gửi đến một thiết bị đầu cuối hoặc nếu nó được chuyển sang quy trình khác?

Trường hợp cụ thể: Tôi muốn thêm mã thoát để tô màu đầu ra, nhưng chỉ khi chạy tương tác, nhưng không phải khi đường ống, tương tự như những gì ls --color.


2
Dưới đây là một số trường hợp thử nghiệm thú vị hơn! <a href=" serverfault.com/questions/156470/ cho một tập lệnh đang chờ trên stdin</a>

2
@ user940324 Liên kết chính xác là serverfault.com/q/156470/197218
Palec

Câu trả lời:


385

Trong vỏ POSIX thuần túy,

if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi

trả về "terminal", bởi vì đầu ra được gửi đến terminal của bạn, trong khi

(if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi) | cat

trả về "không phải là một thiết bị đầu cuối", bởi vì đầu ra của ngoặc đơn được dẫn đến cat.


Các -tcờ được mô tả trong các trang người đàn ông như

-t fd Đúng nếu mô tả tập tin fd đang mở và tham chiếu đến một thiết bị đầu cuối.

... nơi fdcó thể là một trong những bài tập mô tả tệp thông thường:

0:     stdin  
1:     stdout  
2:     stderr

1
@Kelvin Đoạn trích trang người đàn ông ở đó gợi ý rằng nó nên, nhưng những mô tả tệp đó không được gán theo mặc định.
dmckee --- ex-moderator mèo con

41
Để làm rõ, -tcờ được chỉ định trong POSIX và do đó sẽ hoạt động cho mọi vỏ tương thích POSIX (nghĩa là nó không phải là tiện ích mở rộng bash). pubs.opengroup.org/onlinepub/009695399/utilities/test.html
FireFly

Hoạt động khi chạy một kịch bản như lệnh ssh từ xa là tốt. Câu trả lời hay nhất từ ​​trước đến nay và rất đơn giản.
linux_newbie

Tôi đồng ý rằng sau lần chỉnh sửa của bạn (bản sửa đổi 5), câu trả lời rõ ràng hơn so với phiên bản 3 và cũng thực sự chính xác (bỏ qua việc trả về của Hồi được sử dụng rất không chính thức trong đó có thể in chính xác hơn.
Palec

Đã tìm kiếm một fishcâu trả lời vỏ. Sử dụng testlà gọn gàng, nhưng tôi không thể thử ví dụ được ngoặc đơn vì điều đó không được hỗ trợ. Đã thử gói nó trong một tương tự begin; ...; end, nhưng điều đó dường như không hoạt động, và chỉ chạy lại khối mã tích cực. Nghĩ rằng tôi có thể cần phải sử dụng statusnhưng dường như không kiểm tra đường ống. Tôi đoán rằng về cơ bản tôi muốn kiểm tra xem STDOUT của lệnh / tập lệnh trước không được đặt thành thiết bị đầu cuối hay không, nhờ những câu trả lời làm rõ này.
Pysis

126

Không có cao độ cách để xác định nếu STDIN, STDOUT, hoặc thiết bị lỗi chuẩn đang được bằng đường ống đến / từ kịch bản của bạn, chủ yếu là do các chương trình như ssh.

Những việc "bình thường" hoạt động

Ví dụ: giải pháp bash sau hoạt động chính xác trong trình bao tương tác:

[[ -t 1 ]] && \
    echo 'STDOUT is attached to TTY'

[[ -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a pipe'

[[ ! -t 1 && ! -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a redirection'

Nhưng họ không luôn luôn làm việc

Tuy nhiên, khi thực hiện lệnh này dưới dạng lệnh không phải TTY ssh, các luồng STD luôn trông giống như chúng đang được truyền. Để chứng minh điều này, sử dụng STDIN vì nó dễ dàng hơn:

# CORRECT: Forced-tty mode correctly reports '1', which represents
# no pipe.
ssh -t localhost '[[ -p /dev/stdin ]]; echo ${?}'

# CORRECT: Issuing a piped command in forced-tty mode correctly
# reports '0', which represents a pipe.
ssh -t localhost 'echo hi | [[ -p /dev/stdin ]]; echo ${?}'

# INCORRECT: Non-tty mode reports '0', which represents a pipe,
# even though one isn't specified here.
ssh -T localhost '[[ -p /dev/stdin ]]; echo ${?}'

Tại sao nó quan trọng

Đây là một vấn đề khá lớn, bởi vì nó ngụ ý rằng không có cách nào để một tập lệnh bash cho biết liệu một sshlệnh không tty có được xử lý hay không. Lưu ý rằng hành vi đáng tiếc này đã được giới thiệu khi các phiên bản gần đây sshbắt đầu sử dụng đường ống cho STDIO không TTY. Các phiên bản trước đã sử dụng ổ cắm, mà COULD được phân biệt với trong bash bằng cách sử dụng [[ -S ]].

Khi nó quan trọng

Giới hạn này thường gây ra sự cố khi bạn muốn viết tập lệnh bash có hành vi tương tự như tiện ích được biên dịch, chẳng hạn như cat. Ví dụ: catcho phép hành vi linh hoạt sau đây trong việc xử lý đồng thời các nguồn đầu vào khác nhau và đủ thông minh để xác định xem liệu nó có nhận được đầu vào theo đường ống bất kể sử dụng không phải TTY hay bắt buộc-TTY sshkhông:

ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'

Bạn chỉ có thể làm một cái gì đó như thế nếu bạn có thể xác định một cách đáng tin cậy nếu các đường ống có liên quan hay không. Mặt khác, thực thi lệnh đọc STDIN khi không có sẵn đầu vào từ các đường ống hoặc chuyển hướng sẽ dẫn đến việc tập lệnh bị treo và chờ đầu vào STDIN.

Những thứ khác không hoạt động

Khi cố gắng giải quyết vấn đề này, tôi đã xem xét một số kỹ thuật không giải quyết được vấn đề, bao gồm cả những kỹ thuật liên quan đến:

  • kiểm tra các biến môi trường SSH
  • sử dụng statmô tả tập tin on / dev / stdin
  • kiểm tra chế độ tương tác thông qua [[ "${-}" =~ 'i' ]]
  • kiểm tra tình trạng tty qua ttytty -s
  • kiểm tra sshtình trạng thông qua[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Lưu ý rằng nếu bạn đang sử dụng HĐH hỗ trợ /prochệ thống tệp ảo, bạn có thể gặp may mắn khi theo các liên kết tượng trưng cho STDIO để xác định xem một đường ống có được sử dụng hay không. Tuy nhiên, /prockhông phải là một giải pháp đa nền tảng, tương thích POSIX.

Tôi cực kỳ thú vị trong việc giải quyết vấn đề này, vì vậy vui lòng cho tôi biết nếu bạn nghĩ về bất kỳ kỹ thuật nào khác có thể hoạt động, tốt nhất là các giải pháp dựa trên POSIX hoạt động trên cả Linux và BSD.


2
Rõ ràng kiểm tra các biến môi trường hoặc tên quá trình là các heuristic rất không đáng tin cậy. Nhưng bạn có thể mở rộng một chút lý do tại sao các heuristic khác không phù hợp cho mục đích này hoặc vấn đề của họ là gì? Ví dụ, tôi thấy không có sự khác biệt trong đầu ra của một statcuộc gọi trên / dev / stdin. Và tại sao không "${-}"hoặc tty -skhông làm việc? Tôi cũng đã xem xét mã nguồn của catnhưng không thấy phần nào đang thực hiện phép thuật ở đó mà bạn không thể làm trong vỏ POSIX. bạn có thể mở rộng về điều đó?
ngày

30

Lệnh test(dựng sẵn bash), có một tùy chọn để kiểm tra xem bộ mô tả tệp có phải là tty không.

if [ -t 1 ]; then
    # stdout is a tty
fi

Xem " man test" hoặc " man bash" và tìm kiếm " -t"


3
+1 cho "man test" vì / usr / bin / test sẽ hoạt động ngay cả trong trình bao không thực hiện -t trong thử nghiệm tích hợp của nó
Neil Mayhew

4
Như FireFly đã lưu ý trong câu trả lời của dmckee, một lớp vỏ không thực hiện -t không phù hợp với POSIX.
scy

Xem thêm phần tích hợp của bash help test(và help helpđể biết thêm), sau đó info bashđể biết thêm thông tin chi tiết. Các lệnh này rất tuyệt nếu bạn kết thúc kịch bản ngoại tuyến hoặc chỉ muốn hiểu rõ hơn.
Joel Purra

13

Bạn không đề cập đến loại vỏ nào bạn đang sử dụng, nhưng trong Bash, bạn có thể làm điều này:

#!/bin/bash

if [[ -t 1 ]]; then
    # stdout is a terminal
else
    # stdout is not a terminal
fi

6

Trên Solaris, đề xuất từ ​​Dejay Clayton hoạt động chủ yếu. Các -p không đáp ứng như mong muốn.

bash_redir_test.sh trông giống như:

[[ -t 1 ]] && \
    echo 'STDOUT is attached to TTY'

[[ -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a pipe'

[[ ! -t 1 && ! -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a redirection'

Trên Linux, nó hoạt động rất tốt:

:$ ./bash_redir_test.sh
STDOUT is attached to TTY

:$ ./bash_redir_test.sh | xargs echo
STDOUT is attached to a pipe

:$ rm bash_redir_test.log 
:$ ./bash_redir_test.sh >> bash_redir_test.log

:$ tail bash_redir_test.log 
STDOUT is attached to a redirection

Trên Solaris:

:# ./bash_redir_test.sh
STDOUT is attached to TTY

:# ./bash_redir_test.sh | xargs echo
STDOUT is attached to a redirection

:# rm bash_redir_test.log 
bash_redir_test.log: No such file or directory

:# ./bash_redir_test.sh >> bash_redir_test.log
:# tail bash_redir_test.log 
STDOUT is attached to a redirection

:# 

Thật thú vị, tôi ước mình có quyền truy cập vào Solaris để kiểm tra. Nếu phiên bản Solaris của bạn sử dụng hệ thống tập tin "/ Proc", có nhiều giải pháp đáng tin cậy hơn liên quan đến việc tìm kiếm các liên kết tượng trưng "/ Proc" cho stdin, stdout và stderr.
Dejay Clayton

1

Mã sau đây (chỉ được thử nghiệm trong linux bash 4.4) không nên được coi là di động cũng không được khuyến nghị , nhưng vì mục đích hoàn chỉnh ở đây là:

ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"

Tôi không biết tại sao, nhưng có vẻ như mô tả tệp "3" bằng cách nào đó được tạo khi hàm bash có đường ống STDIN.

Hy vọng nó giúp,

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.