6. Anexo: Descriptores de ficheros y redirecciones¶
Para facilitar el acceso a los distintos recursos del sistema (zona
de almacenamiento en disco, teclado, pantalla, socket,…), el kernel
de Linux vincula cada recurso con un fichero, real (espacio en disco)
o virtual (en memoria, como /dev
y /proc
). Ejemplos habituales son:
Recurso |
Fichero |
---|---|
Directorio |
|
Pantalla |
|
Sockets TCP |
|
Fichero regular |
|
Teclado (según USB…) |
|
Proceso con PID xxxx |
|
Disco duro |
|
Ratón |
|
Caché ARP |
|
Cuando un proceso necesita acceder a un recurso, debe realizar las siguientes operaciones:
Abrir el fichero correspondiente a dicho recurso: el proceso indica al sistema la ruta del fichero, especificando su intención de abrirlo para lectura (obtener información del recurso), escritura (enviar información al recurso) o lectura/escritura; el sistema concederá el tipo de acceso solicitado según las características del recurso (teclado, pantalla, …) y los permisos que el usuario efectivo del proceso posea sobre el fichero. Como respuesta, el sistema devolverá al proceso el descriptor (o descriptor de fichero) con el que el proceso podrá acceder al fichero.
Acceder al fichero/recurso usando el descriptor suministrado por el sistema: el proceso indicará el descriptor al kernel, éste localiza el fichero y accede al recurso asociado.
Recurso
- kernel
⇔
Fichero
- proceso
⇔
Descriptor
Un descriptor no es más que un número entero «n», con las siguientes características:
Cada proceso tiene su propia tabla de descriptores para acceder a los recursos/ficheros; el fichero que un proceso tenga asociado al descriptor «n» es independiente del que puedan tener asociado para ese mismo descriptor los demás procesos del sistema. El conjunto de descriptores que tiene asociados un determinado proceso puede obtenerse con
ls /proc/PID/fd/
, cambiandoPID
por el identificador de dicho proceso.Proceso con PID xxxx
Fichero
Descriptor
Fichero_A
0
Fichero_A
1
Fichero_A
2
…
…
Se dice que un descriptor es de entrada, salida o entrada/salida según el fichero al que esté asociado en ese instante (es posible cambiar el fichero al que está asociado un descriptor) haya sido abierto por el proceso que posee dicho descriptor para lectura, escritura o lectura/escritura, respectivamente.
Al trabajar con un intérprete de comandos encontramos que, de manera habitual, los comandos suelen imprimir su información en pantalla y obtenerla del teclado. Este funcionamiento se debe a los dos motivos siguientes:
Muchas aplicaciones, especialmente pensadas para ser usadas en modo consola, son programadas para que, por defecto (sus procesos):
Obtengan datos del (recurso asociado al) descriptor
0
: dado que éste es el descriptor del que convencionalmente las aplicaciones obtienen información, este descriptor suele denominarse «entrada estándar (a pesar de este nombre, dado su uso especial, internamente los descriptores0
,1
y2
son de entrada/salida).Envíen su información al (recurso asociado al) descriptor
1
: dado que éste es el descriptor al que convencionalmente las aplicaciones envían su información, este descriptor suele denominarse «salida estándar».Envíen su información de errores al (recurso asociado al) descriptor
2
: dado que éste es el descriptor al que convencionalmente las aplicaciones envían su información de errores, este descriptor suele denominarse «salida de error estándar».
Esta configuración «estándar» es la empleada por los comandos POSIX. Así, por ejemplo, los comandos
echo cadena
ocat fichero
están preparados para imprimir (enviar la información) en el recurso asociado al descriptor1
(salida estándar), o el comandocat
(sin argumentos) está diseñado para obtener la información del recurso asociado al descriptor0
(entrada estándar).De forma habitual, el proceso (shell) de cualquier consola en modo comandos asocia el descriptor:
0
: al recurso «teclado (siempre, a través del fichero asociado a esos dispositivos (/dev/uinput
,…), que puede variar según su tipo).1
: al recurso «pantalla».2
: al recurso «pantalla».
Por omisión, cuando un proceso padre crea un proceso hijo, el proceso hijo sólo dispone de los descriptores
0
,1
y2
, cada uno asociado al mismo recurso que en el proceso padre (el proceso hijo hereda la asociación descriptor-fichero para esos tres descriptores, el resto de descriptores no se «heredan»). Todo ello lleva a que en una consola de comandos, las órdenesecho cadena
ocat fichero
suelan imprimir por pantalla (recurso del descriptor1
), o el comandocat
(sin argumentos) obtenga la información del teclado (recurso del descriptor0
), al heredar esa asociación descriptor-fichero del shell (proceso padre).
Sobre este comportamiento habitual, los intérpretes de comandos de Linux permiten:
Modificar el fichero (recurso) asociado a cada descriptor estándar
0
,1
o2
.Asociar ficheros a los descriptores no estándar (
3
y posteriores).
Para ello, la sintaxis a aplicar varía según el proceso sobre el que
queramos establecer la nueva asociación fichero-descriptor sea el
propio proceso shell que estamos utilizando, o sobre un comando
(proceso hijo) invocado desde este shell (en programación C, por
ejemplo, se realizaría con la función open
, entre otras).
6.1. Asociación para el Proceso Shell¶
Para ello se emplea el comando exec, mediante las siguientes sintaxis:
Sintaxis |
Funcionalidad |
---|---|
|
Asocia el descriptor de entrada
|
|
Asocia el descriptor de salida
|
|
Asocia el descriptor de entrada y
salida |
|
Asocia el descriptor |
|
Cierra el descriptor |
Por ejemplo, el siguiente comando permite asociar el archivo
/tmp/fichero
con el descriptor 4
:
exec 4< /tmp/fichero
A partir de ello, cada vez que queramos que un comando realice operaciones de lectura/escritura sobre ese archivo, podremos aplicar los operadores de redirección (detallados más abajo) con dicho descriptor (en lugar de usar el nombre del archivo). Por ejemplo, para que el comando ls envíe su información a ese archivo:
ls >&4
6.2. Asociación para un comando (proceso hijo) invocado desde el shell¶
Ello se consigue mediante la técnica de «redirección», basada en
la siguiente sintaxis (es importante que no haya espacios entre n
y
op
):
comando [n]op fichero/descriptor
donde:
comando
: comando sobre el que aplicar la redirección. Recuerde que, conforme a la sintaxis de los comandos simples, la redirección puede escribirse tanto después como antes del comando.n
(número entero, opcional): descriptor (asociado actualmente o no a algún fichero en el shell actual). POSIX exige que se soporten, al menos, los valores0
,1
,…,9
. El valor por omisión depende del operador empleado.op
: operador de redirección. Si se «escapa» el descriptorn
se usará el descriptor por omisión (e.g.echo \2>fichero
asumirá el descriptor1
). Si se escapa el operador de redirección, no se aplicará redirección alguna (e.g.,echo 2\>fichero
imprime2\>fichero
al recurso del descriptor1
)fichero/descriptor
: a asociar (según indique el operador) con el descriptorn
.
Al aplicar esa redirección sobre el comando, para el descriptor n
, el
comando (el proceso hijo que se crea) no heredará (aún en el caso de
ser 0
, 1
o 2
) la asociación descriptor-fichero del proceso shell
padre, sino que asociará a dicho descriptor n
el fichero
explícitamente indicado.
A continuación se resumen los operadores de redirección definidos por el estándar POSIX, agrupadas según operen sobre descriptores para entrada (lectura de fichero) o salida (escritura en fichero):
Redirección |
El shell invoca comando como proceso hijo, configurándolo para que: |
---|---|
|
Asocie el descriptor de entrada
(recuérde que, para abreviar, se
está usando el calificativo
«descriptor de entrada» para
indicar que en ese instante el
descriptor está asociado con un
fichero abierto para lectura,
pero puede cambiar) |
|
Asocie el descriptor de entrada
|
|
Cierre el descriptor de entrada n (elimina su asociación con el fichero al que actualmente esté vinculado). Si el descriptor no está asociado con ningún fichero, dará error. |
|
Asocie el descriptor de entrada
|
|
Ídem |
Redirección |
El shell invoca comando como proceso hijo, configurándolo para que: |
---|---|
|
Asocie el descriptor de salida
|
|
Ídem |
|
Ídem |
|
Asocie el descriptor de salida
|
|
Cierre el descriptor de salida
|
Redirección |
El shell invoca comando como proceso hijo, configurándolo para que: |
---|---|
|
Asocie el descriptor |
Además de las anteriores, existe un tipo especial de redirección denominado tubería o pipeline (o pipe), en la cual el «recurso» sería esa «tubería» que conecta dos descriptores. Esta redirección se basa en la siguiente sintaxis:
comando1 | comando2
bajo la cual, el descriptor 0
de comando2
se asociaría con el
descriptor 1
de comando1
(esto es, lo que comando1
envíe a su salida
estándar será redirigido a la entrada estándar de comando2
). Pueden
usarse dos o más comandos separados por |
.
A continuación se proponen varios ejemplos de redirección y su explicación:
|
Por omisión |
|
|
|
Toda la información escrita tras
invocar el comando es guardada en
el recurso especial
|
|
El descriptor de salida de |
Adicionalmente a lo anterior, deben realizarse las siguientes aclaraciones sobre las redirecciones:
Las redirecciones son aplicables a cualquier comando, incluso a subshells (shell abierto desde otro shell), de modo que todos los comandos abiertos desde dicho subshell heredarán por defecto la asociación de los descriptores
0
,1
y2
.El orden de lectura de los comandos es importante. Por ejemplo,
(cat << fin) < fichero
no hará que fichero se pase acat
, dado que< fichero
no se aplicará hasta que no haya terminado el comandocat
. Por ejemplo:ls > fichero #"Ctrl-D" termina (EOF) cat #Teclear fin e intro para terminar cat << fin ... fin #Teclear fin e intro para terminar # no muestra el fichero (cat << fin) < fichero
El orden en el que se escriban las redirecciones es importante, pudiendo cambiar el resultado. Por ejemplo:
ls /tmp 2> file 1>&2
Sucedería lo siguiente (en este orden):
El descriptor
2
se asocia confile
.El descriptor
1
se asocia con el mismo fichero al que está asociado el descriptor2
, luego confile
también.Se ejecuta el comando
ls
, que enviará sus salidas estándar1
y de errores2
afile
. En un comando simple, el comando siempre se ejecuta tras aplicar las posibles expansiones/sustituciones e interpretar las redirecciones (leyéndose en orden de izquierda a derecha).
1>&2 ls /tmp 2> file
Sucederá lo siguiente (en este orden):
El descriptor
1
se asocia con el mismo fichero (o recurso, e.g. pantalla) al que actualmente esté asociado el descriptor2
.El descriptor
2
se asocia confile
.Se ejecuta el comando
ls
, que enviará su salida de errores afile
, y su salida estándar1
con ese recurso (posiblemente pantalla) al que inicialmente estuviese asociado el descriptor2
.
Para redireccionar una información a «ninguna parte» se usa el fichero nulo
/dev/null
(fichero que podría considerarse asociado al recurso virtual «destructor de información») . Por ejemplo, el siguiente comando haría toda la información quels
envíe a la salida estándar y a la salida de errores sea enviada a/dev/null
(se elimina):ls -l /usr > /dev/null 2>&1
TAREAS
Ejecute y analice el funcionamiento de las redirecciones empleadas en los siguientes comandos:
cat << END > fichero
Lee información del teclado, hasta que se introduce una línea con
END
. Entonces copia toda la información tecleada al archivofichero
.ls -l /tmp > fich 2> err
Redirige la salida estándar al archivo
fich
y la salida de error al ficheroerr
.ls -l /tmp > fich 2>&1
Redirige las salidas estándar y de error al archivo
fich
.ls 2> /dev/null 1>&2
Redirige las salidas estándar y de error al archivo nulo.
Por último, advertir que la explicación anterior corresponde al
estándar POSIX. Algunos intérpretes de comandos como bash soportan
otros operadores (además de los POSIX). Por ejemplo, para asociar
los descriptores 1
y 2
con el recurso fich
, con bash
se podría realizar con los siguientes comandos (no POSIX):
# bashism (no POSIX)
# Asocia los descriptores 1 y 2 a "fich"
# Hay 2 alternativas equivalentes
cmd &> fich
cmd >& fich
Mientras que el equivalente POSIX es:
# POSIX
# Asocia los descriptores 1 y 2 a "fich"
cmd > fich 2>&1