Linux: jak jednocześnie używać pliku jako wejścia i wyjścia?

55

Właśnie uruchomiłem następujące polecenie bash:

uniq .bash_history > .bash_history

a mój plik historii skończył się zupełnie pusty.

Chyba potrzebuję sposobu na odczytanie całego pliku przed jego zapisaniem. Jak to się robi?

PS: Oczywiście myślałem o użyciu pliku tymczasowego, ale szukam bardziej eleganckiego rozwiązania.

MilliaLover
źródło
To dlatego, że pliki są otwierane od prawej do lewej. Zobacz także stackoverflow.com/questions/146435/…
WheresAlice
Musisz zapisać dane wyjściowe w nowym pliku w tym samym katalogu i zmienić nazwę tego na stary plik. Każde inne podejście może spowodować utratę danych, jeśli zostanie przerwane w połowie. Niektóre narzędzia mogą ukryć przed tobą ten krok.
kasperd 10.04.16
Lub bashnie umieści kolejnych duplikatów w swojej historii, jeśli ustawisz HISTCONTROL, aby zawierał ignorowane kopie; zobacz stronę podręcznika.
dave_thompson_085

Odpowiedzi:

49

Polecam używanie spongez moreutils . Z strony podręcznika:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.

Aby zastosować to do swojego problemu, spróbuj:

uniq .bash_history | sponge .bash_history
jldugger
źródło
6
To jak kot, ale z możliwościami ssania: D
MilliaLover
77

Chciałem tylko przekazać kolejną odpowiedź, która jest prosta i nie używa gąbki (ponieważ często nie jest zawarta w lekkich środowiskach).

echo "$(uniq .bash_history)" > .bash_history

powinien mieć pożądany rezultat. Ta podpowłoka jest wykonywana, zanim plik .bash_history zostanie otwarty do zapisu. Jak wyjaśniono w odpowiedzi Phila P., do czasu odczytania .bash_history w oryginalnym poleceniu zostało już obcięte przez operatora „>”.

Hart Simha
źródło
15
Zwykle nie jestem fanem odpowiedzi, które pogłębiają odwieczne pytanie, które ma już prawidłową, zaakceptowaną odpowiedź - ale jest to eleganckie, dobrze napisane i stanowi silny argument za jego koniecznością (lekkie środowiska); dla mnie to naprawdę dodaje coś do istniejącego zestawu odpowiedzi. Witamy w SF, Hart (jesteś tu od miesiąca, ale myślę, że to twój pierwszy merytoryczny post). Mam nadzieję, że przeczytam więcej takich odpowiedzi od ciebie!
MadHatter,
4
To najlepsze rozwiązanie. Musiałem użyć podpowłoki $()zamiast backticków z powodu pewnych problemów związanych z ucieczką.
CMCDragonkai
3
Zastanawiam się, czy to rozwiązanie można skalować do dużych plików, powiedzmy ... 20 lub 50 GB.
Amit Naidu
1
To naprawdę powinna być akceptowana odpowiedź.
maxywb
1
Użyłem tej odpowiedzi, aby zrobić echo "$(fmt -p '# ' -w 50 readme.txt)" > readme.txtdzisiaj. Długo szukałem eleganckiego rozwiązania. Wielkie dzięki, @Hart Simha!
shredalert
12

Problem polega na tym, że powłoka konfiguruje potok poleceń przed uruchomieniem poleceń. Nie chodzi o „dane wejściowe i wyjściowe”, lecz o to, że zawartość pliku zniknęła, zanim uniq się uruchomi. To idzie mniej więcej tak:

  1. Powłoka otwiera >plik wyjściowy do zapisu, obcinając go
  2. Powłoka ustawia się tak, aby mieć deskryptor pliku 1 (dla standardowego wyjścia) dla tego wyjścia
  3. Powłoka wykonuje uniq, być może coś w rodzaju execlp („uniq”, „uniq”, „.bash_history”, NULL)
  4. uniq działa, otwiera .bash_history i niczego tam nie znajduje

Istnieją różne rozwiązania, w tym edycja na miejscu i tymczasowe użycie pliku, o których wspominają inni, ale kluczem jest zrozumienie problemu, co właściwie nie działa i dlaczego.

Phil P.
źródło
9

Kolejną sztuczką, aby to zrobić bez użycia sponge, jest następujące polecenie:

{ rm .bash_history && uniq > .bash_history; } < .bash_history

Jest to jeden z kodów opisanych w doskonałym artykule „Edycja na miejscu” plików na backreference.org.

Zasadniczo otwiera plik do odczytu, a następnie „usuwa” go. Jednak tak naprawdę nie został usunięty: wskazuje na niego otwarty deskryptor pliku i dopóki jest on otwarty, plik jest nadal dostępny. Następnie tworzy nowy plik o tej samej nazwie i zapisuje w nim unikalne linie.

Wada tego rozwiązania: jeśli uniqz jakiegoś powodu zawiedzie, Twoja historia zniknie.

scy
źródło
3

Ten sedskrypt usuwa sąsiadujące duplikaty. Z tą -iopcją dokonuje modyfikacji na miejscu. Pochodzi z sed infopliku:

sed -i 'h;:b;$b;N;/^\(.*\)\n\1$/ {g;bb};$b;P;D' .bash_history
Dennis Williamson
źródło
sed nadal używa pliku tymczasowego, dodał odpowiedź z straceilustracją (nie żeby to naprawdę miało znaczenie) :-)
Kyle Brandt
3
@Kyle: To prawda, ale „poza zasięgiem wzroku, poza zasięgiem umysłu”. Osobiście użyłbym jawnego pliku tymczasowego, ponieważ coś takiego process input > tmp && mv tmp inputjest o wiele prostsze i bardziej czytelne niż stosowanie sedsztuczek po prostu w celu uniknięcia pliku tymczasowego i nie zastąpi mojego oryginału, jeśli się nie powiedzie (nie wiem, czy sed -izawodzi z wdziękiem - zrobiłbym to myślę, że tak). Poza tym istnieje wiele rzeczy, które można zrobić za pomocą metody pliku wyjściowego do pliku tymczasowego, których nie można zrobić w miejscu bez czegoś bardziej zaangażowanego niż ten sedskrypt. Wiem, że wiesz o tym wszystkim, ale może to przynieść korzyści niektórym gapiom.
Dennis Williamson,
3

Jako ciekawą ciekawostkę sed używa również pliku tymczasowego (robi to za Ciebie):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

Opis:
plik tymczasowy zmienia ./sedPmPv9zsię na fd 4, a foopliki na fd 3. Operacje odczytu są wykonywane na fd 3, a zapis na fd 4 (plik tymczasowy). Plik foo jest następnie nadpisywany plikiem temp w wywołaniu zmiany nazwy.

Kyle Brandt
źródło
1

Inne rozwiązanie:

uniq file 1<> líneas.txt
Antonio Lebrón
źródło
0

Plik tymczasowy to w zasadzie, chyba że dane polecenie obsługuje edycję w miejscu ( uniqnie - niektóre seds do ( sed -i)).

Douglas Leeder
źródło
0

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

ex -sc '%!uniq' -cx .bash_history
  1. % wybierz wszystkie linie

  2. ! Uruchom polecenie

  3. x Zapisz i zamknij

Steven Penny
źródło
-1

Możesz również użyć tee, używając danych wyjściowych uniq jako danych wejściowych:

uniq .bash_history | tee .bash_history
Saul Vargas
źródło
Nie, nie możesz tego zrobić askubuntu.com/a/752451
Steven Penny