Home Dokumentacje Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Wypisywanie wyjścia
22 | 10 | 2019
Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Wypisywanie wyjścia Drukuj

Przejdź do pierwszej, poprzedniej, następnej, ostatniej sekcji, spisu treści.

 


 

6. Wypisywanie wyjścia

Jedną z najczęstszych akcji jest wypisanie, wydruk (print) części lub całości danych wejściowych. Do zrobienia prostego wydruku (wyjścia) używamy instrukcji print. Instrukcja printf daje możliwość bardziej wyszukanego formatowania. Obie opisano w tym rozdziale.

6.1. Instrukcja print

Instrukcja print wykonuje wydruk za pomocą prostego, standaryzowanego formatowania. Określamy tylko, w postaci listy separowanej przecinkami, łańcuchy lub liczby do wypisania. Przy drukowaniu są one rozdzielane pojedynczymi spacjami, a na końcu dołączany jest znak nowej linii. Instrukcja ta wygląda tak:

print elem1, elem2, ...

Całość listy elementów może opcjonalnie być ujęta w nawiasy. Nawiasy są niezbędne jeśli któryś z elementów jest wyrażeniem z operatorem relacyjnym. Inaczej mogłoby ono być pomylone z przekierowaniem (zob. 6.6. Przekierowanie wyjścia print i printf).

Elementy do wypisania mogą być stałymi łańcuchami lub liczbami, polami bieżącego rekordu (jak $1), zmiennymi, czy wreszcie dowolnymi wyrażeniami awk. Wartości numeryczne są przekształcane na łańcuchy, a następnie wypisywane.

Instrukcja print jest całkowicie ogólna, jeśli chodzi o to, jakie wartości wypisać. Jednak, z dwoma wyjątkami, nie można wyszczególnić jak mają one zostać wypisane -- w ilu kolumnach, czy stosować notację wykładniczą czy nie, i tak dalej. (Opis wyjątków, zob. 6.3. Separatory wyjścia i 6.4. Sterowanie wyjściem numerycznym przez print.) W tym celu potrzebujemy instrukcji printf (zob. 6.5. Wymyślne wyjście dzięki instrukcji printf).

Zwykłe polecenie `print', bez żadnych elementów, jest równoważnikiem `print $0': wypisuje cały bieżący rekord. Do wypisania pustego wiersza stosujemy `print ""', gdzie "" jest łańcuchem pustym.

Do wypisania stałego kawałka tekstu stosujemy stałą łańcuchową, jak np. "Nie panikuj", jako jeden element. Jeżeli zapomnimy o znakach cudzysłowu, to tekst zostanie potraktowany jako wyrażenie awk i prawdopodobnie otrzymamy błąd. Należy pamiętać, że pomiędzy każdymi dwoma elementami wypisywana jest spacja.

Każda z instrukcji print generuje co najmniej jeden wiersz wyjścia. Ale nie jest ograniczona do jednego wiersza. Jeżeli wartością któregoś z łańcuchów jest łańcuch zawierający znak nowej linii, to znak ten jest wypisywany razem z resztą łańcucha. Pojedyncze print może w ten sposób stworzyć dowolną liczbę wierszy.

6.2. Przykłady instrukcji print

Oto przykład wypisywania łańcucha, który zawiera wbudowane znaki nowej linii (`\n' jest sekwencją specjalną, służącą do przedstawiania znaku nowej linii, zob. 4.2. Sekwencje specjalne):

$ awk 'BEGIN { print "wiersz jeden\wiersz dwa\nwiersz trzy" }'
-| wiersz jeden
-| wiersz dwa
-| wiersz trzy

Oto przykład wypisujący pierwsze dwa pola każdego rekordu wejściowego, ze spacją między nimi:

$ awk '{ print $1, $2 }' inventory-shipped
-| Jan 13
-| Feb 15
-| Mar 15
...

Typową pomyłką przy stosowaniu instrukcji print jest pominięcie przecinka między dwoma elementami. Często w wyniku na wyjściu elementy są zestawione razem, bez spacji. Przyczyną jest to, że postawienie obok siebie dwu wyrażeń łańcuchowych oznacza w awk ich konkatenację. Oto ten sam program bez przecinka:

$ awk '{ print $1 $2 }' inventory-shipped
-| Jan13
-| Feb15
-| Mar15
...

Dla osoby nie znającej pliku `inventory-shipped', żaden z powyższych przykładów wyjścia nie ma zbyt dużego sensu. Wiersz nagłówka na początku nadałby mu jasności. Dodajmy nagłówek do naszej tabeli miesięcy ($1) i wysłanych zielonych paczek ($2). Zrobimy to wykorzystując wzorzec BEGIN (zob. 8.1.5. Wzorce specjalne BEGIN i END) do wymuszenia jednokrotnego wypisania nagłówka:

awk 'BEGIN {  print "Mies. Paczki"
              print "----- ------" }
           {  print $1, $2 }' inventory-shipped

Odgadłeś już, co się stanie? Po uruchomieniu program wypisze:

Mies. Paczki
----- ------
Jan 13
Feb 15
Mar 15
...

Nagłówki i dane tabeli nie są wyrównane! Możemy to poprawić wypisując trochę spacji między naszymi dwoma polami:

awk 'BEGIN { print "Mies. Paczki"
             print "----- ------" }
           { print $1, "     ", $2 }' inventory-shipped

Można sobie wyobrazić, że taka metoda wyrównywania kolumn może stać się dość skomplikowana, jeśli do poprawienia mamy wiele kolumn. Zliczanie spacji dla dwóch czy trzech kolumn może być proste, lecz przy większej liczbie łatwo się pogubić. Z tego powodu stworzono instrukcję printf (zob. 6.5. Wymyślne wyjście dzięki instrukcji printf); jedną z jej specjalności jest wyrównywanie kolumn danych.

Nawiasem mówiąc, można kontynuować instrukcję print czy printf po prostu stawiając znak nowej linii po przecinku (zob. 2.6. Instrukcje awk a wiersze).

6.3. Separatory wyjścia

Jak uprzednio wspomniano, instrukcja print zawiera listę rozdzielonych przecinkami elementów. Na wyjściu elementy te są normalnie oddzielane spacjami. Tak jednak być nie musi -- pojedyncza spacja jest tylko domyślna. Można podać dowolny łańcuch znaków, który będzie stosowany jako separator pól wyjściowych (output field separator). Robi się to przez przypisanie go zmiennej wbudowanej OFS. Początkową jej wartością jest łańcuch " ", czyli pojedyncza spacja.

Wyjście z całej instrukcji print nazywamy rekordem wyjściowym (output record). Każda instrukcja print wypisuje jeden rekord wyjściowy a następnie łańcuch zwany separatorem rekordów wyjściowych (output record separator). Łańcuch ten jest określany przez zmienną wbudowaną ORS. Początkową wartością ORS jest łańcuch "\n", tj. znak nowej linii. Zatem, normalnie, każda zwykła instrukcja print tworzy osobny wiersz.

Sposób, w jaki rozdzielane są pola i rekordy wyjściowe, może być zmieniany przez przypisanie nowych wartości zmiennym OFS i/lub ORS. Zwyczajowym miejscem takiego przypisania jest wnętrze reguły BEGIN (zob. 8.1.5. Wzorce specjalne BEGIN i END), tak by odbyło się ono przed przetworzeniem czegokolwiek z wejścia. Można to również zrobić za pomocą przypisań w wierszu poleceń, przed nazwami plików wejściowych, lub stosując opcję `-v' wiersza poleceń (zob. 14.1. Opcje wiersza poleceń).

Poniższy przykład wypisuje pierwsze i ostatnie pole każdego rekordu wejściowego, rozdzielając je średnikami, z pustym wierszem dodanym po każdym wierszu:

$ awk 'BEGIN { OFS = ";"; ORS = "\n\n" }
>            { print $1, $2 }' BBS-list
-| aardvark;555-5553
-|
-| alpo-net;555-3412
-|
-| barfly;555-7685
...

Jeżeli wartość ORS nie zawiera znaku nowej linii, to całość wyjścia programu będzie połączona w jednym wierszu, chyba że wyemitujemy znaki nowej linii w jakiś inny sposób.

6.4. Sterowanie wyjściem numerycznym przez print

Gdy stosujemy instrukcję print to wypisania wartości numerycznych, awk wewnętrznie przekształca liczbę na łańcuch znaków i wypisuje ten łańcuch. Do wykonania tej konwersji awk wykorzystuje funkcję sprintf (zob. 12.3. Funkcje wbudowane działające na łańcuchach). Na razie wystarczy powiedzieć, iż funkcja sprintf akceptuje specyfikację formatu, która mówi jej jak formatować liczby (lub łańcuchy), i że istnieje wiele różnych sposobów, na jakie można formatować liczby. Rozmaite specyfikacje formatu omawiane są pełniej w 6.5.2. Litery sterujące formatem.

Zmienna wbudowana OFMT zawiera domyślną specyfikację formatu, który print używa z sprintf gdy chce przekształcić liczbę na łańcuch do wypisania. Domyślną wartością OFMT jest "%.6g". Podając jako wartość OFMT inne specyfikatory formatu zmieniamy sposób, w jaki print będzie wypisywał liczby. Jako krótki przykład:

$ awk 'BEGIN {
>   OFMT = "%.0f"  # wypisz liczby jako całkowite (zaokrągla)
>   print 17.23 }'
-| 17

Zgodnie ze standardem POSIX, zachowanie się awk będzie niezdefiniowane, jeśli OFMT zawiera coś innego niż specyfikację konwersji zmiennoprzecinkowej (c.k.).

6.5. Wymyślne wyjście dzięki instrukcji printf

Jeżeli potrzebujemy dokładniejszej kontroli nad formatem wyjściowym niż to daje print, wykorzystajmy printf. Za pomocą printf można określać szerokość, jaką ma mieć każda z pozycji, i rozmaite opcje formatowania liczb (jak podstawa do stosowania, liczba cyfr do wypisania po kropce dziesiętnej). Robi się to podając łańcuch, zwany łańcuchem formatu, który steruje sposobem i miejscem wypisania pozostałych argumentów.

6.5.1. Wprowadzenie do instrukcji printf

Instrukcja printf wygląda tak:

printf format, elem1, elem2, ...

Całość listy argumentów może być opcjonalnie ujęta w nawiasy. Nawiasy są konieczne jeśli któryś z elementów jest wyrażeniem używa operatora relacyjnego `>'. W przeciwnym razie mogłoby ono zostać pomylone z przekierowaniem (zob. 6.6. Przekierowanie wyjścia print i printf).

Różnicę pomiędzy printf a print stanowi argument format. Jest to wyrażenie, którego wartość jest brana jako łańcuch; określa w jaki sposób wypisać każdy z pozostałych argumentów. Nazywa się je łańcuchem formatu.

Łańcuch formatu jest bardzo podobny do stosowanego w funkcji bibliotecznej ANSI C printf. Większość formatu to tekst, jaki ma być wypisany dosłownie. W tekście rozsiane są specyfikatory formatu, po jednym na element. Każdy specyfikator formatu określa sposób wypisania argumentu mającego tę samą pozycję na liście argumentów, co specyfikator w łańcuchu formatu.

Instrukcja printf nie dołącza samoczynnie znaku nowej linii do tworzonego wyjścia. Wypisuje tylko to, co wyszczególnia łańcuch formatu. Zatem, jeżeli chcemy znaku nowej linii, to musimy go zawrzeć w łańcuchu formatu. Zmienne separatorów wyjścia OFS and ORS nie mają wpływu na instrukcje printf. Na przykład:

BEGIN {
   ORS = "\nAU!\n"; OFS = "!"
   msg = "Nie panikuj!"; printf "%s\n", msg
}

Ten program nadal wypisuje przyjazny komunikat `Nie panikuj!'.

6.5.2. Litery sterujące formatem

Specyfikator formatu rozpoczyna się znakiem `%' a kończy literą sterująca formatem; mówi ona instrukcji printf, jak powinien zostać wypisany pojedynczy element. (Chcąc faktycznie uzyskać wypisanie znaku `%', piszemy `%%'.) Litera sterująca formatem określa, jaki rodzaj wartości wypisać. Reszta specyfikatora formatu zbudowana jest z opcjonalnych modyfikatorów, będących parametrami, jakie mają zostać użyte, jak np. szerokość pola.

Oto lista liter sterujących formatem:

c
Wypisuje liczbę jako znak ASCII. Zatem, `printf "%c", 65' wypisuje literę `A'. Dla wartości łańcuchowej wypisywany jest pierwszy znak łańcucha.
d
i
Są sobie równoważne. Obie wypisują dziesiętną liczbę całkowitą. Specyfikacja `%i' istnieje dla zgodności z ANSI C.
e
E
Wypisuje liczbę w notacji naukowej (wykładniczej). Na przykład,
printf "%4.3e\n", 1950
wypisze `1.950e+03', o ogółem czterech cyfrach znaczących, z których trzy są po kropce dziesiętnej. The `4.3' są modyfikatorami, omówionymi poniżej. `%E' używa `E' zamiast `e' w wyjściu.
f
Wypisuje liczbę w notacji zmiennoprzecinkowej. Na przykład,
printf "%4.3f", 1950
wypisze `1950.000', z czterema cyfrach znaczących, z których trzy są po kropce dziesiętnej. The `4.3' są modyfikatorami, omówionymi poniżej.
g
G
Wypisuje liczbę albo w notacji naukowej albo zmiennoprzecinkowej, zależnie od tego, która zużyje mniej znaków. Jeśli wynik wypisywany jest w notacji naukowej, to `%G' stosuje `E' zamiast `e'.
o
Wypisuje ósemkową liczbę całkowitą bez znaku (unsigned octal integer). (W notacji ósemkowej, o podstawie osiem, cyfry biegną od `0' do `7'; dziesiętna liczba osiem jest reprezentowana jako `10' ósemkowo.)
s
Wypisuje łańcuch.
u
Wypisuje liczbę dziesiętną bez znaku (unsigned decimal). (Format ten ma znaczenie marginalne, gdyż wszystkie liczby w awk są zmiennoprzecinkowe. Obsługiwany głównie dla zgodności z C.)
x
X
Wypisuje szesnastkową liczbę całkowitą bez znaku (unsigned hexadecimal integer). W zapisie szesnastkowym (heksadecymalnym), o podstawie 16, cyframi są `0' do `9' oraz `a' do `f'. Szesnastkowa cyfra `f' reprezentuje dziesiętną liczbę 15.) `%X' stosuje litery `A' do `F' zamiast `a' do `f'.
%
Nie jest to faktycznie litera sterująca formatem, ale ma znaczenie użyta po `%': sekwencja `%%' wypisuje pojedynczy `%'. Nie pobiera argumentu i ignoruje wszelkie modyfikatory.

Przy stosowaniu liter sterujących formatem liczb całkowitych do wartości spoza zakresu długich całkowitych w C (long integer), gawk przełączy się na specyfikator formatu `%g'. Inne wersje awk mogą wypisywać niepoprawne wartości lub robić coś całkiem innego (c.k.).

6.5.3. Modyfikatory formatu w printf

Specyfikacja formatu może zawierać także modyfikatory, decydujące o tym, jaka część wartości elementu będzie wypisana i ile miejsca zajmie. Modyfikatory mogą wystąpić pomiędzy znakiem `%' a literą sterującą formatem. W przykładach niżej zastosowano symbol wyliczenia "*" do przedstawienia spacji w wyjściu. Oto możliwe modyfikatory, w kolejności, w jakiej mogą się pojawić:

-
Znak minus, użyty przed modyfikatorem szerokości (patrz niżej) nakazuje, by argument wyrównać do lewej na podanej szerokości. Normalnie argument jest wypisywany w zadanej szerokości z wyrównaniem do prawej. Zatem,
printf "%-4s", "foo"
wypisuje `foo*'.
spacja
W konwersjach numerycznych, poprzedza wartości dodatnie spacją, a ujemne znakiem minus.
+
Znak plus, używany przed modyfikatorem szerokości (patrz niżej) stanowi, by przy konwersjach numerycznych zawsze podany był znak, nawet jeśli dane do sformatowania są dodatnie. `+' przesłania modyfikator spacji.
#
Stosuje "alternatywną postać" dla niektórych liter sterujących. Przy `%o', zapewnia początkowe zero. Przy `%x' i `%X' zapewnia początkowe `0x' lub `0X' dla wyniku niezerowego. Przy `%e', `%E' i `%f' wynik będzie zawsze zawierał kropkę dziesiętną. Przy `%g' i `%G', końcowe zera nie są usuwane z wyniku.
0
Początkowe `0' (zero) działa jak flaga, wskazująca, że wyjście powinno być dopełnione zerami zamiast spacjami. Odnosi się to także do nienumerycznych formatów wyjściowych (c.k.). Flaga ta ma znaczenie tylko gdy pole jest szersze niż wartość do wypisania.
szer
Jest to liczba określająca pożądaną minimalną szerokość pola. Wstawienie dowolnej liczby pomiędzy znak `%' a znak sterujący formatem wymusza rozszerzenie pola na taką długość. Domyślną metodą poszerzenia jest dopełnienie spacjami po lewej. Na przykład,
printf "%4s", "foo"

wypisuje `*foo'. Wartość szer jest szerokością minimalną, nie maksymalną. Jeżeli wartość elementu wymaga więcej niż szer znaków, może być tak szeroka, jak to jest niezbędne. Zatem,
printf "%4s", "foobar"
wypisuje `foobar'. Poprzedzenie szer znakiem minus powoduje, że wyjście będzie dopełniane spacjami z prawej, zamiast z lewej.
.dokł
Jest to liczba określająca dokładność, jaka ma być użyta zastosowana przy wypisywaniu. Przy formatach `e', `E' i `f' podaje liczbę cyfr, jakie mają być wypisane na prawo od kropki dziesiętnej. Przy formatach `g' i `G', określa maksymalną ilość cyfr znaczących. Przy formatach `d', `o', `i', `u', `x' i `X' określa minimalną liczbę cyfr do wypisania. Dla łańcucha podaje maksymalną liczbę znaków łańcucha, jakie winny zostać wypisane. Stąd,
printf "%.4s", "foobar"
wypisuje `foob'.

Obsługiwana jest możliwość dynamicznych szer i dokł (na przykład, "%*.*s") zapewniana przez printf z biblioteki C. Zamiast podawania w łańcuchu formatu wartości szer i/lub dokł wprost, można je przekazać w liście argumentów. Na przykład:

w = 5
p = 3
s = "abcdefg"
printf "%*.*s\n", w, p, s

jest ściśle równoważne temu:

s = "abcdefg"
printf "%5.3s\n", s

Oba programy wypisują `**abc'.

Wcześniejsze wersje awk nie obsługują powyższej własności. Jeżeli musimy użyć takiej wersji, można zasymulować tę cechę wykorzystując do budowy łańcucha formatu konkatenację, jak tu:

w = 5
p = 3
s = "abcdefg"
printf "%" w "." p "s\n", s

Nie jest do szczególne łatwe w czytaniu, ale działa.

Programiści C mogą być przyzwyczajeni do używania dodatkowych flag `l' i `h' w łańcuchach formatu printf. Nie są one poprawne w awk. Większość implementacji awk milcząco ignoruje te flagi. Jeżeli w wierszu poleceń podano `--lint' (zob. 14.1. Opcje wiersza poleceń), gawk będzie ostrzegał o ich użyciu. Jeżeli podano `--posix', ich użycie jest błędem fatalnym.

6.5.4. Przykłady użycia printf

A oto, w jaki sposób wykorzystać printf do utworzenia wyrównanej tabeli:

awk '{ printf "%-10s %s\n", $1, $2 }' BBS-list

powyższe wypisuje nazwy BBS-ów ($1) z pliku `BBS-list' w postaci łańcucha 10 znaków, wyrównanego do lewej. Za nimi, w tym samym wierszu, wypisuje też numery telefonów ($2). Tworzy to wyrównaną dwukolumnową tabelę nazw i numerów telefonicznych:

$ awk '{ printf "%-10s %s\n", $1, $2 }' BBS-list
-| aardvark   555-5553
-| alpo-net   555-3412
-| barfly     555-7685
-| bites      555-1675
-| camelot    555-0542
-| core       555-2912
-| fooey      555-1234
-| foot       555-6699
-| macfoo     555-6480
-| sdace      555-3430
-| sabafoo    555-2127

Zauważyłeś, że nie określiliśmy, że numery telefonów mają być wypisane jako liczby? Musiały być wypisane jako łańcuchy, gdyż są rozdzielane kreską. Jeśli spróbowalibyśmy wypisać je jako liczby, to wszystkim, co byśmy otrzymali, byłyby pierwsze trzy cyfry, `555'. Wprowadziłoby to niezłe zamieszanie.

Nie podawaliśmy szerokości numerów telefonicznych, ponieważ występują jako ostatnie w wierszach. Nie musimy po nich stawiać spacji.

Można by nawet upiększyć naszą tabelę dodając nagłówki nad kolumnami. Zastosujemy do tego wzorzec BEGIN (zob. 8.1.5. Wzorce specjalne BEGIN i END), wymuszając tylko jednorazowe wypisanie nagłówka, na początku programu awk:

awk 'BEGIN { print "Nazwa     Numer"
             print "-----     -----" }
     { printf "%-10s %s\n", $1, $2 }' BBS-list

Zauważyłeś, że w powyższym przykładzie wymieszaliśmy instrukcje print i printf? Mogliśmy użyć wyłącznie instrukcji printf do uzyskania tych samych rezultatów:

awk 'BEGIN { printf "%-10s %s\n", "Nazwa", "Numer"
             printf "%-10s %s\n", "-----", "-----" }
     { printf "%-10s %s\n", $1, $2 }' BBS-list

Wypisując nagłówek każdej z kolumn za pomocą takiej samej specyfikacji formatu, co zastosowana dla elementów tej kolumny, upewniliśmy się, że nagłówki zostaną wyrównane dokładnie tak samo jak kolumny.

Fakt, iż ta sama specyfikacja formatu jest używana trzykrotnie, można uwypuklić przechowując ją w zmiennej, w ten sposób:

awk 'BEGIN { format = "%-10s %s\n"
             printf format, "Nazwa", "Numer"
             printf format, "-----", "-----" }
     { printf format, $1, $2 }' BBS-list

Sprawdź, czy potrafisz użyć instrukcji printf do wyrównania nagłówków i danych tabeli dla naszego przykładu z plikiem `inventory-shipped', omawianego wcześniej w sekcji o instrukcji print (zob. 6.1. Instrukcja print).

6.6. Przekierowanie wyjścia print i printf

Do tej pory zajmowaliśmy się tylko wyjściem piszącym na standardowe wyjście, zwykle terminal. Zarówno print jak i printf potrafią również wysyłać swoje wyjście w inne miejsca. Nazywamy to przekierowaniem (redirection).

Przekierowanie występuje po instrukcji print lub printf . Przekierowania w awk zapisywane są tak samo jak w poleceniach powłoki, za wyjątkiem tego, że są zapisane wewnątrz programu awk.

Mamy trzy postacie przekierowania wyjścia: wyjście do pliku, wyjście dopisywane do pliku i wyjście poprzez potok do innego polecenia. Wszystkie pokazano dla instrukcji print, ale działają identycznie dla printf.

print elementy > plik-wyj
Ten rodzaj przekierowania wypisuje elementy do pliku wyjściowego plik-wyj. Nazwa pliku plik-wyj może być dowolnym wyrażeniem. Jego wartość jest zmieniana na łańcuch a następnie używana jako nazwa pliku (zob. 7. Wyrażenia). Przy stosowaniu tego rodzaju przekierowania, plik-wyj jest wymazywany przed zapisaniem do niego pierwszego elementu wyjścia. Kolejne zapisy do tego samego pliku-wyj nie wymazują go, lecz są do niego dołączane. Jeżeli plik-wyj nie istnieje, to jest tworzony. Na przykład, oto jak program awk może zapisać listę BBS-ów do pliku `name-list' a listę numerów telefonów do pliku `phone-list'. Oba pliki wyjściowe zawierają po jednej nazwie lub numerze na wiersz.
$ awk '{ print $2 > "phone-list"
>        print $1 > "name-list" }' BBS-list
$ cat phone-list
-| 555-5553
-| 555-3412
...
$ cat name-list
-| aardvark
-| alpo-net
...
print elementy >> plik-wyj
Ten rodzaj przekierowania wpisuje elementy do wcześniej istniejącego pliku wynikowego plik-wyj. Różnica pomiędzy tym przekierowaniem a przekierowaniem z pojedynczym `>' jest taka, że stara zawartość (jeśli istnieje) pliku-wyj nie jest wymazywana. Zamiast tego wyjście awk jest dołączane na koniec pliku. Jeżeli plik-wyj nie istnieje, to jest tworzony.
print elementy | polecenie
Możliwe jest także wysłanie wyjścia do innego programu przez potok zamiast do pliku. Ten typ przekierowania otwiera potok do polecenia i wpisuje elementy przez ten potok, do innego procesu utworzonego w celu wykonania polecenia. Argument polecenie przekierowania jest faktycznie wyrażeniem awk. Jego wartość jest przekształcana na łańcuch, którego zawartość daje polecenie powłoki, jakie ma być uruchomione. Na przykład, poniższe tworzy dwa pliki, listę niesortowanych nazw BBS-ów i listę posortowaną w odwrotnym porządku alfabetycznym:
awk '{ print $1 > "names.unsorted"
       command = "sort -r > names.sorted"
       print $1 | command }' BBS-list
Tutaj lista niesortowana jest zapisywana zwykłym przekierowaniem, podczas gdy lista posortowana zapisywana jest za pomocą potokowania przez narzędzie sort. Kolejny przykład korzysta z przekierowania do wysłania wiadomości na listę pocztową `bug-system'. Może to być przydatne gdy w pojawiły się kłopoty w skrypcie awk uruchamianym okresowo do konserwacji systemu.
report = "mail bug-system"
print "Awk script failed:", $0 | report
m = ("at record number " FNR " of " FILENAME)
print m | report
close(report)
Wiadomość budowana jest za pomocą konkatenacji (złączenia łańcuchów) i zapisywana w zmiennej m. Następnie jest wysyłana potokiem do programu mail. Wywołujemy tu funkcję close, gdyż dobrym nawykiem jest zamykać potok zaraz po przesłaniu do niego zamierzonego wyjścia. Zob. 6.8. Zamykanie potoków oraz plików wejściowych i wyjściowych, gdzie jest o tym więcej napisane. Ten przykład ilustruje również zastosowanie zmiennej do reprezentacji pliku lub polecenia: nie jest konieczne używanie zawsze stałej łańcuchowej. Stosowanie zmiennej jest na ogół dobrym rozwiązaniem, ponieważ awk wymaga, by wartość łańcucha była za każdym razem zapisana tak samo.

Przekierowanie wyjścia przy użyciu `>', `>>' lub `|' prosi system o otwarcie pliku lub potoku tylko jeśli do konkretnego pliku lub polecenia, które podaliśmy, nasz program jeszcze nie pisał, albo jeśli je zamknięto po ostatnim zapisie.

Jak wspomniano wcześniej (zob. 5.8.8. Podsumowanie wariantów getline), wiele implementacji awk ogranicza ilość potoków, jakie program awk może mieć otwarte, do tylko jednego! W gawk nie ma takiego ograniczenia. Można otworzyć tyle potoków, na ile zezwoli stosowany system operacyjny.

6.7. Specjalne nazwy plików w gawk

Zgodnie z konwencją, pracujące programy mają trzy strumienie wejścia/wyjścia już dostępne dla nich do odczytu i zapisu. Są one znane jako standardowe wejście, standardowe wyjście i standardowe wyjście błędów. Strumienie te domyślnie są połączone z naszym terminalem, ale często są przekierowywane za pomocą powłoki, poprzez operatory `<', `<<', `>', `>>', `>&' i `|'. Standardowe wyjście błędów jest na ogół używane do zapisywania komunikatów o błędach. Powodem, dla którego mamy dwa odrębne strumienie, standardowe wyjście i standardowe wyjście błędów, jest to, że można je wówczas osobno przekierowywać.

W innych implementacjach awk, jedyną metodą zapisania komunikatu o błędzie na standardowym wyjściu błędów w programie awk jest:

print "Wykryto poważny błąd!" | "cat 1>&2"

Działa to dzięki otwarciu potoku do polecenia powłoki, które ma dostęp do standardowego strumienia błędów, jaki dziedziczy po procesie awk. Jest to wysoce nieeleganckie. Jest też nieefektywne, gdyż wymaga odrębnego procesu. Ludzie piszący programy awk często tego nie robią. Zamiast tego, wysyłają komunikaty o błędach na terminal, tak:

print "Wykryto poważny błąd!" > "/dev/tty"

Zwykle na to ten sam efekt, ale nie zawsze: chociaż standardowym strumieniem błędów jest na ogół terminal, to może ono zostać przekierowane, i gdy się tak dzieje, pisanie na terminal nie jest poprawne. W rzeczywistości, jeżeli awk uruchamiany jest z zadania tle, możemy w ogóle nie mieć terminala. Wówczas otwarcie `/dev/tty' się nie powiedzie.

gawk udostępnia specjalne nazwy plików do dostępu do trzech standardowych strumieni. Przy przekierowaniu wejścia lub wyjścia w gawk jeśli nazwa pliku pasuje do jednej z tych nazw specjalnych, to gawk używa wprost strumienia, który ona oznacza.

`/dev/stdin'
Standardowe wejście (deskryptor pliku 0).
`/dev/stdout'
Standardowe wyjście (deskryptor pliku 1).
`/dev/stderr'
Standardowe wejście błędów (deskryptor pliku 2).
`/dev/fd/N'
Plik skojarzony z deskryptorem pliku N. Plik taki musi być otwarty przez program inicjujący wykonanie awk (na ogół powłokę). Dopóki nie podejmujemy specjalnych starań w powłoce, z której wołamy awk, dostępne są tylko deskryptory 0, 1 i 2.

Nazwy plików `/dev/stdin', `/dev/stdout' i `/dev/stderr' są synonimami (aliasami) dla, odpowiednio, `/dev/fd/0', `/dev/fd/1' i `/dev/fd/2', ale są bardziej zrozumiałe.

Prawidłowym sposobem wypisywania komunikatów o błędach w programie gawk jest użycie `/dev/stderr', jak tu:

print "Wykryto poważny błąd!" > "/dev/stderr"

gawk udostępnia też specjalne nazwy plików dające dostęp do informacji o pracującym procesie gawk. Każdy z tych "plików" udostępnia jeden rekord danych. Chcąc odczytać je więcej niż raz, musimy je zamknąć funkcją close (zob. 6.8. Zamykanie potoków oraz plików wejściowych i wyjściowych). Tymi nazwami są:

`/dev/pid'
Odczyt tego pliku zwraca identyfikator (ID) procesu dla bieżącego procesu, dziesiętnie, zakończony znakiem nowej linii.
`/dev/ppid'
Odczyt tego pliku zwraca identyfikator procesu macierzystego dla bieżącego procesu, dziesiętnie, zakończony znakiem nowej linii.
`/dev/pgrpid'
Odczyt tego pliku zwraca identyfikator grupy procesu dla bieżącego procesu, dziesiętnie, zakończony znakiem nowej linii.
`/dev/user'
Odczyt tego pliku zwraca pojedynczy rekord zakończony znakiem nowej linii. Pola rozdzielone są spacjami. Pola reprezentują następujące informacje:
$1
Wartość zwrócona przez funkcję systemową getuid (rzeczywisty identyfikator numeryczny użytkownika).
$2
Wartość zwrócona przez funkcję systemową geteuid (efektywny identyfikator numeryczny użytkownika).
$3
Wartość zwrócona przez funkcję systemową getgid (rzeczywisty identyfikator numeryczny grupy).
$4
Wartość zwrócona przez funkcję systemową getegid (efektywny identyfikator numeryczny grupy).
Jeśli istnieją jakieś dodatkowe pola, to są one identyfikatorami grup zwróconymi przez funkcję systemową getgroups. (Nie wszystkie systemy obsługują przynależność do wielu grup.)

Powyższe specjalne nazwy plików mogą być stosowane w wierszu poleceń jako pliki danych, jak i do przekierowań wejścia/wyjścia w programie awk. Nie można ich stosować jako plików źródłowych opcji `-f'.

Rozpoznawanie powyższych specjalnych nazw plików jest wyłączone jeśli gawk jest w trybie zgodności (zob. 14.1. Opcje wiersza poleceń).

Uwaga!: Interpretacja tych nazw plików wykonywana jest przez sam gawk, chyba że nasz system faktycznie posiada katalog `/dev/fd' (lub dowolny inny z podanych wyżej specjalnych plików). Na przykład, zastosowanie `/dev/fd/4' jako wyjścia w rzeczywistości będzie pisać do pliku o deskryptorze 4, a nie do nowego deskryptora pliku, który został zduplikowany (przez dup) z deskryptora 4. W większości przypadków nie ma to znaczenia; jednak, istotne jest by nie zamykać plików związanych z deskryptorami 0, 1 i 2. Jeżeli zamkniemy jeden z nich, spowoduje to nieprzewidywalne zachowanie.

Pliki specjalne dające dostęp do informacji związanej z procesem znikną w przyszłych wersjach gawk. Zob. C.3. Prawdopodobne przyszłe rozszerzenia.

6.8. Zamykanie potoków oraz plików wejściowych i wyjściowych

Jeśli podczas wykonania programu awk ta sama nazwa pliku lub polecenia powłoki użyta jest z getline (zob. 5.8. Odczyt bezpośredni przez getline) więcej niż raz, plik jest otwierany (lub polecenie wykonywane) tylko za pierwszym razem. W tym momencie z pliku czy polecenia czytany jest pierwszy rekord wejścia. Przy następnym użyciu tego samego pliku lub polecenia z getline, czytany jest z niego kolejny rekord, i tak dalej.

Podobnie, gdy plik lub potok jest otwierany do wyprowadzenia wyników, nazwa z nim skojarzona jest zapamiętywana przez awk, a kolejne zapisy do tego samego pliku czy polecenia są dołączane do poprzednich. Plik lub potok pozostaje otwarty aż do zakończenia pracy awk.

Wynika stąd, że jeśli chcemy zacząć czytanie tego samego pliku od początku albo powtórnie uruchomić polecenie powłoki (zamiast czytania dalszego ciągu jego wyjścia), to musimy podjąć specjalne kroki. To, co musimy zrobić, to użycie funkcji close, jak niżej:

close(nazwapliku)

lub

close(polecenie)

Argumentem nazwapliku lub polecenie może być dowolne wyrażenie. Jego wartość musi dokładnie odpowiadać łańcuchowi, jaki był użyty do otwarcia pliku lub uruchomienia polecenia (łącznie ze spacjami i innymi "nieistotnymi" znakami). Na przykład, jeżeli otwieramy potok za pomocą:

"sort -r names" | getline foo

to musimy go zamknąć tak:

close("sort -r names")

Po wykonaniu wywołania tej funkcji następne getline z tego pliku czy polecenia, albo następne print lub printf do tego pliku czy polecenia, ponownie otworzy plik lub uruchomi polecenie.

Ponieważ wyrażenie użyte do zamknięcia pliku lub potoku musi dokładnie odpowiadać wyrażeniu użytemu do jego otwarcia (uruchomienia), dobrą praktyką jest stosowanie zmiennej do przechowania nazwy pliku (polecenia). Poprzedni przykład miałby postać

sortcom = "sort -r names"
sortcom | getline foo
...
close(sortcom)

Pomaga to uniknąć trudnych do znalezienia błędów typograficznych w programach awk.

Oto kilka powodów, dla których może zachodzić potrzeba zamknięcia pliku wyjściowego:

  • Do pisania do pliku i późniejszego czytania go w tym samym programie awk. Zamknij plik gdy tylko skończysz do niego pisać; następnie możesz zacząć odczyt za pomocą getline.
  • Do zapisu wielu plików, kolejno, w tym samym programie awk. Jeżeli nie zamkniemy plików, możemy ostatecznie wyczerpać systemowy limit plików otwartych w jednym procesie. Zatem zamknij każdy z nich gdy skończysz do niego pisać.
  • Do zakończenia polecenia. Przy przekierowaniu wyjścia przez potok polecenie czytające z potoku normalnie kontynuuje próby czytania wejścia tak długo, jak długo potok jest otwarty. Często znaczy to, że polecenie nie może faktycznie wykonać swojego zadania póki potok nie zostanie zamknięty. Na przykład, jeśli przekierujemy wyjście do programu mail, komunikat nie jest faktycznie wysyłany aż do zamknięcia potoku.
  • Do uruchomienia tego samego programu po raz drugi, z tymi samymi argumentami. To nie jest to samo, co podanie kolejnych danych wejściu pierwszego uruchomienia! Na przykład, załóżmy, że potokujemy wyjście do programu mail. Jeśli wypiszemy kilka wierszy przekierowanych do tego potoku bez jego zamykania, utworzą pojedynczą wiadomość z kilku wierszy. Natomiast jeśli zamkniemy potok po każdym wierszu wyjścia, to każdy wiersz będzie tworzył odrębną wiadomość.

close zwraca wartość zerową jeśli zamknięcie się powiodło. W przeciwnym razie wartość jest niezerowa. W tym przypadku gawk przypisuje zmiennej ERRNO łańcuch opisujący błąd, jaki wystąpił.

Jeśli używamy więcej plików niż system pozwala na posiadanie otwartych, gawk będzie usiłował multipleksować dostępne otwarte pliki między nasze pliki danych. Jego zdolność do wykonania tego zadania zależy od udogodnień systemu operacyjnego: może nie zawsze działać. Z tego powodu dobra praktyka i dobra przenośność doradzają, by zawsze stosować close w stosunku do plików, z którymi skończono pracę.

 


Przejdź do pierwszej, poprzedniej, następnej, ostatniej sekcji, spisu treści.

 
Linki sponsorowane

W celu realizacji usług i funkcji na witrynach internetowych ZUI "ELPRO" stosujemy pliki cookies. Korzystanie z witryny bez zmiany ustawień dotyczących plików cookies oznacza, że będą one zapisywane w urządzeniu wyświetlającym stronę internetową. Więcej szczegółów w Polityce plików cookies.

Akceptuję pliki cookies z tej witryny.