2011-08-16 19 views
49

¿Puede alguien decirme por qué esto no funciona? Estoy jugando con las descripciones de los archivos, pero me siento un poco perdido.¿Cómo funcionan los descriptores de archivos?

#!/bin/bash 
echo "This" 
echo "is" >&2 
echo "a" >&3 
echo "test." >&4 

Las tres primeras líneas funcionan bien, pero faltan los dos últimos. ¿Por qué?

Respuesta

63

Los descriptores de archivo 0, 1 y 2 son para stdin, stdout y stderr, respectivamente.

Los descriptores de archivos 3, 4, .. 9 son para archivos adicionales. Para usarlos, primero debes abrirlos. Por ejemplo:

exec 3<> /tmp/foo #open fd 3. 
echo "test" >&3 
exec 3>&- #close fd 3. 

Para obtener más información echar un vistazo a Advanced Bash-Scripting Guide: Chapter 20. I/O Redirection.

+1

¡Eso es lo que estoy buscando! Entonces, ¿debo especificar un archivo para que lo use como un lugar de almacenamiento temporal con el comando exec, y luego cerrarlo cuando haya terminado? Lo siento, estoy un poco confuso con el comando ejecutivo, no lo uso mucho. – Trcx

+1

sí, pero no es temporal. El archivo existirá incluso después de que se complete su programa. – dogbane

+0

Eso podría funcionar bien, estoy intentando portar algunas secuencias de comandos para que sean compatibles con el programador de tareas crontab, pero estoy teniendo problemas ya que cron no permite la conexión de stdout en las secuencias de comandos. – Trcx

16

¡Está fallando porque esos descriptores de archivos no apuntan a nada! Los descriptores de archivo predeterminados normales son la entrada estándar 0, la salida estándar 1 y la secuencia de error estándar 2. Como su script no abre ningún otro archivo, no hay otros descriptores de archivo válidos. Puede abrir un archivo en bash usando exec. He aquí una modificación de su ejemplo:

#!/bin/bash 
exec 3> out1  # open file 'out1' for writing, assign to fd 3 
exec 4> out2  # open file 'out2' for writing, assign to fd 4 

echo "This"  # output to fd 1 (stdout) 
echo "is" >&2 # output to fd 2 (stderr) 
echo "a" >&3  # output to fd 3 
echo "test." >&4 # output to fd 4 

Y ahora vamos a ejecutarlo:

$ ls 
script 
$ ./script 
This 
is 
$ ls 
out1 out2 script 
$ cat out* 
a 
test. 
$ 

Como se puede ver, la producción adicional fue enviada a los archivos solicitados.

+0

¿Hay alguna manera de que pueda escribir la salida al terminal? Quiero poder ver todo en la terminal, pero quiero poder enviar la salida a donde quiero. es decir ./script 2> out.2 3> out.3 4> out.4 – Trcx

+0

@Trcx, si desea escribir en el terminal, use 'stdout' o' stderr'. ¿Por qué necesitarías o quieres usar otros archivos para eso? –

+0

Las secuencias de comandos que intento hacer compatibles con crontab necesitan escribir varios archivos, pero crontab no permite que se escriban archivos desde las secuencias de comandos (debido a que carece de soporte de stdout). Sin embargo, puedo usar crontab para escribir la salida de las secuencias de comandos a un archivo. Estaba pensando que podría hacer que los guiones escriban en las diversas salidas, y luego tener crontab para separar todo en los archivos apropiados. Solo estaba buscando una forma inteligente de escribir en los archivos sin utilizar stdout. Pero gracias a ustedes, descubrí que estaba pensando demasiado. (de nuevo: P) ¡Gracias por la ayuda! – Trcx

35

Es una vieja pregunta pero una cosa necesita aclaración.

Si bien las respuestas de Carl Norum y dogbane son correctas, la suposición es cambiar la secuencia de comandos para que funcione.

Lo que me gustaría señalar es que que no es necesario cambiar el guión:

#!/bin/bash 
echo "This" 
echo "is" >&2 
echo "a" >&3 
echo "test." >&4 

Funciona si se invoca de forma diferente:

./fdtest 3>&1 4>&1 

lo que significa para redirigir los descriptores de archivos 3 y 4 a 1 (que es salida estándar).

El punto es que el guión está perfectamente bien en su deseo de escribir en los descriptores que no sean sólo 1 y 2 (stdout y stderr) si esos descriptores son proporcionados por el proceso padre.

Su ejemplo es en realidad bastante interesante porque este script puede escribir en 4 archivos diferentes:

./fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt 

Ahora usted tiene la salida en 4 archivos separados:

$ for f in file*; do echo $f:; cat $f; done 
file1.txt: 
This 
file2.txt: 
is 
file3.txt: 
a 
file4.txt: 
test. 

¿Cuál es más interesante sobre esto es que su programa no tiene que tener permisos de escritura para esos archivos, porque en realidad no los abre.

Por ejemplo, cuando corro sudo -s para cambiar al usuario root, cree un directorio como raíz, y tratar de ejecutar el siguiente comando como mi usuario normal (RSP en mi caso) así:

# su rsp -c '../fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt' 

me aparece un error:

bash: file1.txt: Permission denied 

Pero si hago el cambio de dirección fuera del su:

# su rsp -c '../fdtest' >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt 

(nótese la diferencia entre comillas simples) funciona y consigo:

# ls -alp 
total 56 
drwxr-xr-x 2 root root 4096 Jun 23 15:05 ./ 
drwxrwxr-x 3 rsp rsp 4096 Jun 23 15:01 ../ 
-rw-r--r-- 1 root root 5 Jun 23 15:05 file1.txt 
-rw-r--r-- 1 root root 39 Jun 23 15:05 file2.txt 
-rw-r--r-- 1 root root 2 Jun 23 15:05 file3.txt 
-rw-r--r-- 1 root root 6 Jun 23 15:05 file4.txt 

cuales son 4 ficheros titularidad de la raíz en un directorio propiedad de la raíz - a pesar de que el guión no tenía permisos para crear esos archivos.

Otro ejemplo sería usar chroot jail o un contenedor y ejecutar un programa dentro del cual no tendría acceso a esos archivos, incluso si se ejecutara como root y aún redirigiera esos descriptores externamente donde lo necesita, sin dar realmente acceso a todo el sistema de archivos o cualquier otra cosa a este script.

El punto es que ha descubierto un mecanismo muy interesante y útil. No tiene que abrir todos los archivos dentro de su script como se sugirió en otras respuestas. Algunas veces es útil redirigirlos durante la invocación del script.

para resumir, esto:

echo "This" 

es en realidad equivalente a:

echo "This" >&1 

y ejecutar el programa como:

./program >file.txt 

es lo mismo que:

./program 1>file.txt 

El número 1 es simplemente un número predeterminado y es stdout.

Pero incluso este programa:

#!/bin/bash 
echo "This" 

puede producir un error de "Bad descriptor". ¿Cómo? Cuando se realiza según:

./fdtest2 >&- 

La salida será:

./fdtest2: line 2: echo: write error: Bad file descriptor 

Adición de >&- (que es el mismo que 1>&-) significa el cierre de la salida estándar. Agregar 2>&- significaría cerrar el stderr.

Incluso puede hacer una cosa más complicada.Su guión original:

#!/bin/bash 
echo "This" 
echo "is" >&2 
echo "a" >&3 
echo "test." >&4 

cuando se ejecuta con sólo:

./fdtest 

impresiones:

This 
is 
./fdtest: line 4: 3: Bad file descriptor 
./fdtest: line 5: 4: Bad file descriptor 

pero se puede hacer descriptores 3 y 4 de trabajo, pero el número 1 fallar ejecutando:

./fdtest 3>&1 4>&1 1>&- 

Emite:

./fdtest: line 2: echo: write error: Bad file descriptor 
is 
a 
test. 

Si quieren tanto descriptores 1 y 2 fallan, se ejecutan de esta manera:

./fdtest 3>&1 4>&1 1>&- 2>&- 

que se obtiene:

a 
test. 

¿Por qué? ¿No falló nada? Lo hizo pero sin stderr (número de descriptor de archivo 2) ¡no vio los mensajes de error!

Creo que es muy útil experimentar de esta manera para tener una idea de cómo funcionan los descriptores y su redirección.

Su secuencia de comandos es un ejemplo muy interesante de hecho, y yo argumento que no está roto en absoluto, ¡solo lo estaba usando incorrectamente! :)

+0

V respuesta interesante. Gracias –

+0

en realidad, el descriptor de archivo 1 es stdout; stdin es el descriptor de archivo 0. – programmerjake

+0

@programmerjake Vaya. Typo arreglado. Gracias por mencionarlo. – rsp

Cuestiones relacionadas