Czy przekierowanie z `>>` jest równoważne z `>`, gdy plik docelowy jeszcze nie istnieje?

80

Rozważ powłokę taką jak Bash lub sh. Podstawowa różnica między >i >>przejawia się w przypadku, gdy istnieje plik docelowy:

  • > obcina plik do zera, a następnie zapisuje;
  • >> nie obcina się, zapisuje (dołącza) na końcu pliku.

Jeśli plik nie istnieje, jest tworzony z zerowym rozmiarem; następnie napisane do. Dotyczy to obu operatorów. Może się wydawać, że operatory są równoważne, gdy plik docelowy jeszcze nie istnieje.

Czy oni są naprawdę?

Kamil Maciorowski
źródło

Odpowiedzi:

107

tl; dr

Nie. >>Zasadniczo „zawsze dąży do końca pliku”, >zachowując wskaźnik do ostatniej zapisanej lokalizacji.


Pełna odpowiedź

(Uwaga: wszystkie moje testy przeprowadzono na Debian GNU / Linux 9).

Kolejna różnica

Nie, nie są równoważne. Jest jeszcze jedna różnica. Może się manifestować niezależnie od tego, czy plik docelowy istniał wcześniej, czy nie.

Aby to zaobserwować, uruchom proces, który generuje dane i przekierowuje do pliku za pomocą >lub >>(np pv -L 10k /dev/urandom > blob.). Pozwól mu działać i zmień rozmiar pliku (np. Za pomocą truncate). Zobaczysz, że >utrzymuje (rosnące) przesunięcie, >>zawsze dołączając do końca.

  • Jeśli plik zostanie obcięty do mniejszego rozmiaru (może być zerowy)
    • >nie obchodzi mnie to, napisze w pożądanym miejscu, jakby nic się nie stało; tuż po obcięciu przesunięcia poza koniec pliku spowoduje to, że plik odzyska swój stary rozmiar i powiększy się dalej, brakujące dane zostaną wypełnione zerami (jeśli to możliwe, w niewielkim stopniu);
    • >> dołącza się do nowego końca, plik wzrośnie ze swojego obciętego rozmiaru.
  • Jeśli powiększysz plik
    • >nie obchodzi mnie to, napisze w pożądanym miejscu, jakby nic się nie stało; zaraz po zmianie rozmiaru przesunięcie znajduje się gdzieś w pliku, spowoduje to, że plik przestanie rosnąć przez jakiś czas, dopóki przesunięcie nie osiągnie nowego końca, wtedy plik wzrośnie normalnie;
    • >> dołącza się do nowego końca, plik wzrośnie z powiększonego rozmiaru.

Innym przykładem jest dodanie (z osobnym >>) czegoś dodatkowego, gdy proces generowania danych jest uruchomiony i zapisuje się do pliku. Jest to podobne do powiększania pliku.

  • Proces generowania >zapisuje z pożądanym przesunięciem i ostatecznie nadpisuje dodatkowe dane.
  • Proces generowania >>pomija nowe dane i dołącza je obok (mogą wystąpić warunki wyścigu, dwa strumienie mogą się przeplatać, nadal nie należy nadpisywać żadnych danych).

Przykład

Czy to ma znaczenie w praktyce? Oto pytanie :

Pracuję nad procesem, który generuje dużo danych wyjściowych na standardowym wyjściu. Przesyłanie wszystkiego do pliku [...] Czy mogę użyć jakiegoś programu do obracania dzienników?

Ta odpowiedź mówi, że rozwiązanie jest logrotatez copytruncateopcją, która działa tak:

Obetnij oryginalny plik dziennika po utworzeniu kopii, zamiast przenosić stary plik dziennika i opcjonalnie utworzyć nowy.

Zgodnie z tym, co napisałem powyżej, przekierowanie za pomocą >spowoduje, że ścięty dziennik stanie się duży w krótkim czasie. Rzadkość pozwoli zaoszczędzić dzień, nie należy marnować znacznej ilości miejsca na dysku. Niemniej jednak każdy kolejny dziennik będzie zawierał coraz więcej zer wiodących, które są całkowicie niepotrzebne.

Ale jeśli logrotatetworzy kopie bez zachowania rzadkości, te wiodące zera będą wymagały coraz więcej miejsca na dysku za każdym razem, gdy kopia zostanie wykonana. Nie badałem zachowania narzędzia, może być wystarczająco inteligentne z rzadkością lub kompresją w locie (jeśli kompresja jest włączona). Mimo to zera mogą powodować problemy lub w najlepszym razie być neutralne; nic dobrego w nich.

W takim przypadku użycie >>zamiast zamiast >jest znacznie lepsze, nawet jeśli plik docelowy ma być jeszcze utworzony.


Występ

Jak widzimy, dwaj operatorzy zachowują się inaczej, nie tylko na początku, ale także później. Może to powodować pewne (subtelne?) Różnice w wydajności. Na razie nie mam znaczących wyników testów na poparcie lub obalenie go, ale myślę, że nie powinieneś automatycznie zakładać, że ich wydajność jest ogólnie taka sama.

Kamil Maciorowski
źródło
9
Tak >>jest w istocie „zawsze dążyć do końca pliku” podczas >utrzymuje wskaźnik do ostatniego pisemnego lokalizacji. Wygląda na to, że mogą występować pewne subtelne różnice w wydajności w sposobie ich działania ...
Mokubai
10
Na poziomie wywołania systemowego >>używa O_APPENDflagi doopen() . I faktycznie >używa O_TRUNC, podczas gdy >>nie. Kombinacja O_TRUNC | O_APPENDbyłaby również możliwa, język powłoki po prostu nie zapewnia tej funkcji.
ilkkachu
3
@jjmontes, standardowym źródłem będzie POSIX: pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/... ale oczywiście instrukcja Bash zawiera także opisy operatorów przekierowań, w tym niestandardowych, które obsługuje: gnu.org/ software / bash / manual / html_node / Redirections.html
ilkkachu
2
@ilkkachu Stwierdziłem, że jest to interesujące, ponieważ wyjaśnia szczegóły dotyczące O_APPEND, nad którymi zastanawiałem się po twoim komentarzu :): stackoverflow.com/questions/1154446/…
jjmontes
1
@Mokubai, każdy rozsądny system operacyjny miałby pod ręką długość pliku, gdy jest on otwarty, a sprawdzenie flagi i przesunięcie przesunięcia do końca powinno po prostu zniknąć we wszystkich innych księgach. Próba emulacji przed każdym O_APPENDz lseek()nich write()byłaby jednak inna, pojawiłby się dodatkowy koszt wywołania systemowego. (I oczywiście nie zadziałałoby, ponieważ może to write()
nastąpić