쉘 스크립트가 파이프를 통해 실행되고 있는지 감지하는 방법은 무엇입니까?


252

표준 출력이 터미널로 전송되거나 다른 프로세스로 파이프되는지 여부를 쉘 스크립트에서 어떻게 감지합니까?

요점은 : 출력을 채색하기 위해 이스케이프 코드를 추가하고 싶지만 대화식으로 실행할 때만 파이프 할 때가 아닌 것과 비슷합니다 ls --color.


2
좀 더 흥미로운 테스트 사례가 있습니다! <a href=" serverfault.com/questions/156470/… stdin을 기다리는 스크립트의 경우</a>

2
@ user940324 올바른 링크는 serverfault.com/q/156470/197218입니다.
Palec

답변:


385

순수한 POSIX 셸에서

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

출력이 터미널로 전송되기 때문에 "터미널"을 반환합니다.

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

괄호의 출력이로 파이프되기 때문에 "터미널이 아님"을 반환합니다 cat.


-t플래그는 매뉴얼 페이지에서

-t fd 파일 디스크립터 fd가 열려 있고 터미널을 참조하면 참입니다.

... 여기서 fd일반적인 파일 디스크립터 할당 중 하나 일 수 있습니다.

0:     stdin  
1:     stdout  
2:     stderr

1
@Kelvin 맨 페이지 스 니펫은 그에 대한 권장 사항을 제시하지만, 해당 파일 디스크립터는 기본적으로 할당되지 않습니다.
dmckee --- 전 운영자 고양이

41
명확히하기 위해 -t플래그는 POSIX에 지정되므로 POSIX 호환 쉘 (즉, bash 확장이 아님)에 작동해야합니다. pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly

스크립트를 ssh remote 명령으로 실행할 때도 작동합니다. 최고의 답변과 매우 간단합니다.
linux_newbie

본인은 편집 (개정 5) 후, 답변이 개정 3보다 명확하고 사실에도 정확하다는 점에 동의합니다 (“반품”이보다 정확한 위치에“반품”이 매우 비공식적으로 사용되는 것을 무시 함).
Palec

fish쉘 답변을 찾고있었습니다 . 사용 test은 깔끔하지만 괄호로 묶은 예제는 지원되지 않으므로 시도 할 수 없습니다. 비슷한 것으로 래핑을 시도 begin; ...; end했지만 작동하지 않는 것처럼 보였고 양수 코드 블록을 다시 실행했습니다. 사용해야 status한다고 생각 했지만 배관을 확인하지 않는 것 같습니다. 이러한 명확한 답변 덕분에 이전 명령 / 스크립트의 STDOUT이 터미널로 설정되어 있지 않은지 본질적으로 확인하고 싶습니다.
Pysis

126

STDIN, STDOUT 또는 STDERR이 주로와 같은 프로그램으로 인해 스크립트에서 파이프로 또는 파이프에서 파이프되는지 여부를 결정하는 확실한 방법 은 없습니다 ssh.

"정상적으로"작동하는 것들

예를 들어, 다음 bash 솔루션은 대화식 쉘에서 올바르게 작동합니다.

[[ -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'

그러나 그들은 항상 작동하지 않습니다

그러나이 명령을 비 TTY 명령으로 실행할 때 sshSTD 스트림은 항상 파이프되는 것처럼 보입니다. STDIN을 사용하면 쉽게 설명 할 수 있습니다.

# 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 ${?}'

중요한 이유

이것은 bash 스크립트가 비 tty ssh명령이 파이프되고 있는지 여부를 알 수있는 방법이 없음을 의미하기 때문에 상당히 큰 문제 입니다. 이 불행한 동작은 최신 버전의 sshTTY가 아닌 STDIO에 파이프를 사용하기 시작할 때 도입되었습니다 . 이전 버전은 소켓을 사용했으며, 소켓을 사용하여 bash 내에서 구별 할 수 있습니다.[[ -S ]] .

중요한 때

이 제한은 일반적으로와 같은 컴파일 된 유틸리티와 유사한 동작을하는 bash 스크립트를 작성하려고 할 때 문제를 일으 킵니다 cat. 예를 들어, cat다양한 입력 소스를 동시에 처리 할 때 다음과 같은 유연한 동작을 수행 할 수 있으며 비 TTY 또는 강제 TTY 사용 여부에 관계없이 파이프 입력을 수신하는지 여부를 판단 할 수 있습니다 ssh.

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

파이프가 관련되어 있는지 여부를 확실하게 확인할 수있는 경우에만 이와 같은 작업을 수행 할 수 있습니다. 그렇지 않으면 파이프 또는 경로 재 지정에서 입력을 사용할 수 없을 때 STDIN을 읽는 명령을 실행하면 스크립트가 정지되고 STDIN 입력을 기다립니다.

작동하지 않는 다른 것들

이 문제를 해결하기 위해 다음과 같은 기술을 포함하여 문제를 해결하지 못하는 몇 가지 기술을 살펴 보았습니다.

  • SSH 환경 변수 검사
  • 사용 stat은 / dev / stdin을 파일 기술자에
  • 통해 대화 형 모드 검사 [[ "${-}" =~ 'i' ]]
  • 를 통해 청각 장애의 상태를 검사 tty하고tty -s
  • ssh통해 상태를 검사[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

/proc가상 파일 시스템 을 지원하는 OS를 사용하는 경우 STDIO의 기호 링크를 따라 파이프 사용 여부를 판단 할 수 있습니다. 그러나 /proc크로스 플랫폼, POSIX 호환 솔루션은 아닙니다.

이 문제를 해결하는 데 매우 흥미 롭기 때문에 Linux 및 BSD 모두에서 작동하는 POSIX 기반 솔루션이 작동하는 다른 기술에 대해 생각하면 알려주십시오.


2
환경 변수 또는 프로세스 이름을 명확하게 검사하는 것은 매우 신뢰할 수없는 휴리스틱입니다. 그러나 다른 휴리스틱이 왜이 목적에 적합하지 않은지 또는 문제가 무엇인지 조금 확장 할 수 있습니까? 예를 들어 stat/ dev / stdin 의 호출 결과에는 차이가 없습니다 . 그리고 왜 않습니다 "${-}"또는 tty -s작동하지? 또한 소스 코드를 살펴 보았지만 catPOSIX 셸에서 할 수없는 마법을 수행하는 부분을 보지 못했습니다. 그것에 대해 자세하게 말 해 주실 수 있나요?
josch

30

test(내장 bash) 명령 에는 파일 설명자가 tty인지 확인하는 옵션이 있습니다.

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

"참조 man test"또는 " man bash"와 "검색 -t"


3
/ usr / bin / test는 내장 테스트에서 -t를 구현하지 않는 쉘에서도 작동하기 때문에 "man test"에 +1
Neil Mayhew

4
dmckee의 답변에서 FireFly가 지적했듯이 -t를 구현하지 않는 쉘은 POSIX를 준수하지 않습니다.
scy

자세한 내용은 bash의 내장 help test(및 help help기타) info bash을 참조하십시오. 이 명령은 오프라인에서 스크립팅을 끝내거나 더 광범위하게 이해하려는 경우에 유용합니다.
Joel Purra

13

어떤 쉘을 사용하고 있는지 언급하지 않지만 Bash에서는 다음과 같이 할 수 있습니다.

#!/bin/bash

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

6

Solaris에서는 Dejay Clayton의 제안이 대부분 작동합니다. -p가 원하는대로 응답하지 않습니다.

bash_redir_test.sh는 다음과 같습니다.

[[ -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'

Linux에서는 다음과 같이 작동합니다.

:$ ./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

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

:# 

흥미롭게도 테스트를 위해 Solaris에 액세스했으면합니다. Solaris 인스턴스가 "/ proc"파일 시스템을 사용하는 경우 stdin, stdout 및 stderr에 대한 "/ proc"기호 링크를 검색하는보다 안정적인 솔루션이 있습니다.
Dejay Clayton 2016

1

다음 코드 (linux bash 4.4에서만 테스트 됨) 는 이식성이 있거나 권장되지 않아야 하지만 완전성을 위해 여기에 있습니다.

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

왜 그런지 모르겠지만 bash 함수에 STDIN이 파이프 될 때 파일 설명자 "3"이 어떻게 든 생성되는 것 같습니다.

도움이 되길 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.