¿Cómo detectar si mi script de shell se ejecuta a través de una tubería?

252

¿Cómo detecto desde un script de shell si su salida estándar se envía a un terminal o si se canaliza a otro proceso?

El caso en cuestión: me gustaría agregar códigos de escape para colorear la salida, pero solo cuando se ejecuta de forma interactiva, pero no cuando se canaliza, de forma similar a lo que ls --colorhace.

codeforester
fuente
2
¡Aquí hay algunos casos de prueba más interesantes! <a href=" serverfault.com/questions/156470/… para un script que está esperando en stdin</a>
2
@ user940324 El enlace correcto es serverfault.com/q/156470/197218
Palec

Respuestas:

385

En un shell POSIX puro,

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

devuelve "terminal", porque la salida se envía a su terminal, mientras que

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

devuelve "no un terminal", porque la salida del paréntesis se canaliza a cat.


La -tbandera se describe en las páginas del manual como

-t fd Verdadero si el descriptor de archivo fd está abierto y hace referencia a un terminal.

... donde fdpuede ser una de las asignaciones de descriptor de archivo habituales:

0:     stdin  
1:     stdout  
2:     stderr
dmckee --- gatito ex moderador
fuente
1
@Kelvin El fragmento de página de manual sugiere que debería hacerlo, pero esos descriptores de archivo no están asignados de forma predeterminada.
dmckee --- ex gatito moderador
41
Para aclarar, el -tindicador se especifica en POSIX y, por lo tanto, debería funcionar para cualquier shell compatible con POSIX (es decir, no es una extensión bash). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly
Funciona cuando se ejecuta un script como comando remoto ssh también. La mejor respuesta y muy simple.
linux_newbie
Estoy de acuerdo en que después de su edición (revisión 5), la respuesta es más clara que en la revisión 3 y también objetivamente correcta (ignorando que "retornos" se usa de manera muy informal donde "impresiones" serían más precisas).
Palec
Estaba buscando una fishrespuesta de shell. El uso testes ordenado, pero no puedo probar el ejemplo entre paréntesis, ya que no es compatible. Intenté envolverlo de manera análoga begin; ...; end, pero eso no pareció funcionar, y simplemente ejecutó el bloque de código positivo nuevamente. Pensé que podría necesitar usar, statuspero eso no parece comprobar si hay tuberías. Supongo que esencialmente quiero comprobar si STDOUT de un comando / script anterior no está configurado en el terminal, gracias a estas aclaraciones.
Pysis
126

No hay una forma infalible de determinar si STDIN, STDOUT o STDERR se están canalizando hacia / desde su script, principalmente debido a programas como ssh.

Cosas que "normalmente" funcionan

Por ejemplo, la siguiente solución bash funciona correctamente en un shell interactivo:

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

Pero no siempre funcionan

Sin embargo, al ejecutar este comando como un comando que no es TTY ssh, las transmisiones STD siempre parecen estar siendo canalizadas. Para demostrar esto, use STDIN porque es más fácil:

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

Por que es importante

Este es un gran problema, porque implica que no hay forma de que un script bash sepa si un sshcomando que no es tty se está canalizando o no. Tenga en cuenta que este comportamiento desafortunado se introdujo cuando las versiones recientes de sshcomenzaron a utilizar tuberías para STDIO no TTY. Las versiones anteriores usaban sockets, que PODRÍAN diferenciarse desde dentro de bash usando [[ -S ]].

Cuando importa

Esta limitación normalmente causa problemas cuando desea escribir un script bash que tenga un comportamiento similar a una utilidad compilada, como cat. Por ejemplo, catpermite el siguiente comportamiento flexible en el manejo de varias fuentes de entrada simultáneamente, y es lo suficientemente inteligente como para determinar si está recibiendo una entrada canalizada independientemente de si sshse está utilizando TTY no forzado o TTY forzado :

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

Solo puede hacer algo así si puede determinar de manera confiable si las tuberías están involucradas o no. De lo contrario, la ejecución de un comando que lee STDIN cuando no hay entrada disponible desde cualquiera de las tuberías o la redirección dará como resultado que el script se cuelgue y espere la entrada de STDIN.

Otras cosas que no funcionan

Al tratar de resolver este problema, he examinado varias técnicas que no logran resolverlo, incluidas las que incluyen:

  • examinar las variables de entorno SSH
  • utilizando statdescriptores de archivo on / dev / stdin
  • examinar el modo interactivo a través de [[ "${-}" =~ 'i' ]]
  • examinar el estado de tty a través de ttyytty -s
  • examinar el sshestado a través de[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Tenga en cuenta que si está utilizando un sistema operativo que admite el /procsistema de archivos virtual, es posible que tenga suerte siguiendo los enlaces simbólicos de STDIO para determinar si se está utilizando una tubería o no. Sin embargo, /procno es una solución multiplataforma compatible con POSIX.

Soy extremadamente interesante para resolver este problema, así que avíseme si piensa en alguna otra técnica que pueda funcionar, preferiblemente soluciones basadas en POSIX que funcionen tanto en Linux como en BSD.

Dejay Clayton
fuente
2
Inspeccionar claramente las variables de entorno o los nombres de procesos son heurísticas muy poco confiables. Pero, ¿podría ampliar un poco por qué las otras heurísticas no son aptas para este propósito o cuál es su problema? Por ejemplo, no veo diferencia en la salida de una statllamada en / dev / stdin. ¿Y por qué funciona "${-}"o tty -sno? También busqué en el código fuente de catpero no veo qué parte está haciendo la magia allí que no puedes hacer en el shell POSIX. ¿Puede explicar más?
josch
30

El comando test(incorporado bash) tiene una opción para verificar si un descriptor de archivo es un tty.

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

Ver " man test" o " man bash" y buscar " -t"

Comilona
fuente
3
+1 para "prueba de hombre" porque / usr / bin / test funcionará incluso en un shell que no implementa -t en su prueba
integrada
44
Como señaló FireFly en la respuesta de dmckee, un shell que no implementa -t no se ajusta a POSIX.
scy
Ver también bash incorporado help test(y help helppara más), luego info bashpara obtener información más detallada. Estos comandos son excelentes si alguna vez terminas haciendo scripts fuera de línea, o si solo quieres obtener una comprensión más amplia.
Joel Purra
13

No mencionas qué shell estás usando, pero en Bash, puedes hacer esto:

#!/bin/bash

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

En Solaris, la sugerencia de Dejay Clayton funciona principalmente. La -p no responde como se desea.

bash_redir_test.sh se parece a:

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

En Linux, funciona muy bien:

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

En 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
fuente
Interesante, desearía tener acceso a Solaris para probar. Si su instancia de Solaris usa el sistema de archivos "/ proc", existen soluciones más confiables que implican la búsqueda de enlaces simbólicos "/ proc" para stdin, stdout y stderr.
Dejay Clayton
1

El siguiente código (probado solo en linux bash 4.4) no debe considerarse portátil ni recomendado , pero en aras de la exhaustividad aquí es:

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

No sé por qué, pero parece que el descriptor de archivo "3" se crea de alguna manera cuando una función bash tiene STDIN canalizado.

Espero eso ayude,

ATorras
fuente