Odczytywanie i zapisywanie pliku: polecenie tee

10

Powszechnie wiadomo, że takie polecenie:

cat filename | some_sed_command >filename

usuwa nazwę pliku, ponieważ przekierowanie wyjścia, wykonywane przed poleceniem, powoduje obcięcie nazwy pliku.

Problem można rozwiązać w następujący sposób:

cat file | some_sed_command | tee file >/dev/null

ale nie jestem pewien, czy i tak to zadziała: co się stanie, jeśli plik (i wynik polecenia sed) jest bardzo duży? Jak system operacyjny może uniknąć zastąpienia niektórych treści, które wciąż nie są czytane? Widzę, że istnieje również polecenie gąbki, które powinno działać w każdym przypadku: czy jest „bezpieczniejsze” niż tee?

VeryHardCoder
źródło
Jaki jest twój główny cel? (w prostych słowach)
Sergiy Kolodyazhnyy
@Serg po prostu zrozum, jak działają rzeczy ... Odpowiedź napisana przez kos wyjaśnia sprawę
VeryHardCoder

Odpowiedzi:

10

Problem można rozwiązać w następujący sposób:

cat file | some_sed_command | tee file >/dev/null

Nie .

Szanse filezostaną obcięte, ale nie ma gwarancji, cat file | some_sed_command | tee file >/dev/nullże nie zostaną obcięte file.

Wszystko zależy od tego, które polecenie jest przetwarzane jako pierwsze, w przeciwieństwie do tego, czego można się spodziewać, polecenia w potoku nie są przetwarzane od lewej do prawej . Nie ma gwarancji, które polecenie zostanie wybrane jako pierwsze, więc równie dobrze można pomyśleć o tym, że zostało ono wybrane losowo i nigdy nie polegać na tym, że pocisk nie wybierze obrażającego.

Ponieważ szanse na wybranie obrażającego polecenia jako pierwszego spośród trzech poleceń są mniejsze niż szanse na wybranie obrażającego polecenia jako pierwszego pomiędzy dwoma poleceniami, jest mniej prawdopodobne, że filezostanie ono obcięte, ale nadal tak się stanie .

script.sh:

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

Więc nigdy nie używaj czegoś takiego cat file | some_sed_command | tee file >/dev/null. Użyj spongezgodnie z sugestią Oli.

Alternatywnie, w środowiskach o większym rozmiarze i / lub stosunkowo małych plikach można użyć łańcucha tutaj i podstawienia polecenia, aby odczytać plik przed uruchomieniem dowolnego polecenia:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz
kos
źródło
9

W sedszczególności możesz użyć -iargumentu na miejscu. Po prostu zapisuje z powrotem do pliku, który otworzył, np .:

sed -i 's/ /-/g' filename

Jeśli chcesz zrobić coś mocniejszego, zakładając, że robisz więcej niż sed, tak, możesz buforować całość za pomocą sponge(z moreutilspakietu), który „wchłonie” wszystkie standardowe wejścia przed zapisaniem do pliku. To tak, teeale z mniejszą funkcjonalnością. Jednak w przypadku podstawowego zastosowania jest to raczej wymiana zastępcza:

cat file | some_sed_command | sponge file >/dev/null

Czy to jest bezpieczniejsze? Zdecydowanie. Prawdopodobnie ma ograniczenia, więc jeśli robisz coś kolosalnego (i nie możesz edytować w miejscu za pomocą sed), możesz chcieć dokonać edycji drugiego pliku, a następnie mvtego pliku z powrotem do oryginalnej nazwy pliku. To powinno być atomowe (więc wszystko, co zależy od tych plików, nie ulegnie awarii, jeśli będą potrzebować stałego dostępu).

Oli
źródło
0

Możesz używać Vima w trybie Ex:

ex -sc '%!some_sed_command' -cx filename
  1. % wybierz wszystkie linie

  2. ! Uruchom polecenie

  3. x Zapisz i wyjdź

Steven Penny
źródło
0

Och, ale spongeto nie jedyna opcja; nie musisz tego robić moreutils, aby to działało poprawnie. Każdy mechanizm będzie działał, o ile spełnia następujące dwa wymagania:

  1. Przyjmuje nazwę pliku wyjściowego jako parametr.
  2. Tworzy plik wyjściowy dopiero po przetworzeniu wszystkich danych wejściowych.

Widzisz, dobrze znanym problemem, do którego odnosi się OP, jest to, że powłoka utworzy wszystkie pliki, które są niezbędne do działania potoków, zanim zaczną nawet wykonywać polecenia w potoku, więc to powłoka faktycznie obcina plik wyjściowy (który niestety jest również plikiem wejściowym), zanim którekolwiek z poleceń zdążyło się uruchomić.

teeKomenda nie działa, mimo że spełnia pierwszy warunek, ponieważ nie spełnia drugi warunek: będzie zawsze natychmiast utworzyć plik wyjściowy przy starcie, więc jest to w istocie tak źle, jak tworząc rurę prosto do pliku wyjściowego. (Jest tak naprawdę gorzej, ponieważ jego użycie wprowadza niedeterministyczne losowe opóźnienie przed obcięciem pliku wyjściowego, więc możesz pomyśleć, że działa, podczas gdy w rzeczywistości nie działa).

Aby rozwiązać ten problem, potrzebujemy tylko polecenia, które zbuforuje wszystkie dane wejściowe przed wygenerowaniem danych wyjściowych i które jest w stanie zaakceptować nazwę pliku wyjściowego jako parametr, dzięki czemu nie musimy przesyłać danych wyjściowych do plik wyjściowy. Jednym z takich poleceń jest shuf. Tak więc następujące rzeczy osiągną to samo, spongeco:

    shuf --output=file --random-source=/dev/zero 

W --random-source=/dev/zeroczęści sztuczki shufjęzyk robi jego rzecz bez jakiegokolwiek szuranie w ogóle, więc będzie buforować swój wkład bez zmieniania go.

Mike Nakis
źródło