Poprzednią ostatnią linię stdin do całego stdin

9

Rozważ ten skrypt:

tmpfile=$(mktemp)

cat <<EOS > "$tmpfile"
line 1
line 2
line 3
EOS

cat <(tail -1 "$tmpfile") "$tmpfile"

To działa i daje:

line 3
line 1
line 2
line 3

Powiedzmy, że nasze źródło danych wejściowych, zamiast być faktycznym plikiem, było zamiast tego standardowe:

cat <<EOS | # what goes here now?
line 1
line 2
line 3
EOS

Jak zmodyfikujemy polecenie:

cat <(tail -1 "$tmpfile") "$tmpfile"

Czy nadal wytwarza ten sam wynik w tym innym kontekście?

UWAGA: Konkretny Heredoc, którego łowię, a także użycie samego Heredoc, jest jedynie ilustracyjne. Każda akceptowalna odpowiedź powinna zakładać, że odbiera ona dowolne dane za pośrednictwem standardowego wejścia .

Jonasz
źródło
1
stdin jest zawsze „rzeczywistym plikiem” (fifo / socket / etc to także plik; nie wszystkie pliki można zobaczyć). Odpowiedź na twoje pytanie to albo trywialne „użyj pliku tymczasowego”, albo jakiś horror, który załaduje cały plik do pamięci. „Jak mogę odzyskać stare dane ze strumienia bez zapisywania go w dowolnym miejscu ?” nie mogę mieć dobrej odpowiedzi.
mosvy
1
@mosvy Jest to całkowicie akceptowalna odpowiedź, jeśli chcesz ją dodać.
Jonasz
2
@mosvy Jak powiedział Jonah, odpowiedzi należy opublikować w polu odpowiedzi. Wiem, że w tej chwili trudno jest przeczytać dowolną stronę, ale zignoruj ​​czerwony, który powoli ocieka Twoją wizją i użyj dolnej strefy tekstowej.
wizzwizz4

Odpowiedzi:

7

Próbować:

awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'

Przykład

Zdefiniuj zmienną za pomocą naszych danych wejściowych:

$ input="line 1
> line 2
> line 3"

Uruchom nasze polecenie:

$ echo "$input" | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 3
line 1
line 2
line 3

Ewentualnie moglibyśmy użyć tutaj dokumentu:

$ cat <<EOS | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 1
line 2
line 3
EOS
line 3
line 1
line 2
line 3

Jak to działa

  • x=x $0 ORS

    To dołącza każdy wiersz danych wejściowych do zmiennej x.

    W awk ORSjest separatorem rekordów wyjściowych . Domyślnie jest to znak nowej linii.

  • END{printf "%s", $0 ORS x}

    Po czytaliśmy w całym pliku, Drukuje ostatnim wierszu $0, po czym zawartość całego pliku x.

Ponieważ odczytuje to całe wejście do pamięci, nie byłoby właściwe dla dużych ( np. Gigabajtów) wejść.

John1024
źródło
Dzięki, John. Czy nie można tego zrobić w sposób analogiczny do mojego nazwanego przykładu pliku w OP? Wyobrażałem sobie, że stdin jest w jakiś sposób duplikowane ... w pewien sposób tee, ale jeśli stdin i plik, będziemy przesyłać ten sam stdin do dwóch różnych podstawień procesu. czy coś, co byłoby mniej więcej równoważne z tym?
Jonasz
5

Jeśli stdin wskazuje na możliwy do przeglądania plik (jak w przypadku dokumentów bash (ale nie wszystkich innych powłok) tutaj, które są zaimplementowane z plikami tymczasowymi, możesz pobrać ogon, a następnie wyszukać ponownie przed odczytaniem pełnej zawartości:

Operatory wyszukiwania są dostępne w powłokach zshlub ksh93, lub w językach skryptowych takich jak tcl / perl / python, ale nie w bash. Ale zawsze możesz zadzwonić do bardziej zaawansowanych tłumaczy, bashjeśli musisz z nich skorzystać bash.

ksh93 -c 'tail -n1; cat <#((0))' <<...

Lub

zsh -c 'zmodload zsh/system; tail -n1; sysseek 0; cat' <<...

To nie zadziała, gdy stdin wskazuje na niewidoczne pliki, takie jak potok lub gniazdo. Następnie jedyną opcją jest odczyt i zapisanie (w pamięci lub w pliku tymczasowym ...) całego wejścia.

Podano już pewne rozwiązania dotyczące przechowywania w pamięci.

Za pomocą pliku tymczasowego zshmożesz:

seq 10 | zsh -c '{ cat =(sed \$w/dev/fd/3); } 3>&1'

Jeśli w systemie Linux, z bashlub zshlub jakiejkolwiek powłoce że pliki zastosowania temp bo tu-dokumentów, można rzeczywiście wykorzystać plik tymczasowy utworzony przez tu-dokumentu, aby zapisać dane wyjściowe:

seq 10 | {
  chmod u+w /dev/fd/3 # only needed in bash5+
  cat > /dev/fd/3
  tail -n1 /dev/fd/3
  cat <&3
} 3<<EOF
EOF
Stéphane Chazelas
źródło
4
cat <<EOS | sed -ne '1{h;d;}' -e 'H;${G;p;}'
line 1
line 2
line 3
EOS

Problem z przetłumaczeniem tego na coś, co używa, tailpolega na tym, że tailtrzeba przeczytać cały plik, aby znaleźć jego koniec. Aby użyć tego w potoku, musisz

  1. Podaj pełną treść dokumentu do tail.
  2. Dostarczenie go ponownie do cat.
  3. W tej kolejności.

Problem polega na tym, aby nie powielać zawartości dokumentu ( teerobi to), ale uzyskać wynik, tailktóry ma się wydarzyć przed wydrukowaniem pozostałej części dokumentu, bez użycia pośredniego pliku tymczasowego.

Używanie sed(lub awk, jak John1024 ) eliminuje podwójne analizowanie danych i problem z porządkowaniem poprzez przechowywanie danych w pamięci.

sedRozwiązania, które jest zaproponowanie

  1. 1{h;d;}, zapisz pierwszy wiersz w niezatrzymanym miejscu i przejdź do następnego.
  2. H, dodajcie sobie nawzajem linię do miejsca wstrzymania za pomocą osadzonej nowej linii.
  3. ${G;p;}, dodaj przestrzeń wstrzymania do ostatniego wiersza z osadzoną nową linią i wydrukuj uzyskane dane.

Jest to dosłowne tłumaczenie rozwiązania John1024 na sed, z zastrzeżeniem, że standard POSIX gwarantuje tylko, że przestrzeń wstrzymania wynosi co najmniej 8192 bajtów (8 KiB; ale zaleca, aby bufor ten był dynamicznie przydzielany i rozszerzany w razie potrzeby, które to oba GNU sedi BSD sedrobi).


Jeśli pozwolisz sobie na użycie nazwanego potoku:

mkfifo mypipe
cat <<EOS | tee mypipe | cat <( tail -n 1 mypipe ) -
line 1
line 2
line 3
EOS
rm -f mypipe

Służy teedo wysyłania danych w dół mypipei jednocześnie do cat. catNarzędzie najpierw odczytać dane wyjściowe tail(który odczytuje z mypipe, co teepisze się), a następnie dołączyć kopię dokumentu pochodzącego bezpośrednio z tee.

Jest w tym jednak poważna wada, polegająca na tym, że jeśli dokument jest zbyt duży (większy niż rozmiar bufora potoku), teezapisuje mypipei catblokuje się podczas oczekiwania na opróżnienie (nienazwanego) potoku. Nie zostanie opróżniony, dopóki się catz niego nie przeczyta. catnie czytałby z niego, dopóki się tailnie skończy. I tailnie skończy, dopóki się teenie skończy. Jest to klasyczny impas.

Wariacja

tee >( tail -n 1 >mypipe ) | cat mypipe -

ma ten sam problem.

Kusalananda
źródło
2
Ten sednie działa, jeśli wejście ma tylko jedną linię (być może sed '1h;1!H;$!d;G'). Należy również pamiętać, że kilka sedimplementacji ma niski limit rozmiaru wzoru i miejsca do przechowywania.
Stéphane Chazelas
Nazwane rozwiązanie rurowe jest tym, czego szukałem. Ograniczenie to wstyd. Zrozumiałem twoje wyjaśnienie z wyjątkiem „I ogon nie skończyłby się, dopóki nie skończy się tee” - czy mógłbyś wyjaśnić, dlaczego tak jest?
Jonasz
2

W peekolekcji narzędzi wiersza polecenia znajduje się narzędzie o nazwie „moreutils” (lub w inny sposób dostępne na stronie głównej ).

Jeśli możesz mieć go w swoim systemie, odpowiednik dla twojego przykładu będzie wyglądał następująco:

cat <<EOS | pee 'tail -1' cat 
line 1
line 2
line 3
EOS

Kolejność uruchamianych poleceń peejest ważna, ponieważ są one wykonywane w podanej kolejności.

LL3
źródło
1

Próbować:

cat <<EOS # | what goes here now? Nothing!
line 3
line 1
line 2
line 3
EOS

Ponieważ cała rzecz to dosłowne dane („tutaj jest dokument”), a różnica między nim a pożądanym wynikiem jest banalna, po prostu masuj te dosłowne dane, aby dopasować wynik.

Załóżmy teraz, że line 3pochodzi skądś i jest przechowywany w zmiennej o nazwie lastline:

cat <<EOS # | what goes here now? Nothing!
$lastline
line 1
line 2
$lastline
EOS

W niniejszym dokumencie możemy wygenerować tekst, zastępując zmienne. Nie tylko to, ale możemy obliczyć tekst za pomocą podstawiania poleceń:

cat <<EOS
this is template text
here we have a hex conversion: $(printf "%x" 42)
EOS

Możemy interpolować wiele linii:

cat <<EOS
multi line
preamble
$(for x in 3 1 2 3; do echo line $x ; done)
epilog
EOS

Ogólnie rzecz biorąc, unikaj przetwarzania tekstu tutaj szablon doc; spróbuj wygenerować go za pomocą interpolowanego kodu.

Kaz
źródło
1
Szczerze mówiąc, nie wiem, czy to żart, czy nie. W cat <<EOS...PO był tylko przykładem „catting a arbitrary file”, aby post był konkretny, a pytanie jasne. Czy to naprawdę nie było dla ciebie oczywiste, czy po prostu pomyślałeś, że sprytnie byłoby interpretować pytanie dosłownie?
Jonasz
@Jonah Pytanie wyraźnie mówi „[l] et mówią, że nasze źródło danych wejściowych, zamiast być faktycznym plikiem, było zamiast tego standardowe:”. Nic o „dowolnych plikach”; tu chodzi o dokumenty. Dokument tutaj nie jest arbitralny. To nie jest wkład do twojego programu, ale fragment jego składni, którą wybiera programista.
Kaz
1
Myślę, że kontekst i istniejące odpowiedzi wyjaśniły, że tak było, choćby dlatego, że dla poprawnej interpretacji dosłownie musiałeś założyć, że ani ja, ani żaden z pozostałych plakatów, którzy odpowiedzieli, zdali sobie sprawę, że można skopiować i wkleić wiersz kodu. Niemniej jednak zmienię pytanie, aby było wyraźne.
Jonasz
1
Kaz, dziękuję za odpowiedź, ale pamiętaj, że nawet przy edycji nie masz zamiaru zadać pytania. Otrzymujesz dowolne wejście wielowierszowe za pośrednictwem potoku . Nie masz pojęcia, co to będzie. Twoim zadaniem jest wyprowadzenie ostatniego wiersza wprowadzania, a następnie całego wpisu.
Jonasz
1
Kaz, dane wejściowe podano tylko jako przykład. Większość ludzi, w tym ja, uważa za przydatne posiadanie przykładu rzeczywistego wkładu i oczekiwanych rezultatów, a nie tylko abstrakcyjne pytanie. Jesteś jedynym, który był tym zmieszany.
Jonasz
0

Jeśli nie zależy ci na zamówieniu. To zadziała cat lines | tee >(tail -1). Jak powiedzieli inni. Musisz odczytać plik dwa razy lub buforować cały plik, aby zrobić to w żądanej kolejności.

ctrl-alt-delor
źródło