Używanie głowy i ogona do chwytania różnych zestawów linii i zapisywanie do tego samego pliku

10

To dotyczy pracy domowej, ale nie będę zadawać konkretnego pytania dotyczącego pracy domowej.

Muszę użyć głowy i ogona, aby pobrać różne zestawy linii z jednego pliku. Podobnie jak linie 6-11 i linie 19-24 i zapisz je oba do innego pliku. Wiem, że mogę to zrobić za pomocą append, takiego jak

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

Ale nie sądzę, że powinniśmy.
Czy istnieje konkretny sposób na połączenie poleceń głowy i ogona, a następnie zapisanie do pliku?

użytkownik2709291
źródło
1
Czy oni konkretnie proszą cię o użycie headi tail? Jeśli tak, Twoje rozwiązanie jest prawie najlepsze, co możesz zrobić. Jeśli możesz korzystać z innych programów sedlub możesz awkpozwolić na lepsze rozwiązania (tj. Z mniejszą liczbą wywołań procesów).
n.
Tak, proszą nas o użycie głowy i ogona. Dziękuję za Twoją odpowiedź.
user2709291
Jeszcze jedno mogę dodać: Można ominąć dołączając wyjściowy przekierowanie ( >>) załączając dwa polecenia w nawiasach, aby przekierować ich wyjście łączone: (head -11 file | tail -6; head -24 file | tail -6) > file1. To naprawdę sprowadza się do osobistych preferencji, co jest przyjemniejsze.
n.
Dziękuję, że wszystko się ułoży. Bardzo to doceniam.
user2709291

Odpowiedzi:

11

Możesz to zrobić za pomocą headpojedynczej i podstawowej arytmetyki, jeśli zgrupujesz polecenia za { ... ; }pomocą konstrukcji podobnej do

{ head -n ...; head -n ...; ...; } < input_file > output_file

gdzie wszystkie polecenia mają takie same dane wejściowe (dzięki @mikeserv ).
Uzyskiwanie linii 6-11 i linii 19-24 jest równoważne z:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

Zasadniczo uruchomiłbyś:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file
don_crissti
źródło
Nie działa mi. Wkład jest spożywany przez pierwszą głowę
kapryśny
6

Za pomocą { … }konstrukcji grupującej można zastosować operator przekierowania do polecenia złożonego.

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

Zamiast powielać pierwsze linie M + N i zachowywać tylko ostatnie N, możesz pominąć pierwsze M linii i powielić następne N. Jest to zauważalnie szybsze w przypadku dużych plików . Uważaj, że +Nargumentem tailnie jest liczba linii do pominięcia, ale jeden plus, że - to liczba pierwszej linii do wydrukowania z liniami o numerze od 1.

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

Tak czy inaczej, plik wyjściowy jest otwierany tylko raz, ale plik wejściowy jest przesuwany jeden raz dla każdego fragmentu do wyodrębnienia. Co powiesz na grupowanie danych wejściowych?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

Ogólnie to nie działa. (Może działać na niektórych systemach, przynajmniej gdy dane wejściowe są zwykłymi plikami.) Dlaczego? Z powodu buforowania danych wejściowych . Większość programów, w tym tailtakże nie odczytuje bajtów bajt po bajcie, ale kilka kilobajtów na raz, ponieważ jest to szybsze. tailCzyta więc kilka kilobajtów, przeskakuje nieco na początku, przekazuje nieco więcej headi zatrzymuje - ale to, co jest czytane, jest czytane i nie jest dostępne dla następnego polecenia.

Innym podejściem jest użycie headpipowania /dev/nulldo omijania linii.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

Ponownie nie gwarantuje się, że zadziała z powodu buforowania. Zdarza się, że działa z headpoleceniem z GNU coreutils (tym, które można znaleźć w niewbudowanych systemach Linux), gdy dane wejściowe pochodzą ze zwykłego pliku. Dzieje się tak, ponieważ gdy ta implementacja headprzeczytała, co chce, ustawia pozycję pliku na pierwszy bajt, którego nie wyprowadził. To nie działa, jeśli wejście jest potokiem.

Prostszym sposobem wydrukowania kilku sekwencji wierszy z pliku jest wywołanie bardziej ogólnego narzędzia, takiego jak sed lub awk . (Może to być wolniejsze, ale ma to znaczenie tylko w przypadku bardzo dużych plików).

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1
Gilles „SO- przestań być zły”
źródło
2
To nie działa, jest to standardowe, określone zachowanie - choć z pewnością, jak mówisz, potok nie jest niezawodnym źródłem wejściowym dla współdzielonego wejścia. DOMYŚLNE OPIS UŻYTKOWNIKA : Gdy standardowe narzędzie odczytuje widoczny plik wejściowy i kończy się bez błędu, zanim osiągnie koniec pliku, narzędzie upewni się, że przesunięcie pliku w otwartym opisie pliku jest właściwie ustawione tuż za ostatnim bajtem przetworzonym przez narzędzie.
mikeserv
2

Wiem, że powiedziałeś, że musisz używać głowy i ogona, ale sed jest zdecydowanie prostszym narzędziem do tego zadania.

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

Możesz nawet budować bloki w ciągu z innym procesem i przeprowadzać je przez sed.

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n neguje dane wyjściowe, a następnie określasz zakresy do wydrukowania za pomocą p, przy czym pierwszą i ostatnią liczbę zakresu oddzielamy przecinkiem.

To powiedziawszy, możesz albo grupować polecenia, które sugerował @don_crissti, albo kilkakrotnie zapętlać plik z głową / ogonem, chwytając kawałek linii za każdym razem, gdy przejdziesz.

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

Im więcej wierszy w pliku i im więcej bloków masz, tym bardziej wydajny będzie sed.

Falsenames
źródło
2

Z sedtobą możesz zrobić:

sed '24q;1,5d;12,18d' <infile >outfile

... Być może można by uzyskać bardziej wydajne rozwiązanie head. Don już pokazał, jak to może działać bardzo dobrze, ale bawiłem się również tym. Coś, co możesz zrobić, aby rozwiązać ten konkretny przypadek:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

... który wywołałby head4 razy pisząc na outfilelub w /dev/nullzależności od tego, czy wartość tej iteracji $njest liczbą parzystą czy nieparzystą.

W bardziej ogólnych przypadkach, zebrałem to razem z kilku innych rzeczy, które już miałem:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

Może to zrobić coś takiego:

 seq 100 | somehead -1 -5 6 -7 6

... które drukuje ...

6
7
8
9
10
11
19
20
21
22
23
24

Oczekuje, że jego pierwszy argument będzie liczbą powtórzeń poprzedzoną znakiem -, lub, w przypadku braku takiej liczby , tylko -. Jeśli podano liczbę, powtórzy wzór linii podany w poniższych argumentach tyle razy, ile określono, i zatrzyma się, gdy tylko to zrobi.

Dla każdego następującego argumentu zinterpretuje ujemną liczbę całkowitą wskazującą liczbę wierszy, w której należy zapisać, /dev/nulloraz dodatnią liczbę całkowitą wskazującą liczbę wierszy, w której należy zapisać stdout.

Tak więc w powyższym przykładzie wypisuje pierwsze 5 wierszy do /dev/null, następne 6 do stdout, następne 7 do /dev/nullponownie i kolejne 6 ponownie do stdout. Po osiągnięciu ostatniego z argumentów i pełnym przejściu -1liczby powtórzeń, następnie kończy pracę. Gdyby był pierwszy argument, -2powtórzyłby ten proces jeszcze raz lub -tak długo, jak to możliwe.

Dla każdego cyklu argowania whilepętla jest przetwarzana raz. Na górze każdej pętli pierwszy wiersz z stdinjest wczytywany do zmiennej powłoki $l. Jest to konieczne, ponieważ while head </dev/null; do :; donebędzie się powtarzało w nieskończoność - headwskazuje w swoim zwrocie, kiedy osiągnie koniec pliku. Zatem kontrola EOF jest dedykowana readi printfzapisze $lplus nowy wiersz stdouttylko wtedy, gdy drugi argument jest dodatnią liczbą całkowitą.

readCheck komplikuje pętli trochę, bo zaraz po drugiej pętli nazywa - forpętla która iteruje argumenty 2-$#reprezentowane w $ndla każdej iteracji jego macierzystej whilepętli. Oznacza to, że dla każdej iteracji pierwszy argument musi być pomniejszony o jeden z wartości określonej w wierszu poleceń, ale wszystkie pozostałe powinny zachować swoje oryginalne wartości, więc wartość zmiennej $_nvar jest odejmowana od każdej, ale zawsze zawiera wartość wartość większa niż 0 dla pierwszego argumentu.

To stanowi główną pętlę funkcji, ale większość kodu znajduje się na górze i ma na celu umożliwić tej funkcji czyste buforowanie nawet potoku jako danych wejściowych. Działa to najpierw poprzez wywołanie tła w ddcelu skopiowania go do pliku tmp na wyjściu przy rozmiarach bloków 4k na kawałek. Następnie funkcja ustanawia pętlę wstrzymania - która prawie nigdy nie powinna ukończyć nawet jednego pełnego cyklu - tylko po to, aby upewnić się, że dddokonała przynajmniej jednego zapisu do pliku, zanim funkcja zastąpi swoje standardowe wejście deskryptorem pliku powiązanym z plikiem tmp i potem natychmiast rozłącza plik za pomocąrm. Dzięki temu funkcja niezawodnie przetwarza strumień, nie wymagając pułapek ani w inny sposób do oczyszczenia - jak tylko funkcja zwolni swoje roszczenie na fd, plik tmp przestanie istnieć, ponieważ jego jedyne nazwane łącze do systemu plików zostało już usunięte.

mikeserv
źródło
0

Użyj funkcji bash w następujący sposób:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

W tym przypadku jest to trochę przesada, ale jeśli twoje filtry powiększą się, może stać się dobrodziejstwem.

mkalkov
źródło