Kabuk betiğimin bir kanaldan çalışıp çalışmadığını nasıl tespit edebilirim?


253

Kabuk betiğinin içinden standart çıktısı bir terminale gönderiliyorsa veya başka bir işleme geçiriliyorsa nasıl algılayabilirim?

Vaka örneği: Çıktıyı renklendirmek için kaçış kodları eklemek istiyorum, ancak sadece etkileşimli olarak çalıştığında, ancak ne zaman benzer şekilde borulandığında değil ls --color.


2
İşte bazı ilginç test örnekleri! stdin</a> 'da bekleyen bir komut dosyası için <a href=" serverfault.com/questions/156470/…

2
@ user940324 Doğru bağlantı serverfault.com/q/156470/197218
Palec

Yanıtlar:


385

Saf bir POSIX kabuğunda,

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

çıktı "terminal", çünkü çıkış terminalinize gönderilir, oysa

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

parantezin çıktısı borulandığından "terminal değil" değerini döndürür cat.


-tBayrak olarak man sayfalarında açıklanmıştır

-t fd fd dosya tanıtıcısı açıksa ve bir terminale başvuruyorsa doğrudur.

... burada fdolağan dosya tanımlayıcı atamalarından biri olabilir:

0:     stdin  
1:     stdout  
2:     stderr

1
@Kelvin Buradaki man sayfası snippet'i olması gerektiğini önerir, ancak bu dosya tanımlayıcıları varsayılan olarak atanmaz.
dmckee --- eski moderatör yavru kedi

41
Açıklığa kavuşturmak için, -tbayrak POSIX'te belirtilmiştir ve bu nedenle herhangi bir POSIX uyumlu kabuk için çalışmalıdır (yani, bir bash uzantısı değildir). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly

Bir komut dosyasını ssh uzak komut olarak çalıştırırken de çalışır. Şimdiye kadarki en iyi cevap ve çok basit.
linux_newbie

Düzenlemenizden sonra (revizyon 5), cevabın revizyon 3'tekinden daha net olduğunu ve aynı zamanda gerçekte doğru olduğunu kabul ediyorum (“baskıların” daha kesin olacağı yerlerde gayri resmi olarak kullanıldığını göz ardı etmek).
Palec

Bir fishkabuk cevabı arıyordum . Kullanmak testdüzgün, ancak desteklenmediğinden parantez örneğini deneyemiyorum. Benzer bir şekilde sarmayı denedim begin; ...; end, ancak işe yaramadı ve sadece pozitif kod bloğunu tekrar çalıştırdı. Kullanmam gerekebileceğini düşündüm statusama bu boruları kontrol etmiyor gibi görünüyor. Aslında bu açıklayıcı cevaplar sayesinde önceki komutun / komut dosyasının STDOUT'un terminale ayarlanıp ayarlanmadığını kontrol etmek istiyorum.
Pysis

126

STDIN, STDOUT veya STDERR'ın betiğinize / betiğinden boru olup olmadığını belirlemenin kusursuz bir yolu yoktur ssh.

"Normalde" çalışan şeyler

Örneğin, aşağıdaki bash çözümü etkileşimli bir kabukta düzgün çalışır:

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

Ama her zaman çalışmazlar

Ancak, bu komutu TTY olmayan bir komut olarak yürütürken ssh, STD akışları her zaman yönlendiriliyor gibi görünür. Bunu göstermek için STDIN'i kullanmak daha kolay çünkü:

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

Neden önemli?

Bu oldukça büyük bir anlaşma çünkü bash betiğinin tty olmayan bir sshkomutun iletilip iletilmediğini söylemesinin bir yolu olmadığını ima ediyor . Bu talihsiz davranışın sshTTY olmayan STDIO için pipo kullanmaya başladığı son sürümlerde ortaya çıktığını unutmayın. Önceki versiyonlar, kullanılarak bash içinden ayırt edilebilen soketler kullanıyordu [[ -S ]].

Ne zaman önemli

Bu sınırlama normalde, derlenmiş bir yardımcı programa benzer davranışlara sahip bir bash betiği yazmak istediğinizde sorunlara neden olur cat. Örneğin, catçeşitli giriş kaynaklarını aynı anda işlemede aşağıdaki esnek davranışa izin verir ve TTY olmayan veya zorlamalı TTY sshkullanılmasına bakılmaksızın borulu girdi alıp almadığını belirleyecek kadar akıllıdır :

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

Böyle bir şeyi ancak boruların dahil olup olmadığını güvenilir bir şekilde belirleyebiliyorsanız yapabilirsiniz. Aksi takdirde, borulardan veya yeniden yönlendirmeden herhangi bir girdi olmadığında STDIN yazan bir komutun çalıştırılması komut dosyasının asılı kalmasına ve STDIN girişinin beklemesine neden olur.

Çalışmayan diğer şeyler

Bu sorunu çözmeye çalışırken, aşağıdakileri içeren problemler de dahil olmak üzere sorunu çözemeyen çeşitli tekniklere baktım:

  • SSH ortam değişkenlerinin incelenmesi
  • kullanarak stat/ dev / stdin'i dosya göstericisi üzerinde
  • etkileşimli modu incelemek [[ "${-}" =~ 'i' ]]
  • ttyve üzerinden tty durumunu incelemetty -s
  • sshdurumunun incelenmesi[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

/procSanal dosya sistemini destekleyen bir işletim sistemi kullanıyorsanız, bir borunun kullanılıp kullanılmadığını belirlemek için STDIO'nun sembolik bağlantılarını takip etme şansınız olabilir. Ancak, /procplatformlar arası, POSIX uyumlu bir çözüm değildir.

Bu sorunu çözmede son derece ilginçim, bu yüzden işe yarayabilecek başka bir teknik, tercihen hem Linux hem de BSD üzerinde çalışan POSIX tabanlı çözümler düşünüyorsanız lütfen bize bildirin.


2
Ortam değişkenlerini veya işlem adlarını açıkça denetlemek çok güvenilir olmayan sezgisel yöntemlerdir. Ama diğer buluşsal yöntemlerin neden bu amaç için uygun olmadığını veya sorunlarının ne olduğunu biraz genişletebilir misiniz? Örneğin stat, / dev / stdin çağrısının çıkışında hiçbir fark görmüyorum . Ve neden çalışıyor "${-}"ya tty -sda çalışmıyor? Ayrıca kaynak koduna baktım catama hangi parçanın POSIX kabuğunda yapamayacağınız sihri yaptığını göremiyorum. Onu genişletebilirmiydin?
josch

30

Komut test(içinde yerleşik bash), bir dosya tanıtıcı bir tty olup olmadığını kontrol etmek için bir seçenek vardır.

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

Bkz. " man test" Veya " man bash" ve " -t" için arama yapın


3
"Us test" için +1, / usr / bin / test, yerleşik testinde -t uygulamayan bir kabukta bile çalışır
Neil Mayhew

4
FireFly tarafından dmckee'nin cevabında belirtildiği gibi, -t uygulamayan bir kabuk POSIX ile uyumlu değildir.
13'te scy

Daha ayrıntılı bilgi için bash yerleşkesine help test(ve help helpdaha fazlası için) bakınız info bash. Bu komutları çevrimdışı olarak komut dosyası oluşturmayı sonlandırırsanız veya yalnızca daha geniş bir anlayış edinmek istiyorsanız mükemmeldir.
Joel Purra

13

Hangi kabuğu kullandığınızdan bahsetmiyorsunuz, ancak Bash'te bunu yapabilirsiniz:

#!/bin/bash

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

6

Solaris'te Dejay Clayton'ın önerisi çoğunlukla işe yarıyor. -P istendiği gibi yanıt vermiyor.

bash_redir_test.sh şöyle görünüyor:

[[ -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'ta harika çalışıyor:

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

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

:# 

İlginçtir, keşke Solaris'e test yapabilseydim. Solaris örneğiniz "/ proc" dosya sistemini kullanıyorsa, stdin, stdout ve stderr için "/ proc" sembolik bağlantılarını aramayı içeren daha güvenilir çözümler vardır.
Dejay Clayton

1

Aşağıdaki kod (yalnızca linux bash 4.4'te test edilmiştir) taşınabilir olarak kabul edilmemeli veya önerilmemelidir , ancak burada tamlık uğruna:

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

Neden bilmiyorum, ama bir bash işlevi STDIN piped olduğunda dosya tanımlayıcı "3" bir şekilde oluşturulmuş gibi görünüyor.

Umarım yardımcı olur,

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.