Perché 'ls> ls.out' fa 'includere' ls.out 'nell'elenco dei nomi?


26

Perché $ ls > ls.out'ls.out' viene incluso nell'elenco dei nomi dei file nella directory corrente? Perché è stato scelto questo? Perché no altrimenti?


3
probabilmente perché prima crea un file ls.out e poi scrive l'output su di esso
Dimitri Podborski,

1
Se si desidera evitare l'inclusione, è sempre possibile archiviare il file di output in una directory diversa. Ad esempio, puoi scegliere la directory principale (purché non sia alla radice del file system) con ls > ../ls.out
Elder Geek,

Risposte:


36

Quando si valuta il comando, il >reindirizzamento viene risolto per primo: quindi al momento lsdell'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 lsviene eseguita prima della ls.outcreazione:

    $ 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)

    spongescrive nel file solo quando il resto della pipe ha terminato l'esecuzione, quindi lsviene eseguito prima della ls.outcreazione ( spongeviene fornito con il moreutilspacchetto):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
  • ls * > ls.out(funziona per ls > ls.outil caso specifico)

    L'espansione del nome file viene eseguita prima della risoluzione del reindirizzamento, quindi lsverrà 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.


1
Si noti che durante il reindirizzamento la shell in realtà non tocca i dati (sul reindirizzamento di input o sul reindirizzamento di output). Apre solo il file e passa il descrittore di file al programma.
Peter Green,

11

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 stdinreindirizzamento 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?


10

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.outpossiamo vedere i seguenti punti salienti:

open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

Questo si apre ls.outcome 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.oute 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 spongee 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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.