Dlaczego „ls> ls.out” powoduje, że „ls.out” znajduje się na liście nazwisk?

26

Dlaczego $ ls > ls.out„ls.out” znajduje się na liście nazw plików w bieżącym katalogu? Dlaczego został wybrany? Dlaczego nie inaczej?

Edward Torvalds
źródło
3
prawdopodobnie dlatego, że najpierw tworzy plik ls.out, a następnie zapisuje w nim dane wyjściowe
Dimitri Podborski
1
Jeśli chcesz uniknąć włączenia, zawsze możesz zapisać plik wyjściowy w innym katalogu. Na przykład możesz wybrać katalog nadrzędny (pod warunkiem, że nie jesteś w katalogu głównym systemu plików) za pomocą ls > ../ls.out
Elder Geek

Odpowiedzi:

36

Podczas oceny polecenia >przekierowanie jest rozwiązywane w pierwszej kolejności: więc z biegiem czasu lsplik wyjściowy został już utworzony.

Jest to również powód, dla którego odczyt i zapis do tego samego pliku przy użyciu >przekierowania w tym samym poleceniu obcina plik; do czasu uruchomienia polecenia plik został już obcięty:

$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$ 

Sztuczki, aby tego uniknąć:

  • <<<"$(ls)" > ls.out (działa dla każdego polecenia, które musi zostać uruchomione przed rozwiązaniem problemu z przekierowaniem)

    Podstawienie polecenia jest uruchamiane przed oceną polecenia zewnętrznego, więc lsjest uruchamiane przed ls.oututworzeniem:

    $ ls
    bar  foo
    $ <<<"$(ls)" > ls.out
    $ cat ls.out 
    bar
    foo
  • ls | sponge ls.out (działa dla każdego polecenia, które musi zostać uruchomione przed rozwiązaniem problemu z przekierowaniem)

    spongezapisuje do pliku dopiero po zakończeniu wykonywania reszty potoku, więc lsjest uruchamiany przed ls.oututworzeniem ( spongejest dostarczany z moreutilspakietem):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
  • ls * > ls.out(działa w ls > ls.outkonkretnym przypadku)

    Rozwinięcie nazwy pliku jest wykonywane przed rozstrzygnięciem przekierowania, więc lsbędzie działać na jego argumentach, które nie będą zawierać ls.out:

    $ ls
    bar  foo
    $ ls * > ls.out
    $ cat ls.out 
    bar
    foo
    $

Dlaczego przekierowania są rozwiązywane przed uruchomieniem programu / skryptu / czegokolwiek, nie widzę konkretnego powodu, dla którego jest to obowiązkowe , ale widzę dwa powody, dla których lepiej to zrobić:

  • nie przekierowanie STDIN wcześniej spowoduje zatrzymanie programu / skryptu / czegokolwiek, dopóki STDIN nie zostanie przekierowany;

  • brak wcześniejszego przekierowania STDOUT powinien koniecznie spowodować, że powłoka buforuje wyjście programu / skryptu / cokolwiek, dopóki STDOUT nie zostanie przekierowany;

Więc strata czasu w pierwszym przypadku i strata czasu i pamięci w drugim przypadku.

Właśnie to mi się przydarza. Nie twierdzę, że to są prawdziwe powody; ale wydaje mi się, że w sumie, gdyby ktoś miał wybór, i tak przekierowaliby go wcześniej z wyżej wymienionych powodów.

kos
źródło
1
Zauważ, że podczas przekierowania powłoka tak naprawdę nie dotyka danych (ani przekierowania wejściowego, ani wyjściowego). Po prostu otwiera plik i przekazuje deskryptor pliku do programu.
Peter Green
11

Od man bash:

REDYKCJA

Przed wykonaniem polecenia jego dane wejściowe i wyjściowe mogą zostać przekierowane przy użyciu specjalnej notacji interpretowanej przez powłokę. Przekierowanie pozwala duplikować, otwierać, zamykać uchwyty plików poleceń, odwoływać się do różnych plików oraz zmieniać pliki, z których polecenie czyta i zapisuje.

Pierwsze zdanie sugeruje, że dane wyjściowe są wysyłane w miejsce inne niż stdinprzekierowanie tuż przed wykonaniem polecenia. Dlatego, aby zostać przekierowanym do pliku, plik musi najpierw zostać utworzony przez samą powłokę.

Aby uniknąć posiadania pliku, sugeruję przekierowanie wyjścia do nazwanego potoku, a następnie do pliku. Zwróć uwagę na użycie, &aby zwrócić użytkownikowi kontrolę nad terminalem

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

Ale dlaczego?

Pomyśl o tym - gdzie będzie wynik? Program posiada funkcje takie jak printf, sprintf, puts, które domyślnie idź do stdout, ale może ich być już do wyjścia pliku, jeśli plik nie istnieje w pierwszej kolejności? To jest jak woda. Czy możesz dostać szklankę wody, nie wkładając najpierw szklanki pod kran?

Sergiy Kolodyazhnyy
źródło
10

Nie zgadzam się z obecnymi odpowiedziami. Plik wyjściowy musi zostać otwarty przed uruchomieniem polecenia, w przeciwnym razie polecenie nie będzie miało miejsca na zapisanie danych wyjściowych.

Wynika to z faktu, że „wszystko jest plikiem” w naszym świecie. Wyjście na ekran to SDOUT (inaczej deskryptor pliku 1). Aby aplikacja mogła pisać na terminalu, otwiera fd1 i zapisuje w nim jak plik.

Kiedy przekierowujesz dane wyjściowe aplikacji w powłoce, zmieniasz fd1, tak aby wskazywał na plik. Podczas potoku zmieniasz STDOUT jednej aplikacji, aby stał się STDIN innej aplikacji (fd0).


Ale miło jest to powiedzieć, ale możesz dość łatwo sprawdzić, jak to działa strace. To dość ciężkie rzeczy, ale ten przykład jest dość krótki.

strace sh -c "ls > ls.out" 2> strace.out

Wewnątrz strace.outmożemy zobaczyć następujące najważniejsze informacje:

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

To otwiera się ls.outjako fd3. Tylko pisać. Obcina (zastępuje), jeśli istnieje, w przeciwnym razie tworzy.

fcntl(1, F_DUPFD, 10)                   = 10
close(1)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0

To trochę żonglerka. Przesuwamy STDOUT (fd1) do fd10 i zamykamy go. Wynika to z faktu, że za pomocą tego polecenia nie wypisujemy niczego na prawdziwy STDOUT. Kończy się poprzez skopiowanie dojścia do zapisu ls.outi zamknięcie oryginalnego.

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

To jest szukanie pliku wykonywalnego. Lekcja może nie mieć długiej ścieżki;)

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

Następnie polecenie jest uruchamiane i rodzic czeka. Podczas tej operacji dowolne STDOUT zostanie faktycznie zamapowane na uchwyt otwartego pliku ls.out. Gdy dziecko poda SIGCHLDproblem, informuje to, że proces nadrzędny jest zakończony i że może wznowić. Kończy się trochę żonglowaniem i zakończeniem ls.out.

Dlaczego istnieje tak wiele żonglerka? Nie, nie jestem też do końca pewien.


Oczywiście możesz zmienić to zachowanie. Możesz buforować do pamięci z czymś podobnym, spongea to będzie niewidoczne z polecenia. Nadal wpływamy na deskryptory plików, ale nie w sposób widoczny dla systemu plików.

ls | sponge ls.out
Oli
źródło
6

Jest też fajny artykuł na temat implementacji przekierowań i operatorów potoków w powłoce . Co pokazuje, jak można zastosować przekierowanie, aby $ ls > ls.outmogło wyglądać:

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"); 
    }
}
Dimitri Podborski
źródło