2011-08-16 5 views
49

Qualcuno può dirmi perché questo non funziona? Sto giocando con i descrittori di file, ma mi sento un po 'perso.Come funzionano i descrittori di file?

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

Le prime tre righe funzionano bene, ma gli ultimi due errori fuori. Perché?

risposta

63

I descrittori di file 0, 1 e 2 sono rispettivamente per stdin, stdout e stderr.

I descrittori di file 3, 4, .. 9 sono per file aggiuntivi. Per poterli utilizzare, è necessario prima aprirli. Per esempio:

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

Per ulteriori informazioni un'occhiata a Advanced Bash-Scripting Guide: Chapter 20. I/O Redirection.

+1

Questo è quello che sto cercando! Quindi ho bisogno di specificare un file da usare come spazio di archiviazione temporaneo con il comando exec, e poi chiuderli quando ho finito? Scusate, sono un po 'confuso con il comando exec, non lo uso molto. – Trcx

+1

sì, ma non è temporaneo. Il file esisterà anche dopo il completamento del programma. – dogbane

+0

Che potrebbe funzionare bene allora, sto provando a portare alcuni script per essere compatibili con lo scheduler di crontab, ma sto avendo problemi visto che cron non consente il piping di stdout negli script. – Trcx

16

Non funziona perché quei descrittori di file non puntano a nulla! I normali descrittori di file predefiniti sono lo standard input 0, lo standard output 1 e lo standard error stream 2. Poiché lo script non sta aprendo altri file, non ci sono altri descrittori di file validi. È possibile aprire un file in bash utilizzando exec. Ecco una modifica del vostro esempio:

#!/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 

E ora faremo eseguirlo:

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

Come si può vedere, l'output in più è stato inviato ai file richiesti.

+0

C'è un modo in cui potrei farlo scrivere l'out put al terminale? Voglio poter essere in grado di vedere tutto nel terminale, ma voglio essere in grado di inviare l'output dove voglio. cioè ./script 2> out.2 3> out.3 4> out.4 – Trcx

+0

@Trcx, se vuoi scrivere sul terminale, usa 'stdout' o' stderr'. Perché dovresti o vuoi usare altri file per questo? –

+0

Gli script che sto cercando di rendere compatibili con crontab devono scrivere più file, ma crontab non consente di scrivere i file dagli script (perché non ha il supporto dello stdout) Tuttavia posso usare crontab per scrivere l'output degli script in un file. Stavo pensando che avrei potuto scrivere gli script nei vari output e poi fare in modo che crontab separi tutto nei file appropriati. Stavo solo cercando un modo per scrivere sui file senza usare lo stdout. Ma grazie a voi ragazzi ho scoperto che stavo pensando troppo. (di nuovo: P) Grazie per l'aiuto! – Trcx

35

È una vecchia domanda ma una cosa ha bisogno di chiarimenti.

Mentre le risposte di Carl Norum e dogbane sono corrette, l'ipotesi è di cambiare lo script per farlo funzionare.

Quello che vorrei sottolineare è che non c'è bisogno di cambiare il copione:

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

Funziona se si richiama in modo diverso:

./fdtest 3>&1 4>&1 

che significa per reindirizzare i descrittori di file 3 e 4 a 1 (che è l'output standard).

Il punto è che lo script è perfettamente bene nel voler scrivere descrittori diversi solo 1 e 2 (stdout e stderr) se tali descrittori sono forniti dal processo genitore.

Il vostro esempio è in realtà molto interessante perché questo script può scrivere a 4 diversi file:

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

Ora avete l'output in 4 file separati:

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

Che cosa è più interessante su di esso è che il tuo programma non deve avere permessi di scrittura per quei file, perché in realtà non li apre.

Per esempio, quando ho eseguito sudo -s di cambiare utente di root, creare una directory come root, e provare a eseguire il seguente comando come il mio utente normale (RSP nel mio caso) come questo:

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

ottengo un errore:

bash: file1.txt: Permission denied 

Ma se faccio il reindirizzamento al di fuori di su:

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

(si noti la differenza tra apici) funziona e ottengo:

# 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 

che sono 4 file di proprietà di root in una directory di proprietà di root - anche se lo script non aveva autorizzazioni per creare quei file.

Un altro esempio potrebbe essere utilizzare chroot jail o un contenitore ed eseguire un programma all'interno del quale non avrebbe accesso a quei file anche se è stato eseguito come root e reindirizza ancora quei descrittori esternamente dove è necessario, senza fornire effettivamente l'accesso all'intero file system o qualsiasi altra cosa a questo script.

Il punto è che hai scoperto un meccanismo molto interessante e utile. Non è necessario aprire tutti i file all'interno del tuo script come suggerito in altre risposte. A volte è utile reindirizzarli durante l'invocazione dello script.

Insomma, questo:

echo "This" 

è in realtà equivale a:

echo "This" >&1 

e l'esecuzione del programma come:

./program >file.txt 

è lo stesso:

./program 1>file.txt 

Il numero 1 è solo un numero predefinito ed è stdout.

Ma anche questo programma:

#!/bin/bash 
echo "This" 

può produrre un errore "descrittore". Come? Quando eseguito come:

./fdtest2 >&- 

L'output sarà:

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

Aggiungendo >&- (che è lo stesso come 1>&-) significa chiudere l'uscita standard. Aggiungere 2>&- significherebbe chiudere lo stderr.

Si può anche fare una cosa più complicata.Lo script originale:

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

quando eseguito con un semplice:

./fdtest 

stampe:

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

ma si può fare descrittori 3 e 4 di lavoro, ma il numero 1 sicuro eseguendo:

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

Uscite:

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

Se si desidera descrittori sia 1 e 2 non riescono, correre in questo modo:

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

si ottiene:

a 
test. 

Perché? Non è successo nulla? Ha fatto ma senza stderr (numero descrittore di file 2) non hai visto i messaggi di errore!

Penso che sia molto utile sperimentare in questo modo per avere un'idea di come funzionano i descrittori e il loro reindirizzamento.

Il tuo script è davvero un esempio molto interessante - e sostengo che lo non si è rotto affatto, lo stavi solo usando male! :)

+0

V risposta interessante .. Grazie –

+0

, il descrittore di file 1 è stdout; stdin è il descrittore di file 0. – programmerjake

+0

@programmerjake Oops. Errore di battitura fisso. Grazie per segnalarlo. – rsp