¿Cómo detectar si mi script de shell se está ejecutando a través de un conducto?

¿Cómo puedo detectar desde un script de shell si su salida estándar está siendo enviada a un terminal o si está conectada 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 interactivamente, pero no cuando se canaliza, de forma similar a lo que hace ls --color .

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 es un terminal”, porque la salida del paréntesis está canalizada a cat .


El -t se describe en páginas man como

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

… donde fd puede ser una de las asignaciones usuales del descriptor de archivos:

 0: stdin 1: stdout 2: stderr 

No hay una forma infalible de determinar si STDIN, STDOUT o STDERR se canalizan hacia / desde su secuencia de comandos, principalmente debido a progtwigs 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 ssh no TTY, las transmisiones STD siempre se ven como si estuvieran siendo canalizadas. Para demostrar esto, usar 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 qué es importante

Esto es bastante importante, porque implica que no hay forma de que un script bash indique si un comando ssh no tty está siendo canalizado o no. Tenga en cuenta que este comportamiento desafortunado se introdujo cuando las versiones recientes de ssh comenzaron a usar pipes para STDIO no TTY. Las versiones anteriores usaban sockets, que PODRÍAN diferenciarse dentro de bash usando [[ -S ]] .

Cuando importa

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

 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, ejecutar un comando que lea STDIN cuando no haya ninguna entrada disponible desde cualquiera de los conductos o la redirección dará lugar a que el script cuelgue y espere la entrada de STDIN.

Otras cosas que no funcionan

Al tratar de resolver este problema, he analizado varias técnicas que no resuelven el problema, incluidas las que implican:

  • examinar las variables de entorno SSH
  • usando stat en los descriptores de archivo / dev / stdin
  • examinando el modo interactivo a través de [[ "${-}" =~ 'i' ]]
  • examinando el estado de tty a través de tty y tty -s
  • examinando el estado de ssh través de [[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

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

Soy extremadamente interesante para resolver este problema, por lo tanto, 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.

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

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

Consulte ” man test ” o ” man bash ” y busque ” -t

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 

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

bash_redir_test.sh se ve así:

 [[ -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 genial:

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