Przekierowanie IO i polecenie główne

9

Próbowałem .hgignoredzisiaj szybko edytować plik z powłoki bash Cygwin i dodałem wiersz, który był błędem. Nie jestem pewien, czy to był najlepszy sposób, aby to zrobić, ale szybko pomyślałem o head -1 .hgignoreusunięciu linii obrażającej (wcześniej miałem tylko jedną linię w pliku). Rzeczywiście, po wykonaniu daje pierwszy wiersz jako jedyne wyjście.

Ale kiedy próbowałem przekierować dane wyjściowe i przepisać plik za pomocą head -1 .hgignore > .hgignore, plik był pusty. Dlaczego to się dzieje? Jeśli spróbuję zamiast head -1 .hgignore >> .hgignoretego dołączyć, to dodaje się poprawnie, ale to oczywiście nie jest pożądany wynik. Dlaczego przekierowanie obcinające nie działa w tym przypadku?

voithos
źródło

Odpowiedzi:

10

Gdy powłoka otrzyma wiersz poleceń, taki jak: command > file.outsama powłoka otwiera (i może tworzy) plik o nazwie file.out. Powłoka ustawia deskryptor pliku 0 na deskryptor pliku otrzymany z otwartej przestrzeni. Tak działa przekierowanie We / Wy: każdy proces wie o deskryptorach plików 0, 1 i 2.

Trudność polega na tym, jak otworzyć file.out. file.outPrzez większość czasu chcesz otwierać do zapisu z przesunięciem 0 (tzn. Obcięte) i właśnie to zrobiła dla ciebie powłoka. Skrócił plik .hgignore, otworzył go do zapisu, skopiował deskryptor plików do 0, a następnie wykonał head. Natychmiastowe blokowanie plików.

W powłoce bash możesz set noclobberzmienić to zachowanie.

Bruce Ediger
źródło
Aha widzę. Myślałem, że powłoka obcina plik przed uruchomieniem polecenia, ale nie wiedziałam dlaczego. Dziękuję za wyjaśnienie!
voithos
10

Myślę, że Bruce odpowiada na to, co się tu dzieje z rurociągiem pocisków.

Jednym z moich ulubionych małych narzędzi jest spongepolecenie moreutils . Rozwiązuje dokładnie ten problem, „wchłaniając” wszystkie dostępne dane wejściowe przed otwarciem docelowego pliku wyjściowego i zapisaniem danych. Umożliwia pisanie potoków dokładnie tak, jak się spodziewałeś:

$ head -1 .hgignore | sponge .hgignore

Rozwiązaniem biednego człowieka jest potokowanie wyjścia do pliku tymczasowego, a następnie po zakończeniu potoku (na przykład następnego uruchomionego polecenia) przeniesienie pliku tymczasowego z powrotem do pierwotnej lokalizacji pliku.

$ head -1 .hgingore > .hgignore.tmp
$ mv .hgignore{.tmp,}
Caleb
źródło
Patrząc na to kilka lat później, przyszła mi do głowy myśl: czy nie możemy tego zrobić head -1 .hgignore | tee .hgignore? teejest w coreutils, i jako efekt / efekt uboczny, to również pisze STDOUT
voithos
@voithos O ​​ile mi wiadomo, teeotwiera i obcina plik, do którego zapisuje, gdy jest tworzony, podobnie jak wszystko inne, więc nie rozwiązuje on głównego problemu warunków wyścigu podczas czytania zawartości pliku przed obcięciem go przy zapisie.
Caleb
Poruszasz kwestię, o której właściwie nie wiedziałem - mianowicie, że polecenia potokowe są uruchamiane natychmiast, a nie sekwencyjnie. Czy to jest dokładne? Ja jednak to przetestowałem i tee wydaje się, że robię to, czego pragnę . Mam wersję 8.13na moim komputerze.
voithos
1
@voithos Tak polecenia w potoku, a wszystkie zaangażowane kanały wejściowe / wyjściowe są uruchamiane w odwrotnej kolejności, więc potok jest gotowy do odbierania danych, gdy pierwszy zacznie je podawać. Podejrzewam, że twój test jest wadliwy, ponieważ prawdopodobnie użyłeś zbyt małej części danych, a wszystko to zostało buforowane w buforze odczytu, zanim go potrzebujesz. teeProgram będzie obciąć swoje pliki, nie jest ustawiony tak, aby podwoić bufor nich.
Caleb
3

W

head -n 1 file > file

filejest obcinany przed headuruchomieniem, ale jeśli go napiszesz:

head -n 1 file 1<> file

nie jest tak, jak filejest otwarty w trybie do odczytu i zapisu. Jednak po headzakończeniu pisania plik nie jest obcinany, więc powyższa linia nie headbyłaby dostępna ( po prostu przepisałaby pierwszą linię nad sobą i pozostawiła pozostałe bez zmian).

Jednak po headpowrocie i gdy fdnadal jest otwarty, możesz wywołać inne polecenie, które wykonuje truncate.

Na przykład:

{ head -n 1 file; perl -e 'truncate STDOUT, tell STDOUT'; } 1<> file

Ważne jest to, że truncatepowyżej, headpo prostu przesuwa kursor dla fd 1 wewnątrz pliku tuż po pierwszym wierszu. Przepisuje pierwszą linię, której nie potrzebowaliśmy, ale to nie jest szkodliwe.

Dzięki głowicy POSIX moglibyśmy uciec bez przepisywania pierwszej linii:

{ head -n 1 > /dev/null
  perl -e 'truncate STDIN, tell STDIN'
} <> file

W tym przypadku wykorzystujemy fakt, że headprzesuwa pozycję kursora w jego standardowe wejście. Podczas gdy headzwykle czytałby swoje dane wejściowe dużymi fragmentami, aby poprawić wydajność, POSIX wymagałby (tam, gdzie to możliwe) seekcofnięcia się zaraz po pierwszym wierszu, jeśli przekroczyłby go. Należy jednak pamiętać, że nie wszystkie implementacje to robią.

Alternatywnie możesz readzamiast tego użyć polecenia powłoki :

{ read -r dummy; perl -e 'truncate STDIN, tell STDIN'; } <> file
Stéphane Chazelas
źródło
1
Stephane, czy znasz standardowe lub Coreutils polecenie, które może obciąć STDINpodobnie do tego, co osiągnąłeś perlpowyżej
iruvar
2
@ 1_CR, no. ddmoże jednak obciąć dowolny dowolny bezwzględny offset w pliku. Możesz więc ustalić przesunięcie bajtu drugiej linii i stamtąd stamtąd za pomocądd bs=1 seek="$offset" of=file
Stéphane Chazelas
1

Rozwiązaniem Prawdziwego Człowieka jest

ed .hgignore
$d
wq

lub jako jedna linijka

printf '%s\n' '$d' 'wq' | ed .hgignore

Lub z GNU sed:

sed -i '$d' .hgignore

(Nie, żartuję. Użyłbym interaktywnego edytora. vi .hgignore GddZZ)

Gilles „SO- przestań być zły”
źródło
Zastanawiałem się, czy istnieje zaletą korzystania :wqnad ZZ?
voithos
Także to, :xco robią moje palce automatycznie
glenn jackman
i ZQjest taki sam jak:q!
glenn jackman
ZZ i: x pisz tylko, jeśli jest coś do zapisania ...: w zawsze synchronizuje plik na dysk, niezależnie od tego, czy go potrzebuje. Używam: xa, ponieważ używam tabulatorów.
Xenoterracide
1

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

ex -sc '2,d|x' .hgignore
  1. 2, zaznacz linie 2 do końca

  2. d usunąć

  3. x Zapisz i zamknij

Steven Penny
źródło
0

Do edycji plików w miejscu możesz także użyć sztuczki z otwartym uchwytem pliku, jak Jürgen Hötzel w danych wyjściowych Przekieruj z sed 's / c / d /' myFile do myFile .

exec 3<.hgignore
rm .hgignore  # prevent open file from being truncated
head -1 <&3 > .hgignore

ls -l .hgignore  # note that permissions may have changed
dan55
źródło
2
I zaraz po tym, jak rm .hgignoreTwoja moc zawiedzie, zabierając godziny ciężkiej pracy. Ok, to nie ma znaczenia .hgignore, ale dlaczego miałbyś zrobić coś tak skomplikowanego? Tak więc moja opinia negatywna: technicznie poprawna, ale bardzo zły pomysł.
Gilles „SO- przestań być zły”
@Gilles, może nie jest to dobry pomysł, ale takie jest na przykład perl -i(w przypadku edycji w miejscu) i nie byłbym zaskoczony, gdyby niektóre implementacje sed -irównież to zrobiły (choć sedwydaje się, że nie jest to najnowsza wersja GNU ).
Stéphane Chazelas,