Perché $ ls > ls.out
'ls.out' viene incluso nell'elenco dei nomi dei file nella directory corrente? Perché è stato scelto questo? Perché no altrimenti?
ls > ../ls.out
Perché $ ls > ls.out
'ls.out' viene incluso nell'elenco dei nomi dei file nella directory corrente? Perché è stato scelto questo? Perché no altrimenti?
ls > ../ls.out
Risposte:
Quando si valuta il comando, il >
reindirizzamento viene risolto per primo: quindi al momento ls
dell'esecuzione il file di output è già stato creato.
Questo è anche il motivo per cui leggere e scrivere nello stesso file usando un >
reindirizzamento all'interno dello stesso comando tronca il file; al momento dell'esecuzione del comando il file è già stato troncato:
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
Trucchi per evitare questo:
<<<"$(ls)" > ls.out
(funziona per qualsiasi comando che deve essere eseguito prima che il reindirizzamento venga risolto)
La sostituzione del comando viene eseguita prima della valutazione del comando esterno, quindi ls
viene eseguita prima della ls.out
creazione:
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
ls | sponge ls.out
(funziona per qualsiasi comando che deve essere eseguito prima che il reindirizzamento venga risolto)
sponge
scrive nel file solo quando il resto della pipe ha terminato l'esecuzione, quindi ls
viene eseguito prima della ls.out
creazione ( sponge
viene fornito con il moreutils
pacchetto):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(funziona per ls > ls.out
il caso specifico)
L'espansione del nome file viene eseguita prima della risoluzione del reindirizzamento, quindi ls
verrà eseguita sui suoi argomenti, che non conterranno ls.out
:
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
Sul motivo per cui i reindirizzamenti vengono risolti prima del programma / script / qualunque cosa venga eseguita, non vedo un motivo specifico per cui è obbligatorio farlo, ma vedo due motivi per cui è meglio farlo:
non reindirizzare STDIN in anticipo renderebbe il programma / script / qualunque cosa trattenere fino a quando STDIN viene reindirizzato;
non reindirizzare preventivamente STDOUT dovrebbe necessariamente rendere il buffer della shell l'output del programma / script / qualunque cosa fino a quando STDOUT viene reindirizzato;
Quindi una perdita di tempo nel primo caso e una perdita di tempo e memoria nel secondo caso.
Questo è proprio quello che mi viene in mente, non sto sostenendo che questi sono i motivi reali; ma immagino che tutto sommato, se si avesse una scelta, andrebbero comunque con il reindirizzamento prima per i motivi di cui sopra.
Da man bash
:
RIDIREZIONE
Prima dell'esecuzione di un comando, l'input e l'output possono essere reindirizzati utilizzando una notazione speciale interpretata dalla shell. Il reindirizzamento consente agli handle di file dei comandi di essere duplicati, aperti, chiusi, fatti fare riferimento a diversi file e può cambiare i file da cui il comando legge e scrive.
Prima frase, suggerisce che l'output viene fatto per andare in un posto diverso dal stdin
reindirizzamento proprio prima dell'esecuzione del comando. Pertanto, per essere reindirizzato al file, il file deve prima essere creato dalla shell stessa.
Per evitare di avere un file, ti suggerisco di reindirizzare prima l'output sulla pipe denominata, quindi su file. Notare l'uso di &
per restituire all'utente il controllo sul terminale
DIR:/xieerqi
skolodya@ubuntu:$ mkfifo /tmp/namedPipe.fifo
DIR:/xieerqi
skolodya@ubuntu:$ ls > /tmp/namedPipe.fifo &
[1] 14167
DIR:/xieerqi
skolodya@ubuntu:$ cat /tmp/namedPipe.fifo > ls.out
Ma perché?
Pensa a questo: dove sarà l'output? Un programma ha funzioni come printf
, sprintf
, puts
, che tutti per default Vai a stdout
, ma può essere la loro produzione andato a file se il file non esiste, in primo luogo? È come l'acqua. Riesci a prendere un bicchiere d'acqua senza prima mettere il bicchiere sotto il rubinetto?
Non sono in disaccordo con le risposte attuali. Il file di output deve essere aperto prima dell'esecuzione del comando o il comando non avrà da nessuna parte per scrivere il suo output.
Questo perché "tutto è un file" nel nostro mondo. L'output sullo schermo è SDOUT (ovvero il descrittore di file 1). Perché un'applicazione scriva sul terminale, apre fd1 e vi scrive come un file.
Quando reindirizzi l'output di un'applicazione in una shell, stai modificando fd1 in modo che indichi effettivamente il file. Quando si esegue il pipe, si modifica lo STDOUT di un'applicazione per diventare un altro STDIN (fd0).
Ma è bello dirlo, ma puoi facilmente vedere come funziona strace
. È roba piuttosto pesante ma questo esempio è piuttosto breve.
strace sh -c "ls > ls.out" 2> strace.out
All'interno strace.out
possiamo vedere i seguenti punti salienti:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Questo si apre ls.out
come fd3
. Scrivi solo. Tronca (sovrascrive) se esiste, altrimenti crea.
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
Questo è un po 'di giocoleria. Spostiamo STDOUT (fd1) su fd10 e lo chiudiamo. Questo perché non stiamo trasmettendo nulla al vero STDOUT con questo comando. Termina duplicando la maniglia di scrittura ls.out
e chiudendo quella originale.
stat("/opt/wine-staging/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/home/oli/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0
Questo è alla ricerca dell'eseguibile. Una lezione forse per non avere un lungo cammino;)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0961324a10) = 31933
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 31933
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31933, si_status=0, si_utime=0, si_stime=0} ---
rt_sigreturn() = 31933
dup2(10, 1) = 1
close(10) = 0
Quindi il comando viene eseguito e il genitore attende. Durante questa operazione qualsiasi STDOUT sarà effettivamente mappato sull'handle del file aperto su ls.out
. Quando il figlio emette SIGCHLD
, questo dice al processo genitore che è finito e che può riprendere. Termina con un po 'più di giocoleria e una chiusura di ls.out
.
Perché c'è così tanto giocoleria? No, non ne sono del tutto sicuro.
Ovviamente puoi cambiare questo comportamento. È possibile memorizzare nella memoria con qualcosa di simile sponge
e che sarà invisibile dal comando procedendo. Stiamo ancora influenzando i descrittori di file, ma non in modo visibile nel file system.
ls | sponge ls.out
C'è anche un bell'articolo sull'implementazione del reindirizzamento e degli operatori di pipe nella shell . Il che mostra come il reindirizzamento potrebbe essere implementato in modo che $ ls > ls.out
possa apparire come:
main(){
close(1); // Release fd no - 1
open("ls.out", "w"); // Open a file with fd no = 1
// Child process
if (fork() == 0) {
exec("ls");
}
}