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.


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

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 „>”.


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.


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.



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

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.



0

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


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


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.