En Bash, ¿para qué sirve el descriptor de archivo 255? ¿Puedo usarlo?

10

Entiendo que el descriptor de archivo (o controlador de archivo) es una técnica de IO de archivo en sistemas Linux.

También sé que cada proceso tiene 3 flujos estándar (a saber, stdin, stdout y stderr) que están representados por archivos con descriptores de 0 a 3.

Sin embargo, noto que todos los procesos que examiné lsof -p <pid>tienen un descriptor de archivo adicional 255con permiso de lectura.

De esta respuesta , aprendí que esta característica es específica del shell Bash , sin embargo, tanto la respuesta como la fuente a la que se hace referencia realmente no explican para qué sirve este descriptor de archivo.

Mi pregunta:

  1. ¿Para qué sirve el descriptor de archivo 255?
  2. ¿Puedo usarlo en mi script Bash o es solo un mecanismo de trabajo interno que no debe usarse / manipularse manualmente?
Tran Triet
fuente
En mi opinión, sus preguntas fueron respondidas en la página vinculada.
Cyrus
Examinaré la respuesta nuevamente para ver si responde mi pregunta. Ahora me di cuenta de que usted fue quien dio esa respuesta;)
Tran Triet
2
@Cyrus dice que "es un pequeño truco" sin explicar qué es ese "pequeño truco" no es una respuesta adecuada.
mosvy
El primer comentario sobre la respuesta vinculada parece tener una mejor discusión ... La última respuesta es probablemente lo que está buscando ...
RubberStamp
Relacionado: askubuntu.com/a/866722/772601
Isaac

Respuestas:

12

Para la última parte de tu pregunta:

¿Puedo usarlo?

De man bash:

Las redirecciones que utilizan descriptores de archivo superiores a 9 deben usarse con cuidado, ya que pueden entrar en conflicto con los descriptores de archivo que el shell usa internamente.

Entonces, si te refieres a usar como crear un nuevo fd con ese número, la respuesta es no.

Si te refieres a usar como: "escribe a esa fd":

$ echo hello >/dev/fd/255"

O para leerlo:

$ read a </dev/fd/255
abc
$ echo "$a"
abc

la respuesta es sí.
Pero, probablemente, debería ser mejor (independiente del shell) usar /dev/ttypara acceder a tty.

¿para qué sirve el descriptor de archivo 255?

Como una conexión alternativa al tty en caso de que fd 1 ( /dev/stdout) y fd 0 ( /dev/stdin) se bloqueen.

Más detalle .

Otros shells pueden usar un número diferente (como 10 en zsh)

$ zsh
mail% ls -l /proc/self/fd /proc/$$/fd/* &
[1] 3345
mail% lrwx------ 1 isaac isaac 64 Oct 14 09:46 /proc/3250/fd/0 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 /proc/3250/fd/1 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 /proc/3250/fd/10 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 /proc/3250/fd/2 -> /dev/pts/2

/proc/self/fd:
total 0
lrwx------ 1 isaac isaac 64 Oct 14 09:50 0 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 1 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 2 -> /dev/pts/2
lr-x------ 1 isaac isaac 64 Oct 14 09:50 3 -> /proc/3345/fd

[1]  + done       ls -l /proc/self/fd /proc/$$/fd/*
mail% 

De la lista de correo :

Fd 255 se usa internamente como una conexión al tty, de modo que no interfiere con el uso de exec para reubicar fds. Bash también asigna fds altos cuando maneja una sustitución de proceso '<(foo)', por la misma razón.
Andreas Schwab

Isaac
fuente
fd 255 no se usa para "guardar una copia de fd 1 y fd 0"; puede verificarlo fácilmente con dd bs=1 | bash -i -c 'sleep .1; ls -l /proc/$$/fd' 2>/tmp/err | tee /tmp/out. Además, ese comentario de la lista de correo trata sobre cuándo bashse ejecuta como bash scriptfile( 255siendo en ese caso el controlador abierto para scriptfile, y en ese caso, ls -l /proc/pid/fdse imprimirá de manera muy convincente 255 -> scriptfile;-)), no sobre cuándo se ejecuta de forma interactiva.
mosvy
Lamento que los fragmentos de código fuente y el análisis de mi respuesta no lo convencieron, pero para ser claros: a) no es una conexión "alternativa" al tty, sino la conexión principal al tty, utilizada para todos tty propósitos relacionados b) se copia desde fd 2 (stderr) o se abre directamente desde /dev/tty, no desde fd 0 o fd 1 c) si fds 0, 1 o 2 se "bloquean", bash no usará ese 255 fd como alternativa para leer la entrada del usuario o para escribir la salida de comandos, instrucciones, mensajes de error, etc.
mosvy
8

Ese 255descriptor de archivo es un identificador abierto para el tty de control y solo se usa cuando bashse ejecuta en modo interactivo.

Le permite redirigir stderren el shell principal, al tiempo que permite que el control de trabajo funcione (es decir, poder matar procesos con ^ C, interrumpirlos con ^ Z, etc.).

Ejemplo:

$ exec 2> >(tee /tmp/err); ls /nosuchfile; sleep 1000

Si intenta eso en un shell como ksh93, que simplemente usa el descriptor de archivo 2 como referencia al terminal de control, el sleepproceso se volverá inmune a ^ C y ^ Z, y tendrá que ser eliminado desde otra ventana / sesión. Esto se debe a que el shell no podrá establecer el grupo de procesos sleepcomo primer plano en el terminal tcsetgrp(), ya que el descriptor de archivo 2 ya no apunta al terminal.

Esto no es bashespecífico, también se usa dashy zsh, solo que el descriptor no se mueve tan alto (generalmente es 10).

zsh también usará esa fd para hacer eco de las indicaciones y la entrada del usuario, por lo que simplemente funcionará lo siguiente:

$ exec 2>/tmp/err
$ 

No tiene nada que ver con los identificadores de archivos que bashse utilizan al leer secuencias de comandos y configurar tuberías (que también se duplican con la misma función move_to_high_fd()), como se sugirió en otras respuestas y comentarios.

bashestá utilizando un número tan grande para permitir que los fds sean más grandes que 9para ser utilizados con redireccionamientos en shell (por ejemplo exec 87<filename); eso no es compatible con otros proyectiles.

Puede usar ese identificador de archivo usted mismo, pero no tiene mucho sentido hacerlo, porque puede obtener un identificador para el mismo terminal de control en cualquier comando con ... < /dev/tty.

Análisis del código fuente de bash :

En bash, el descriptor de archivo del terminal de control se almacena en la shell_ttyvariable. Si el shell es interactivo, esa variable se inicializa (al inicio o después de un exec fallido) jobs.c:initialize_job_control()al duplicarla desde stderr(si stderrestá conectada a un terminal) o al abrirla directamente /dev/tty, y luego se duplica nuevamente a un fd más alto con general.c:move_to_high_fd():

int
initialize_job_control (force)
     int force;
{
  ...
  if (interactive == 0 && force == 0)
    {
      ...
    }
  else
    {
      shell_tty = -1;

      /* If forced_interactive is set, we skip the normal check that stderr
         is attached to a tty, so we need to check here.  If it's not, we
         need to see whether we have a controlling tty by opening /dev/tty,
         since trying to use job control tty pgrp manipulations on a non-tty
         is going to fail. */
      if (forced_interactive && isatty (fileno (stderr)) == 0)
        shell_tty = open ("/dev/tty", O_RDWR|O_NONBLOCK);

      /* Get our controlling terminal.  If job_control is set, or
         interactive is set, then this is an interactive shell no
         matter where fd 2 is directed. */
      if (shell_tty == -1)
        shell_tty = dup (fileno (stderr));        /* fd 2 */

      if (shell_tty != -1)
        shell_tty = move_to_high_fd (shell_tty, 1, -1);
      ...
    }

Si aún shell_ttyno es el tty controlador, entonces se hace así:

          /* If (and only if) we just set our process group to our pid,
             thereby becoming a process group leader, and the terminal
             is not in the same process group as our (new) process group,
             then set the terminal's process group to our (new) process
             group.  If that fails, set our process group back to what it
             was originally (so we can still read from the terminal) and
             turn off job control.  */
          if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp)
            {
              if (give_terminal_to (shell_pgrp, 0) < 0)

shell_tty entonces se usa para

  1. obtener y establecer el grupo de procesos en primer plano con tc[sg]etpgrpin jobs.c:maybe_give_terminal_to(), jobs.c:set_job_control()yjobs.c:give_terminal_to()

  2. obtener y establecer los termios(3)parámetros en jobs.c:get_tty_state()yjobs.c:set_tty_state()

  3. obtener el tamaño de la ventana de terminal con ioctl(TIOCGWINSZ)in lib/sh/winsize.c:get_new_window_size().

move_to_high_fd()generalmente se usa con todos los descriptores de archivos temporales utilizados por bash(archivos de script, tuberías, etc.), de ahí la confusión en la mayoría de los comentarios que aparecen de manera prominente en las búsquedas de Google.

Los descriptores de archivo utilizados internamente por bash, incluidos, shell_ttyestán configurados en close-on-exec, por lo que no se filtrarán a los comandos.

Mosvy
fuente