Dlaczego $ ls > ls.out
„ls.out” znajduje się na liście nazw plików w bieżącym katalogu? Dlaczego został wybrany? Dlaczego nie inaczej?
ls > ../ls.out
Dlaczego $ ls > ls.out
„ls.out” znajduje się na liście nazw plików w bieżącym katalogu? Dlaczego został wybrany? Dlaczego nie inaczej?
ls > ../ls.out
Odpowiedzi:
Podczas oceny polecenia >
przekierowanie jest rozwiązywane w pierwszej kolejności: więc z biegiem czasu ls
plik 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 ls
jest uruchamiane przed ls.out
utworzeniem:
$ 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)
sponge
zapisuje do pliku dopiero po zakończeniu wykonywania reszty potoku, więc ls
jest uruchamiany przed ls.out
utworzeniem ( sponge
jest dostarczany z moreutils
pakietem):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(działa w ls > ls.out
konkretnym przypadku)
Rozwinięcie nazwy pliku jest wykonywane przed rozstrzygnięciem przekierowania, więc ls
bę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.
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ż stdin
przekierowanie 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?
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.out
możemy zobaczyć następujące najważniejsze informacje:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
To otwiera się ls.out
jako 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.out
i 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 SIGCHLD
problem, 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, sponge
a 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
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.out
mogł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");
}
}