Bagaimana cara mendeteksi apakah skrip shell saya berjalan melalui pipa?

252

Bagaimana saya mendeteksi dari dalam skrip shell jika output standarnya dikirim ke terminal atau jika disalurkan ke proses lain?

Contoh kasus: Saya ingin menambahkan kode pelarian untuk mewarnai output, tetapi hanya ketika dijalankan secara interaktif, tetapi tidak ketika disalurkan, mirip dengan apa yang ls --colordilakukan.

codeforester
sumber
2
Berikut ini beberapa kasus uji yang lebih menarik! <a href=" serverfault.com/questions/156470/… untuk skrip yang menunggu di stdin</a>
2
@ user940324 Tautan yang benar adalah serverfault.com/q/156470/197218
Palec

Jawaban:

385

Dalam shell POSIX murni,

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

mengembalikan "terminal", karena output dikirim ke terminal Anda, sedangkan

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

mengembalikan "bukan terminal", karena output dari tanda kurung disalurkan ke cat.


The -tflag dijelaskan dalam halaman manusia sebagai

-t fd Benar jika file descriptor fd terbuka dan merujuk ke terminal.

... di mana fddapat menjadi salah satu tugas deskriptor file yang biasa:

0:     stdin  
1:     stdout  
2:     stderr
dmckee --- mantan kucing moderator
sumber
1
@Kelvin Cuplikan halaman manual di sana menyarankan bahwa seharusnya, tetapi deskriptor file tersebut tidak ditetapkan secara default.
dmckee --- ex-moderator kitten
41
Untuk memperjelas, -tflag dispesifikasikan dalam POSIX, dan karenanya harus bekerja untuk semua shell yang kompatibel dengan POSIX (artinya, ini bukan ekstensi bash). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly
Bekerja ketika menjalankan skrip sebagai perintah ssh remote juga. Jawaban terbaik dan sangat sederhana.
linux_newbie
Saya setuju bahwa setelah edit Anda (revisi 5), jawabannya lebih jelas daripada di revisi 3 dan juga benar secara faktual (mengabaikan bahwa "pengembalian" digunakan sangat informal di mana "cetakan" akan lebih tepat).
Palec
Sedang mencari fishjawaban shell. Menggunakannya testrapi, tapi saya tidak bisa mencoba contoh yang diurung karena tidak didukung. Sudah mencoba membungkusnya dengan analog begin; ...; end, tetapi itu tampaknya tidak berhasil, dan hanya menjalankan blok kode positif lagi. Pikir saya mungkin perlu menggunakan statustetapi tampaknya tidak memeriksa perpipaan. Saya kira saya pada dasarnya ingin memeriksa apakah STDOUT dari perintah / skrip sebelumnya tidak disetel ke terminal, berkat jawaban klarifikasi ini.
Pysis
126

Tidak ada cara mudah untuk menentukan apakah STDIN, STDOUT, atau STDERR sedang disalurkan ke / dari skrip Anda, terutama karena program seperti ssh.

Hal-hal yang "normal" bekerja

Misalnya, solusi bash berikut berfungsi dengan benar di shell interaktif:

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

Tetapi mereka tidak selalu berhasil

Namun, ketika mengeksekusi perintah ini sebagai perintah non-TTY ssh, aliran STD selalu terlihat seperti sedang disalurkan. Untuk menunjukkan ini, gunakan STDIN karena lebih mudah:

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

Mengapa itu penting?

Ini adalah masalah yang cukup besar, karena ini menyiratkan bahwa tidak ada cara untuk skrip bash untuk mengetahui apakah perintah non-tty sshsedang disalurkan atau tidak. Perhatikan bahwa perilaku malang ini diperkenalkan ketika versi terbaru sshmulai menggunakan pipa untuk STDIO non-TTY. Versi sebelumnya menggunakan soket, yang BISA dibedakan dari dalam bash dengan menggunakan [[ -S ]].

Ketika itu penting

Batasan ini biasanya menyebabkan masalah ketika Anda ingin menulis skrip bash yang memiliki perilaku mirip dengan utilitas yang dikompilasi, seperti cat. Misalnya, catmemungkinkan perilaku fleksibel berikut dalam menangani berbagai sumber input secara bersamaan, dan cukup pintar untuk menentukan apakah itu menerima input yang disalurkan terlepas dari apakah non-TTY atau terpaksa-TTY sshsedang digunakan:

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

Anda hanya dapat melakukan sesuatu seperti itu jika Anda dapat menentukan dengan andal apakah pipa terlibat atau tidak. Jika tidak, mengeksekusi perintah yang membaca STDIN ketika tidak ada input yang tersedia dari pipa atau pengalihan akan menghasilkan skrip tergantung dan menunggu input STDIN.

Hal-hal lain yang tidak berhasil

Dalam mencoba menyelesaikan masalah ini, saya telah melihat beberapa teknik yang gagal untuk menyelesaikan masalah, termasuk yang melibatkan:

  • memeriksa variabel lingkungan SSH
  • menggunakan statdeskriptor file on / dev / stdin
  • memeriksa mode interaktif via [[ "${-}" =~ 'i' ]]
  • memeriksa status tty melalui ttydantty -s
  • memeriksa sshstatus melalui[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Perhatikan bahwa jika Anda menggunakan OS yang mendukung /procsistem file virtual, Anda mungkin beruntung mengikuti tautan simbolik untuk STDIO untuk menentukan apakah pipa digunakan atau tidak. Namun, /procbukan solusi lintas platform, kompatibel POSIX.

Saya sangat menarik dalam menyelesaikan masalah ini, jadi tolong beri tahu saya jika Anda memikirkan teknik lain yang mungkin berhasil, lebih disukai solusi berbasis POSIX yang bekerja pada Linux dan BSD.

Dejay Clayton
sumber
2
Memeriksa dengan jelas variabel lingkungan atau nama proses adalah heuristik yang sangat tidak dapat diandalkan. Tetapi dapatkah Anda sedikit memperluas mengapa heuristik lain tidak cocok untuk tujuan ini atau apa masalahnya? Misalnya saya tidak melihat perbedaan dalam output statpanggilan pada / dev / stdin. Dan mengapa "${-}"atau tty -stidak bekerja? Saya juga melihat ke dalam kode sumber cattetapi gagal melihat bagian mana yang melakukan keajaiban di sana yang tidak dapat Anda lakukan di shell POSIX. Bisakah Anda mengembangkannya?
josch
30

Perintah test(builtin in bash), memiliki opsi untuk memeriksa apakah file descriptor adalah tty.

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

Lihat " man test" atau " man bash" dan cari " -t"

Pesta
sumber
3
+1 untuk "man test" karena / usr / bin / test akan bekerja bahkan di shell yang tidak mengimplementasikan -t dalam tes build-in
Neil Mayhew
4
Seperti dicatat oleh FireFly dalam jawaban dmckee, shell yang tidak mengimplementasikan -t tidak sesuai dengan POSIX.
scy
Lihat juga built-in bash help test(dan help helpuntuk lebih banyak), lalu info bashuntuk informasi yang lebih mendalam. Perintah-perintah ini sangat bagus jika Anda akhirnya membuat skrip offline, atau hanya ingin mendapatkan pemahaman yang lebih luas.
Joel Purra
13

Anda tidak menyebutkan shell mana yang Anda gunakan, tetapi di Bash, Anda bisa melakukan ini:

#!/bin/bash

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

Tentang Solaris, saran dari Dejay Clayton kebanyakan berhasil. -P tidak merespon seperti yang diinginkan.

bash_redir_test.sh terlihat seperti:

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

Di Linux, ini bekerja dengan sangat baik:

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

Tentang 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

:# 
sbj3
sumber
Menarik, saya berharap saya memiliki akses ke Solaris untuk menguji. Jika instance Solaris Anda menggunakan sistem file "/ proc", ada solusi yang lebih andal yang melibatkan pencarian tautan simbolis "/ proc" untuk stdin, stdout, dan stderr.
Dejay Clayton
1

Kode berikut (hanya diuji di linux bash 4.4) tidak boleh dianggap portabel atau direkomendasikan , tetapi demi kelengkapan, ini adalah:

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

Saya tidak tahu mengapa, tetapi sepertinya file descriptor "3" entah bagaimana dibuat ketika fungsi bash telah STDIN piped.

Semoga ini bisa membantu,

ATORRA
sumber