วิธีการตรวจสอบว่าสคริปต์เปลือกของฉันทำงานผ่านท่อ


253

ฉันจะตรวจจับจากภายในสคริปต์เชลล์ได้อย่างไรหากเอาต์พุตมาตรฐานกำลังถูกส่งไปยังเทอร์มินัลหรือถ้ามันถูกส่งไปยังกระบวนการอื่น

กรณีตรงประเด็น: ฉันต้องการเพิ่มรหัสหลีกเลี่ยงเพื่อกำหนดสีเอาท์พุท แต่เฉพาะเมื่อทำงานแบบโต้ตอบ แต่ไม่ใช่เมื่อมีการไพพ์คล้ายกับสิ่งที่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 จริงถ้าไฟล์ descriptor fd เปิดอยู่และอ้างถึงเทอร์มินัล

... ซึ่งfdสามารถเป็นหนึ่งในการกำหนด descriptor ไฟล์ตามปกติ:

0:     stdin  
1:     stdout  
2:     stderr

1
@Kelvin ตัวอย่างของ man page แนะนำว่าควรมี แต่ตัวอธิบายไฟล์เหล่านั้นไม่ได้ถูกกำหนดไว้ตามค่าเริ่มต้น
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 sshสตรีม STD จะดูเหมือนว่ากำลังถูกไพพ์ เพื่อแสดงสิ่งนี้ใช้ 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 ${?}'

ทำไมมันถึงสำคัญ

นี่เป็นเรื่องใหญ่เพราะมันบอกเป็นนัยว่าไม่มีวิธีใดที่สคริปต์ทุบตีจะบอกว่าsshคำสั่งที่ไม่ใช่ tty กำลังถูกไพพ์หรือไม่ โปรดทราบว่าพฤติกรรมที่โชคร้ายนี้ถูกนำมาsshใช้เมื่อเริ่มต้นเวอร์ชันล่าสุดโดยใช้ไพพ์สำหรับ non-TTY STDIO [[ -S ]]รุ่นก่อนที่ใช้ซ็อกเก็ตซึ่งอาจจะแตกต่างจากภายในทุบตีโดยใช้

เมื่อมันสำคัญ

ข้อ จำกัด 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อธิบายไฟล์ on / dev / stdin
  • ตรวจสอบโหมดโต้ตอบผ่านทาง [[ "${-}" =~ 'i' ]]
  • ตรวจสอบสถานะ tty ผ่านทางttyและtty -s
  • ตรวจสอบsshสถานะผ่าน[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

โปรดทราบว่าหากคุณใช้ระบบปฏิบัติการที่สนับสนุน/procระบบไฟล์เสมือนคุณอาจโชคดีที่ติดตามลิงก์สัญลักษณ์สำหรับ STDIO เพื่อตรวจสอบว่ามีการใช้ไปป์หรือไม่ อย่างไรก็ตาม/procไม่ใช่โซลูชันข้ามแพลตฟอร์มที่รองรับ POSIX

ฉันน่าสนใจอย่างมากในการแก้ปัญหานี้ดังนั้นโปรดแจ้งให้เราทราบหากคุณคิดว่ามีเทคนิคอื่นใดที่อาจใช้งานได้ดีกว่าโดยใช้โซลูชัน POSIX ที่ทำงานบนทั้ง Linux และ BSD


2
การตรวจสอบตัวแปรสภาพแวดล้อมหรือชื่อกระบวนการอย่างชัดเจนคือการวิเคราะห์พฤติกรรมที่ไม่น่าเชื่อถือ แต่คุณสามารถขยายออกไปหน่อยได้ไหมว่าทำไมฮิวริสติกอื่น ๆ จึงไม่เหมาะสำหรับวัตถุประสงค์นี้หรือปัญหาของพวกเขาคืออะไร ตัวอย่างเช่นฉันไม่เห็นความแตกต่างในผลลัพธ์ของการstatโทรบน / dev / stdin และทำไมไม่"${-}"หรือtty -sไม่ทำงาน? ฉันยังดูในซอร์สโค้ดของcatแต่ไม่สามารถดูว่าส่วนใดที่ทำเวทมนต์ที่นั่นซึ่งคุณไม่สามารถทำได้ในเชลล์ POSIX คุณช่วยขยายความหน่อยได้มั้ย?
josch

30

คำสั่งtest(ในตัวbash) มีตัวเลือกเพื่อตรวจสอบว่า file descriptor เป็น tty

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

ดู " man test" หรือ " man bash" และค้นหา " -t"


3
+1 สำหรับ "man test" เพราะ / usr / bin / test จะทำงานแม้ในเชลล์ที่ไม่ได้ใช้ -t ในการทดสอบ build-in
Neil Mayhew

4
ตามที่ระบุโดย FireFly ในคำตอบของ dmckee เชลล์ที่ไม่ได้ใช้ -t ไม่สอดคล้องกับ POSIX
scy

ดูเพิ่มเติมที่บูชของ builtin 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

:# 

น่าสนใจฉันหวังว่าฉันจะสามารถเข้าถึงโซลาริสเพื่อทดสอบ หากอินสแตนซ์โซลาริสของคุณใช้ระบบไฟล์ "/ proc" จะมีโซลูชันที่น่าเชื่อถือมากกว่าที่เกี่ยวข้องกับการค้นหาลิงก์สัญลักษณ์ "/ proc" สำหรับ stdin, stdout และ stderr
Dejay Clayton

1

รหัสต่อไปนี้ (ทดสอบเฉพาะใน linux bash 4.4) ไม่ควรพิจารณาพกพาหรือแนะนำแต่เพื่อความสมบูรณ์ที่นี่:

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

ฉันไม่รู้ว่าทำไม แต่ดูเหมือนว่า file descriptor "3" จะถูกสร้างขึ้นเมื่อฟังก์ชั่นทุบตีมี piped STDIN

หวังว่าจะช่วย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.