Bash receptury 9788324659746, 8324659749

236 114 12MB

Polish Pages [624]

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Bash receptury
 9788324659746, 8324659749

Table of contents :
Spis treści
Przedmowa
Dla kogo przeznaczona jest ta książka?
O książce
Konwencje typograficzne
Wykorzystanie przykładów kodu
Podziękowania
1. Wprowadzenie do pracy z powłoką bash
1.1. Rozszyfrowanie znaku zachęty
1.2. Ustalenie katalogu bieżącego
1.3. Wyszukiwanie i wykonywanie poleceń
1.4. Uzyskiwanie informacji na temat plików
1.5. Wyświetlenie plików ukrytych z bieżącego katalogu
1.6. Cudzysłowy i apostrofy w instrukcjach powłoki
1.7. Wykorzystywanie i zastępowanie poleceń wbudowanych i zewnętrznych
1.8. Sprawdzenie, czy powłoka pracuje w trybie interaktywnym
1.9. Ustawienie interpretera bash jako domyślnej powłoki
1.10. Pobranie interpretera bash dla systemu Linux
1.11. Pobranie interpretera bash dla systemu BSD
1.12. Pobranie interpretera bash dla systemu Mac OS X
1.13. Pobranie interpretera bash dla systemu Unix
1.14. Pobranie interpretera bash dla systemu Windows
1.15. Korzystanie z powłoki bash bez jej pobierania
1.16. Dodatkowa dokumentacja powłoki bash
2. Standardowy strumień wyjściowy
2.1. Przekazywanie danych wyjściowych do okna terminala
2.2. Wyświetlanie tekstu z zachowaniem znaków spacji
2.3. Formatowanie danych wyjściowych
2.4. Wyświetlanie wyniku bez znaku nowego wiersza
2.5. Zapisywanie danych wyjściowych polecenia
2.6. Zapisywanie wyniku w plikach innych katalogów
2.7. Zapisywanie wyniku polecenia ls
2.8. Przekazanie danych wyjściowych i komunikatów o błędach do różnych plików
2.9. Przekazanie danych wyjściowych i komunikatów o błędach do tego samego pliku
2.10. Dodawanie danych wyjściowych zamiast nadpisywania ich
2.11. Wykorzystanie jedynie początkowego lub końcowego fragmentu pliku
2.12. Pomijanie nagłówka pliku
2.13. Odrzucanie danych wyjściowych
2.14. Zapisywanie i grupowanie danych wyjściowych większej liczby poleceń
2.15. Łączenie dwóch programów przez wykorzystanie danych wyjściowych jako wejściowych
2.16. Zapisywanie kopii danych wyjściowych mimo wykorzystywania ich jako danych wejściowych
2.17. Łączenie dwóch programów z wykorzystaniem danych wyjściowych jako parametrów
2.18. Wielokrotne przekierowania w jednym wierszu
2.19. Zapisywanie danych wyjściowych, gdy wydaje się, że przekierowanie nie działa
2.20. Zamiana strumieni STDERR i STDOUT
2.21. Zabezpieczanie pliku przed przypadkowym nadpisaniem
2.22. Celowe nadpisanie pliku
3. Standardowy strumień wejściowy
3.1. Pobieranie danych wejściowych z pliku
3.2. Umieszczenie danych w skrypcie
3.3. Wyeliminowanie nietypowego działania osadzonych dokumentów
3.4. Wcinanie osadzonych dokumentów
3.5. Pobieranie danych od użytkownika
3.6. Wprowadzanie odpowiedzi typu tak-nie
3.7. Wybór opcji z listy
3.8. Wprowadzanie haseł
4. Wykonywanie poleceń
4.1. Uruchamianie pliku wykonywalnego
4.2. Sprawdzenie, czy polecenie zostało wykonane poprawnie
4.3. Sekwencyjne wykonanie kilku poleceń
4.4. Jednoczesne wykonanie kilku poleceń
4.5. Ustalenie poprawności wykonania polecenia
4.6. Zmniejszenie liczby instrukcji if
4.7. Wykonywanie poleceń bez nadzoru
4.8. Wyświetlenie komunikatów o błędach
4.9. Wykonywanie poleceń zapisanych w zmiennych
4.10. Uruchomienie wszystkich skryptów w katalogu
5. Podstawy tworzenia skryptów — zmienne powłoki
5.1. Dokumentowanie skryptu
5.2. Osadzanie dokumentacji w treści skryptu
5.3. Zachowanie czytelności skryptu
5.4. Oddzielenie nazw zmiennych od otaczającego je tekstu
5.5. Eksportowanie zmiennych
5.6. Wyświetlanie wartości wszystkich zmiennych
5.7. Wykorzystanie parametrów w skryptach powłoki
5.8. Iteracyjna analiza parametrów przekazanych do skryptu
5.9. Obsługa parametrów zawierających znaki spacji
5.10. Przetwarzanie listy parametrów zawierających znaki spacji
5.11. Zliczanie parametrów
5.12. Wykorzystanie parametrów
5.13. Pobieranie wartości domyślnych
5.14. Ustawianie wartości domyślnych
5.15. Wykorzystanie pustego ciągu tekstowego jako poprawnej wartości domyślnej
5.16. Wykorzystanie wartości innych niż stały ciąg tekstowy jako wartości domyślnych
5.17. Generowanie komunikatów o błędach w przypadku niezdefiniowania parametrów
5.18. Modyfikacja fragmentów ciągu tekstowego
5.19. Wykorzystanie zmiennych tablicowych
6. Logika i arytmetyka powłoki
6.1. Wykonywanie działań arytmetycznych w skrypcie powłoki
6.2. Rozgałęzianie kodu w instrukcjach warunkowych
6.3. Sprawdzanie właściwości plików
6.4. Sprawdzanie więcej niż jednego warunku
6.5. Sprawdzanie właściwości ciągów tekstowych
6.6. Sprawdzanie równości dwóch wartości
6.7. Sprawdzanie zgodności wartości ze wzorcem
6.8. Sprawdzanie wartości z użyciem wyrażeń regularnych
6.9. Zmiana sposobu działania skryptu w zależności od rodzaju przekierowania danych
6.10. Pętla while
6.11. Pętla z wykorzystaniem instrukcji read
6.12. Pętla ze zliczaniem iteracji
6.13. Pętla z wykorzystaniem wartości zmiennoprzecinkowych
6.14. Wielokrotne rozgałęzianie kodu
6.15. Przetwarzanie parametrów wiersza polecenia
6.16. Utworzenie prostego menu
6.17. Zmiana znaku zachęty w tekstowym menu
6.18. Kalkulator wykorzystujący notację RPN
6.19. Kalkulator obsługiwany z poziomu wiersza poleceń
7. Dodatkowe narzędzia powłoki I
7.1. Wyszukiwanie określonego ciągu w pliku tekstowym
7.2. Wyświetlenie jedynie nazwy pliku zawierającego poszukiwany ciąg tekstowy
7.3. Zakończenie wyszukiwania wynikiem typu „prawda - fałsz”
7.4. Wyszukiwanie ciągu tekstowego niezależnie od wielkości liter
7.5. Przeszukiwanie danych przekazywanych w potoku
7.6. Odrzucenie niepotrzebnych danych z procedury wyszukiwania
7.7. Wyszukiwanie z użyciem bardziej rozbudowanych wzorców
7.8. Wyszukiwanie numeru NIP
7.9. Wykorzystanie polecenia grep do wyszukiwania informacji w zarchiwizowanych plikach
7.10. Zachowanie części listingu wynikowego
7.11. Zachowanie fragmentu wiersza wynikowego
7.12. Odwrócenie kolejności słów w każdym wierszu
7.13. Sumowanie zbioru wartości
7.14. Zliczanie wartości tekstowych
7.15. Wyświetlanie danych w formie histogramu
7.16. Wyświetlenie fragmentu tekstu występującego za wyszukaną frazą
8. Dodatkowe narzędzia powłoki II
8.1. Sortowanie danych wyjściowych
8.2. Sortowanie wartości liczbowych
8.3. Sortowanie adresów IP
8.4. Wycinanie fragmentów listingu wynikowego
8.5. Usuwanie zduplikowanych wierszy
8.6. Kompresja plików
8.7. Rozpakowywanie plików
8.8. Sprawdzenie docelowego katalogu dla plików archiwum
8.9. Zamiana znaków
8.10. Zamiana dużych liter na małe
8.11. Konwersja plików DOS do formatu systemu Linux
8.12. Usuwanie cudzysłowów drukarskich
8.13. Zliczanie wierszy, słów i znaków pliku
8.14. Zmiana podziału wierszy
8.15. Dodatkowe funkcje polecenia less
9. Wyszukiwanie plików — polecenia find, locate, slocate
9.1. Wyszukiwanie wszystkich plików MP3
9.2. Przetwarzanie nazw plików zawierających niestandardowe znaki
9.3. Zwiększenie szybkości przetwarzania wyszukanych plików
9.4. Wyszukiwanie plików wskazywanych przez dowiązania symboliczne
9.5. Wyszukiwanie plików bez względu na wielkość liter występujących w nazwach
9.6. Wyszukiwanie plików na podstawie daty
9.7. Wyszukiwanie plików określonego typu
9.8. Wyszukiwanie plików o określonym rozmiarze
9.9. Wyszukiwanie plików o określonej treści
9.10. Szybkie wyszukiwanie plików i ich treści
9.11. Wyszukiwanie plików z wykorzystaniem listy potencjalnych lokalizacji
10. Dodatkowe mechanizmy skryptowe
10.1. „Demonizowanie” skryptu
10.2. Wielokrotne wykorzystanie kodu — polecenia include i source
10.3. Wykorzystanie skryptów konfiguracyjnych w skrypcie
10.4. Definiowanie funkcji
10.5. Wykorzystanie funkcji. Parametry i zwracane wartości
10.6. Przechwytywanie przerwań
10.7. Zmiana definicji poleceń za pomocą aliasów
10.8. Pomijanie aliasów i funkcji
11. Przetwarzanie informacji o dacie i czasie
11.1. Formatowanie dat podczas wyświetlania
11.2. Dostarczanie domyślnej wartości daty
11.3. Automatyczne generowanie dat z określonego zakresu
11.4. Przekształcenie daty i czasu w znacznik czasu
11.5. Przekształcanie znaczników czasu w ciągi dat i czasu
11.6. Pobranie daty poprzedniego lub kolejnego dnia w języku Perl
11.7. Obliczanie daty i czasu
11.8. Obsługa stref czasowych, czasu letniego oraz lat przestępnych
11.9. Wykorzystanie polecenia date i mechanizmu cron do uruchomienia skryptu w wybranym dniu
12. Skrypty usprawniające pracę użytkownika
12.1. Na początek coś łatwego — wyświetlanie myślników
12.2. Przeglądanie albumu ze zdjęciami
12.3. Zapis danych w odtwarzaczu MP3
12.4. Nagrywanie płyt CD
12.5. Porównywanie dwóch dokumentów
13. Interpretacja danych i podobne zadania
13.1. Przetwarzanie parametrów skryptu powłoki
13.2. Przetwarzanie parametrów z własnymi komunikatami o błędach
13.3. Interpretacja kodu HTML
13.4. Zapisywanie danych wynikowych w tablicy
13.5. Pobieranie danych wynikowych z wykorzystaniem wywołania funkcji
13.6. Interpretacja tekstu z wykorzystaniem instrukcji read
13.7. Zapisywanie danych w tablicy za pomocą instrukcji read
13.8. Liczba mnoga angielskich rzeczowników
13.9. Przetwarzanie danych znak po znaku
13.10. Wyczyszczenie drzewa kodu źródłowego w systemie SVN
13.11. Utworzenie bazy danych MySQL
13.12. Wyodrębnianie określonych pól listingu danych
13.13. Modyfikacja określonych pól listingu danych
13.14. Usuwanie krańcowych znaków odstępu
13.15. Kompresowanie znaków odstępu
13.16. Przetwarzanie pól o stałej długości
13.17. Przetwarzanie plików niezawierających znaków nowego wiersza
13.18. Zapis pliku danych w formacie CSV
13.19. Przetwarzanie plików z danymi CSV
14. Bezpieczne skrypty powłoki
14.1. Unikanie częstych problemów związanych z bezpieczeństwem
14.2. Unikanie spoofingu w pracy interpretera
14.3. Wyznaczanie bezpiecznej wartości $PATH
14.4. Usuwanie wszystkich aliasów
14.5. Czyszczenie tablicy odwzorowań plików wykonywalnych
14.6. Zapobieganie zrzutom pamięci
14.7. Wyznaczenie bezpiecznej wartości $IFS
14.8. Wyznaczanie bezpiecznej wartości umask
14.9. Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfikowanie zawartości
14.10. Dodawanie bieżącego katalogu do listy $PATH
14.11. Bezpieczne pliki tymczasowe
14.12. Walidacja wprowadzanych danych
14.13. Definiowanie praw dostępu
14.14. Ujawnienie haseł na liście procesów
14.15. Tworzenie skryptów z prawami setuid i setgid
14.16. Ograniczenie praw konta gościa
14.17. Wykorzystanie środowiska chroot
14.18. Wykonywanie skryptu z prawami zwykłego użytkownika
14.19. Bezpieczne wykorzystanie mechanizmu sudo
14.20. Wykorzystanie haseł w skryptach
14.21. Wykorzystanie usługi SSH bez hasła
14.22. Ograniczenie liczby poleceń SSH
14.23. Rozłączanie nieaktywnych sesji
15. Zaawansowane mechanizmy skryptowe
15.1. Przenośność skryptu — problem wiersza #!
15.2. Ustawianie zmiennej $PATH zgodnie z zaleceniami POSIX
15.3. Tworzenie przenośnych skryptów powłoki
15.4. Testowanie skryptów w środowisku VMware
15.5. Przenośność kodu pętli
15.6. Przenośność instrukcji echo
15.7. Dzielenie danych wyjściowych tylko wtedy, gdy jest to konieczne
15.8. Przeglądanie danych wynikowych w formacie szesnastkowym
15.9. Wykorzystanie mechanizmów przekierowania sieciowego
15.10. Ustalenie własnych adresów IP
15.11. Pobieranie danych z innego komputera
15.12. Przekierowanie wyjścia na czas działania skryptu
15.13. Eliminacja błędów typu „argument list too long”
15.14. Wysyłanie komunikatów syslog z poziomu skryptu
15.15. Wysyłanie wiadomości e-mail ze skryptu
15.16. Automatyzacja zadań z wykorzystaniem podziału procesu na etapy
16. Konfiguracja i dostosowanie powłoki bash
16.1. Opcje startowe powłoki bash
16.2. Dostosowanie znaku zachęty
16.3. Trwała zmiana wartości $PATH
16.4. Chwilowa zmiana wartości $PATH
16.5. Wyznaczanie wartości $CDPATH
16.6. Skracanie i zmienianie nazw poleceń
16.7. Dostosowanie domyślnego sposobu działania powłoki i jej środowiska
16.8. Zmiana sposobu działania mechanizmu readline za pomocą skryptu .inputrc
16.9. Własny zbiór narzędzi — dodanie ścieżki ~/bin
16.10. Wykorzystanie dodatkowych znaków zachęty
— $PS2, $PS3, $PS4
16.11. Synchronizowanie historii poleceń wprowadzanych w różnych sesjach
16.12. Włączanie opcji związanych z historią poleceń
16.13. Utworzenie lepszego polecenia cd
16.14. Utworzenie katalogu i przejście do niego w jednym kroku
16.15. Przejście do katalogów najniższego poziomu
16.16. Dodawanie nowych funkcji przez zastosowanie ładowanych poleceń wbudowanych
16.17. Usprawnienie mechanizmu uzupełniania poleceń
16.18. Właściwe wykorzystanie plików startowych
16.19. Tworzenie samodzielnych, przenośnych plików RC
16.20. Uruchomienie powłoki z własną konfiguracją
17. Zadania administracyjne
17.1. Zmiana nazwy wielu plików
17.2. Dokumentacja GNU Texinfo i Info w systemie Linux
17.3. Rozpakowywanie wielu plików ZIP
17.4. Przywracanie przerwanych sesji za pomocą narzędzia screen
17.5. Współdzielenie pojedynczej sesji powłoki
17.6. Rejestrowanie danych z całej sesji lub zadania wsadowego
17.7. Czyszczenie ekranu po wylogowaniu
17.8. Rejestracja metadanych plików w celu ich późniejszego odtworzenia
17.9. Tworzenie indeksu plików
17.10. Wykorzystanie poleceń diff i patch
17.11. Zliczanie różnic między plikami
17.12. Usuwanie lub zmiana nazwy plików
zawierających znaki specjalne w nazwie
17.13. Dołączanie danych na początku pliku
17.14. Edycja treści pliku
17.15. Wykorzystanie mechanizmu sudo w odniesieniu do grupy poleceń
17.16. Wyszukiwanie wierszy tylko jednego pliku
17.17. Zachowanie N ostatnich obiektów
17.18. Filtrowanie wyniku polecenia ps za pomocą instrukcji grep, ale z pominięciem w zestawieniu samej instrukcji grep
17.19. Sprawdzenie, czy dany proces działa
17.20. Dodawanie prefiksu lub sufiksu do danych wynikowych
17.21. Numerowanie wierszy
17.22. Generowanie sekwencji liczbowych
17.23. Emulacja instrukcji pause systemu DOS
17.24. Wyświetlanie separatora tysięcy
18. Mniej pisania — szybsza praca
18.1. Szybkie przechodzenie między określonymi katalogami
18.2. Powtarzanie ostatniego polecenia
18.3. Uruchomienie polecenia zbliżonego do wykonywanego wcześniej
18.4. Podmiana wartości składających się z większej liczby słów
18.5. Powtórne wykorzystanie parametrów
18.6. Automatyczne uzupełnianie nazw
18.7. Bezpieczne działania
19. Często popełniane błędy — rozwiązania i podpowiedzi
19.1. Zapominanie o ustawieniu praw wykonywania
19.2. Usuwanie błędu „Nie ma takiego pliku ani katalogu”
19.3. Zapominanie o braku bieżącego katalogu w zmiennej $PATH
19.4. Nadawanie skryptowi nazwy test
19.5. Spodziewana zmiana eksportowanych wartości
19.6. Brak cudzysłowów w operacjach przypisania wywołuje błędy „command not found”
19.7. Wartości pasujące do wzorca są układane w kolejności alfabetycznej
19.8. Potoki powołują podpowłoki
19.9. Uzdrawianie terminala
19.10. Usuwanie plików z użyciem pustej zmiennej
19.11. Niestandardowe zachowanie instrukcji printf
19.12. Sprawdzanie składni skryptu powłoki
19.13. Śledzenie przebiegu skryptów
19.14. Unikanie komunikatów „command not found” podczas korzystania z funkcji
19.15. Pomyłki w stosowaniu symboli wieloznacznych powłoki i wyrażeń regularnych
A. Podręczna pomoc
Wywołanie powłoki bash
Dostosowanie znaku zachęty
Symbole specjalne ANSI
Polecenia wbudowane i słowa kluczowe
Wbudowane zmienne powłoki
Opcje instrukcji set
Opcje instrukcji shopt
Zmiana sposobu działania powłoki za pomocą instrukcji set, shopt i zmiennych środowiskowych
Operatory wyrażeń warunkowych
Przekierowanie strumieni wejścia-wyjścia
Opcje i znaki specjalne instrukcji echo
Instrukcja printf
Formatowanie daty i czasu z wykorzystaniem funkcji strftime
Znaki wzorców dopasowywania
Operatory rozszerzonego mechanizmu dopasowywania do wzorca extglob
Symbole specjalne polecenia tr
Składnia pliku konfiguracyjnego mechanizmu readline
Polecenia trybu emacs
Polecenia sterujące trybu vi
Tabela wartości ASCII
B. Przykłady dołączone do oprogramowania bash
Przykłady zapisane w katalogu startup-files
C. Przetwarzanie poleceń
Etapy przetwarzania polecenia
D. Kontrola wersji
CVS
Subversion
RCS
Inne rozwiązania
E. Kompilacja powłoki bash
Pobranie pakietu bash
Rozpakowanie archiwum
Co zawiera archiwum?
Do kogo się zwrócić?
Skorowidz
O autorach
Kolofon

Citation preview

Tytuł oryginału: bash Cookbook™ Tłumaczenie: Marek Pałczyński ISBN: 978-83-246-5974-6 © Helion S.A. 2008 Authorized translation of the English edition of bash Cookbook © 2007 O’Reilly Media, Inc. This translation is published and sold by permission of O’Reilly Media, Inc., the owner of all rights to publish and sell the same. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 032 231 22 19, 032 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/bashre.zip Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie?bashre_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland.

• Poleć książkę na Facebook.com

• Księgarnia internetowa

• Kup w wersji papierowej

• Lubię to! » Nasza społeczność

• Oceń książkę

Spis treści

Przedmowa .............................................................................................................................. 13 1. Wprowadzenie do pracy z powłoką bash . ................................................................. 21 1.1. 1.2. 1.3. 1.4. 1.5. 1.6. 1.7. 1.8. 1.9. 1.10. 1.11. 1.12. 1.13. 1.14. 1.15. 1.16.

Rozszyfrowanie znaku zachęty Ustalenie katalogu bieżącego Wyszukiwanie i wykonywanie poleceń Uzyskiwanie informacji na temat plików Wyświetlenie plików ukrytych z bieżącego katalogu Cudzysłowy i apostrofy w instrukcjach powłoki Wykorzystywanie i zastępowanie poleceń wbudowanych i zewnętrznych Sprawdzenie, czy powłoka pracuje w trybie interaktywnym Ustawienie interpretera bash jako domyślnej powłoki Pobranie interpretera bash dla systemu Linux Pobranie interpretera bash dla systemu BSD Pobranie interpretera bash dla systemu Mac OS X Pobranie interpretera bash dla systemu Unix Pobranie interpretera bash dla systemu Windows Korzystanie z powłoki bash bez jej pobierania Dodatkowa dokumentacja powłoki bash

24 25 26 28 30 32 34 36 37 38 42 43 43 44 46 47

2. Standardowy strumień wyjściowy . ............................................................................ 51 2.1. 2.2. 2.3. 2.4. 2.5. 2.6. 2.7. 2.8.

Przekazywanie danych wyjściowych do okna terminala Wyświetlanie tekstu z zachowaniem znaków spacji Formatowanie danych wyjściowych Wyświetlanie wyniku bez znaku nowego wiersza Zapisywanie danych wyjściowych polecenia Zapisywanie wyniku w plikach innych katalogów Zapisywanie wyniku polecenia ls Przekazanie danych wyjściowych i komunikatów o błędach do różnych plików

52 53 54 55 56 58 59 60

3

2.9. 2.10. 2.11. 2.12. 2.13. 2.14. 2.15. 2.16. 2.17. 2.18. 2.19. 2.20. 2.21. 2.22.

Przekazanie danych wyjściowych i komunikatów o błędach do tego samego pliku Dodawanie danych wyjściowych zamiast nadpisywania ich Wykorzystanie jedynie początkowego lub końcowego fragmentu pliku Pomijanie nagłówka pliku Odrzucanie danych wyjściowych Zapisywanie i grupowanie danych wyjściowych większej liczby poleceń Łączenie dwóch programów przez wykorzystanie danych wyjściowych jako wejściowych Zapisywanie kopii danych wyjściowych mimo wykorzystywania ich jako danych wejściowych Łączenie dwóch programów z wykorzystaniem danych wyjściowych jako parametrów Wielokrotne przekierowania w jednym wierszu Zapisywanie danych wyjściowych, gdy wydaje się, że przekierowanie nie działa Zamiana strumieni STDERR i STDOUT Zabezpieczanie pliku przed przypadkowym nadpisaniem Celowe nadpisanie pliku

61 62 63 64 65 66 67 69 71 72 73 75 77 78

3. Standardowy strumień wejściowy . ............................................................................ 81 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. 3.7. 3.8.

Pobieranie danych wejściowych z pliku Umieszczenie danych w skrypcie Wyeliminowanie nietypowego działania osadzonych dokumentów Wcinanie osadzonych dokumentów Pobieranie danych od użytkownika Wprowadzanie odpowiedzi typu tak-nie Wybór opcji z listy Wprowadzanie haseł

81 82 83 85 86 87 90 91

4. Wykonywanie poleceń .................................................................................................93 4.1. 4.2. 4.3. 4.4. 4.5. 4.6. 4.7. 4.8. 4.9. 4.10.

4

|

Uruchamianie pliku wykonywalnego Sprawdzenie, czy polecenie zostało wykonane poprawnie Sekwencyjne wykonanie kilku poleceń Jednoczesne wykonanie kilku poleceń Ustalenie poprawności wykonania polecenia Zmniejszenie liczby instrukcji if Wykonywanie poleceń bez nadzoru Wyświetlenie komunikatów o błędach Wykonywanie poleceń zapisanych w zmiennych Uruchomienie wszystkich skryptów w katalogu

Spis treści

93 95 97 98 100 101 102 103 104 105

5. Podstawy tworzenia skryptów — zmienne powłoki . ............................................. 107 5.1. 5.2. 5.3. 5.4. 5.5. 5.6. 5.7. 5.8. 5.9. 5.10. 5.11. 5.12. 5.13. 5.14. 5.15. 5.16. 5.17. 5.18. 5.19.

Dokumentowanie skryptu Osadzanie dokumentacji w treści skryptu Zachowanie czytelności skryptu Oddzielenie nazw zmiennych od otaczającego je tekstu Eksportowanie zmiennych Wyświetlanie wartości wszystkich zmiennych Wykorzystanie parametrów w skryptach powłoki Iteracyjna analiza parametrów przekazanych do skryptu Obsługa parametrów zawierających znaki spacji Przetwarzanie listy parametrów zawierających znaki spacji Zliczanie parametrów Wykorzystanie parametrów Pobieranie wartości domyślnych Ustawianie wartości domyślnych Wykorzystanie pustego ciągu tekstowego jako poprawnej wartości domyślnej Wykorzystanie wartości innych niż stały ciąg tekstowy jako wartości domyślnych Generowanie komunikatów o błędach w przypadku niezdefiniowania parametrów Modyfikacja fragmentów ciągu tekstowego Wykorzystanie zmiennych tablicowych

109 110 111 113 114 116 117 118 119 121 123 125 126 127 128 129 130 132 133

6. Logika i arytmetyka powłoki . ................................................................................... 135 6.1. 6.2. 6.3. 6.4. 6.5. 6.6. 6.7. 6.8. 6.9. 6.10. 6.11. 6.12. 6.13. 6.14. 6.15. 6.16.

Wykonywanie działań arytmetycznych w skrypcie powłoki Rozgałęzianie kodu w instrukcjach warunkowych Sprawdzanie właściwości plików Sprawdzanie więcej niż jednego warunku Sprawdzanie właściwości ciągów tekstowych Sprawdzanie równości dwóch wartości Sprawdzanie zgodności wartości ze wzorcem Sprawdzanie wartości z użyciem wyrażeń regularnych Zmiana sposobu działania skryptu w zależności od rodzaju przekierowania danych Pętla while Pętla z wykorzystaniem instrukcji read Pętla ze zliczaniem iteracji Pętla z wykorzystaniem wartości zmiennoprzecinkowych Wielokrotne rozgałęzianie kodu Przetwarzanie parametrów wiersza polecenia Utworzenie prostego menu

135 138 141 144 145 146 148 150 152 153 156 158 159 160 162 165 Spis treści

|

5

6.17. Zmiana znaku zachęty w tekstowym menu 6.18. Kalkulator wykorzystujący notację RPN 6.19. Kalkulator obsługiwany z poziomu wiersza poleceń

166 167 170

7. Dodatkowe narzędzia powłoki I . ...............................................................................173 7.1. 7.2. 7.3. 7.4. 7.5. 7.6. 7.7. 7.8. 7.9. 7.10. 7.11. 7.12. 7.13. 7.14. 7.15. 7.16.

Wyszukiwanie określonego ciągu w pliku tekstowym Wyświetlenie jedynie nazwy pliku zawierającego poszukiwany ciąg tekstowy Zakończenie wyszukiwania wynikiem typu „prawda - fałsz” Wyszukiwanie ciągu tekstowego niezależnie od wielkości liter Przeszukiwanie danych przekazywanych w potoku Odrzucenie niepotrzebnych danych z procedury wyszukiwania Wyszukiwanie z użyciem bardziej rozbudowanych wzorców Wyszukiwanie numeru NIP Wykorzystanie polecenia grep do wyszukiwania informacji w zarchiwizowanych plikach Zachowanie części listingu wynikowego Zachowanie fragmentu wiersza wynikowego Odwrócenie kolejności słów w każdym wierszu Sumowanie zbioru wartości Zliczanie wartości tekstowych Wyświetlanie danych w formie histogramu Wyświetlenie fragmentu tekstu występującego za wyszukaną frazą

174 176 177 178 179 181 182 184 185 186 187 188 189 190 192 194

8. Dodatkowe narzędzia powłoki II . ............................................................................. 197 8.1. 8.2. 8.3. 8.4. 8.5. 8.6. 8.7. 8.8. 8.9. 8.10. 8.11. 8.12. 8.13. 8.14. 8.15.

6

|

Sortowanie danych wyjściowych Sortowanie wartości liczbowych Sortowanie adresów IP Wycinanie fragmentów listingu wynikowego Usuwanie zduplikowanych wierszy Kompresja plików Rozpakowywanie plików Sprawdzenie docelowego katalogu dla plików archiwum Zamiana znaków Zamiana dużych liter na małe Konwersja plików DOS do formatu systemu Linux Usuwanie cudzysłowów drukarskich Zliczanie wierszy, słów i znaków pliku Zmiana podziału wierszy Dodatkowe funkcje polecenia less

Spis treści

197 198 199 202 203 204 207 208 209 210 211 212 213 214 215

9. Wyszukiwanie plików — polecenia find, locate, slocate .........................................217 9.1. 9.2. 9.3. 9.4. 9.5.

Wyszukiwanie wszystkich plików MP3 Przetwarzanie nazw plików zawierających niestandardowe znaki Zwiększenie szybkości przetwarzania wyszukanych plików Wyszukiwanie plików wskazywanych przez dowiązania symboliczne Wyszukiwanie plików bez względu na wielkość liter występujących w nazwach 9.6. Wyszukiwanie plików na podstawie daty 9.7. Wyszukiwanie plików określonego typu 9.8. Wyszukiwanie plików o określonym rozmiarze 9.9. Wyszukiwanie plików o określonej treści 9.10. Szybkie wyszukiwanie plików i ich treści 9.11. Wyszukiwanie plików z wykorzystaniem listy potencjalnych lokalizacji

218 219 221 221 222 223 224 225 226 227 229

10. Dodatkowe mechanizmy skryptowe .........................................................................233 10.1. 10.2. 10.3. 10.4. 10.5. 10.6. 10.7. 10.8.

„Demonizowanie” skryptu Wielokrotne wykorzystanie kodu — polecenia include i source Wykorzystanie skryptów konfiguracyjnych w skrypcie Definiowanie funkcji Wykorzystanie funkcji. Parametry i zwracane wartości Przechwytywanie przerwań Zmiana definicji poleceń za pomocą aliasów Pomijanie aliasów i funkcji

233 234 236 238 239 242 245 247

11. Przetwarzanie informacji o dacie i czasie . ............................................................... 251 11.1. 11.2. 11.3. 11.4. 11.5. 11.6. 11.7. 11.8. 11.9.

Formatowanie dat podczas wyświetlania Dostarczanie domyślnej wartości daty Automatyczne generowanie dat z określonego zakresu Przekształcenie daty i czasu w znacznik czasu Przekształcanie znaczników czasu w ciągi dat i czasu Pobranie daty poprzedniego lub kolejnego dnia w języku Perl Obliczanie daty i czasu Obsługa stref czasowych, czasu letniego oraz lat przestępnych Wykorzystanie polecenia date i mechanizmu cron do uruchomienia skryptu w wybranym dniu

252 253 255 257 258 259 260 262 263

12. Skrypty usprawniające pracę użytkownika . ............................................................265 12.1. 12.2.. 12.3. 12.4. 12.5.

Na początek coś łatwego — wyświetlanie myślników Przeglądanie albumu ze zdjęciami Zapis danych w odtwarzaczu MP3 Nagrywanie płyt CD Porównywanie dwóch dokumentów

265 268 273 277 280 Spis treści

|

7

13. Interpretacja danych i podobne zadania . .................................................................283 13.1. 13.2. 13.3. 13.4. 13.5. 13.6. 13.7. 13.8. 13.9. 13.10. 13.11. 13.12. 13.13. 13.14. 13.15. 13.16. 13.17. 13.18. 13.19.

Przetwarzanie parametrów skryptu powłoki Przetwarzanie parametrów z własnymi komunikatami o błędach Interpretacja kodu HTML Zapisywanie danych wynikowych w tablicy Pobieranie danych wynikowych z wykorzystaniem wywołania funkcji Interpretacja tekstu z wykorzystaniem instrukcji read Zapisywanie danych w tablicy za pomocą instrukcji read Liczba mnoga angielskich rzeczowników Przetwarzanie danych znak po znaku Wyczyszczenie drzewa kodu źródłowego w systemie SVN Utworzenie bazy danych MySQL Wyodrębnianie określonych pól listingu danych Modyfikacja określonych pól listingu danych Usuwanie krańcowych znaków odstępu Kompresowanie znaków odstępu Przetwarzanie pól o stałej długości Przetwarzanie plików niezawierających znaków nowego wiersza Zapis pliku danych w formacie CSV Przetwarzanie plików z danymi CSV

283 286 288 290 291 292 293 294 296 297 298 299 302 303 307 309 311 312 314

14. Bezpieczne skrypty powłoki . .................................................................................... 315 14.1. 14.2. 14.3. 14.4. 14.5. 14.6. 14.7. 14.8. 14.9. 14.10. 14.11. 14.12. 14.13. 14.14. 14.15. 14.16. 14.17. 14.18.

8

|

Unikanie częstych problemów związanych z bezpieczeństwem Unikanie spoofingu w pracy interpretera Wyznaczanie bezpiecznej wartości $PATH Usuwanie wszystkich aliasów Czyszczenie tablicy odwzorowań plików wykonywalnych Zapobieganie zrzutom pamięci Wyznaczenie bezpiecznej wartości $IFS Wyznaczanie bezpiecznej wartości umask Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfikowanie zawartości Dodawanie bieżącego katalogu do listy $PATH Bezpieczne pliki tymczasowe Walidacja wprowadzanych danych Definiowanie praw dostępu Ujawnienie haseł na liście procesów Tworzenie skryptów z prawami setuid i setgid Ograniczenie praw konta gościa Wykorzystanie środowiska chroot Wykonywanie skryptu z prawami zwykłego użytkownika

Spis treści

317 318 319 321 321 322 323 324 325 327 328 332 334 335 336 338 340 341

14.19. 14.20. 14.21. 14.22. 14.23.

Bezpieczne wykorzystanie mechanizmu sudo Wykorzystanie haseł w skryptach Wykorzystanie usługi SSH bez hasła Ograniczenie liczby poleceń SSH Rozłączanie nieaktywnych sesji

342 343 345 352 355

15. Zaawansowane mechanizmy skryptowe ..................................................................357 15.1. 15.2. 15.3. 15.4. 15.5. 15.6. 15.7. 15.8. 15.9. 15.10. 15.11. 15.12. 15.13. 15.14. 15.15. 15.16.

Przenośność skryptu — problem wiersza #! Ustawianie zmiennej $PATH zgodnie z zaleceniami POSIX Tworzenie przenośnych skryptów powłoki Testowanie skryptów w środowisku VMware Przenośność kodu pętli Przenośność instrukcji echo Dzielenie danych wyjściowych tylko wtedy, gdy jest to konieczne Przeglądanie danych wynikowych w formacie szesnastkowym Wykorzystanie mechanizmów przekierowania sieciowego Ustalenie własnych adresów IP Pobieranie danych z innego komputera Przekierowanie wyjścia na czas działania skryptu Eliminacja błędów typu „argument list too long” Wysyłanie komunikatów syslog z poziomu skryptu Wysyłanie wiadomości e-mail ze skryptu Automatyzacja zadań z wykorzystaniem podziału procesu na etapy

358 359 361 363 365 366 369 370 371 373 377 379 380 382 383 386

16. Konfiguracja i dostosowanie powłoki bash . ............................................................389 16.1. 16.2. 16.3. 16.4. 16.5. 16.6. 16.7. 16.8. 16.9. 16.10. 16.11. 16.12. 16.13. 16.14. 16.15.

Opcje startowe powłoki bash Dostosowanie znaku zachęty Trwała zmiana wartości $PATH Chwilowa zmiana wartości $PATH Wyznaczanie wartości $CDPATH Skracanie i zmienianie nazw poleceń Dostosowanie domyślnego sposobu działania powłoki i jej środowiska Zmiana sposobu działania mechanizmu readline za pomocą skryptu .inputrc Własny zbiór narzędzi — dodanie ścieżki ~/bin Wykorzystanie dodatkowych znaków zachęty — $PS2, $PS3, $PS4 Synchronizowanie historii poleceń wprowadzanych w różnych sesjach Włączanie opcji związanych z historią poleceń Utworzenie lepszego polecenia cd Utworzenie katalogu i przejście do niego w jednym kroku Przejście do katalogów najniższego poziomu

Spis treści

390 390 398 399 404 406 408 408 410 411 414 415 417 419 421

|

9

16.16. Dodawanie nowych funkcji przez zastosowanie ładowanych poleceń wbudowanych 16.17. Usprawnienie mechanizmu uzupełniania poleceń 16.18. Właściwe wykorzystanie plików startowych 16.19. Tworzenie samodzielnych, przenośnych plików RC 16.20. Uruchomienie powłoki z własną konfiguracją

422 426 431 435 437

17. Zadania administracyjne . ......................................................................................... 449 17.1. 17.2. 17.3. 17.4. 17.5. 17.6. 17.7. 17.8. 17.9. 17.10. 17.11. 17.12. 17.13. 17.14. 17.15. 17.16. 17.17. 17.18. 17.19. 17.20. 17.21. 17.22. 17.23. 17.24.

Zmiana nazwy wielu plików Dokumentacja GNU Texinfo i Info w systemie Linux Rozpakowywanie wielu plików ZIP Przywracanie przerwanych sesji za pomocą narzędzia screen Współdzielenie pojedynczej sesji powłoki Rejestrowanie danych z całej sesji lub zadania wsadowego Czyszczenie ekranu po wylogowaniu Rejestracja metadanych plików w celu ich późniejszego odtworzenia Tworzenie indeksu plików Wykorzystanie poleceń diff i patch Zliczanie różnic między plikami Usuwanie lub zmiana nazwy plików zawierających znaki specjalne w nazwie Dołączanie danych na początku pliku Edycja treści pliku Wykorzystanie mechanizmu sudo w odniesieniu do grupy poleceń Wyszukiwanie wierszy tylko jednego pliku Zachowanie N ostatnich obiektów Filtrowanie wyniku polecenia ps za pomocą instrukcji grep, ale z pominięciem w zestawieniu samej instrukcji grep Sprawdzenie, czy dany proces działa Dodawanie prefiksu lub sufiksu do danych wynikowych Numerowanie wierszy Generowanie sekwencji liczbowych Emulacja instrukcji pause systemu DOS Wyświetlanie separatora tysięcy

449 451 452 453 456 457 459 460 461 462 465 467 468 471 473 475 478 481 481 483 485 487 489 489

18. Mniej pisania — szybsza praca . ................................................................................ 491 18.1. 18.2. 18.3. 18.4. 18.5. 18.6. 18.7. 10

|

Szybkie przechodzenie między określonymi katalogami Powtarzanie ostatniego polecenia Uruchomienie polecenia zbliżonego do wykonywanego wcześniej Podmiana wartości składających się z większej liczby słów Powtórne wykorzystanie parametrów Automatyczne uzupełnianie nazw Bezpieczne działania

Spis treści

492 493 494 495 497 498 498

19. Często popełniane błędy — rozwiązania i podpowiedzi ........................................ 501 19.1. 19.2. 19.3. 19.4. 19.5. 19.6. 19.7. 19.8. 19.9. 19.10. 19.11. 19.12. 19.13. 19.14. 19.15.

Zapominanie o ustawieniu praw wykonywania Usuwanie błędu „Nie ma takiego pliku ani katalogu” Zapominanie o braku bieżącego katalogu w zmiennej $PATH Nadawanie skryptowi nazwy test Spodziewana zmiana eksportowanych wartości Brak cudzysłowów w operacjach przypisania wywołuje błędy „command not found” Wartości pasujące do wzorca są układane w kolejności alfabetycznej Potoki powołują podpowłoki Uzdrawianie terminala Usuwanie plików z użyciem pustej zmiennej Niestandardowe zachowanie instrukcji printf Sprawdzanie składni skryptu powłoki Śledzenie przebiegu skryptów Unikanie komunikatów „command not found” podczas korzystania z funkcji Pomyłki w stosowaniu symboli wieloznacznych powłoki i wyrażeń regularnych

501 502 504 505 506 507 509 509 512 513 514 516 516 518 519

A Podręczna pomoc ....................................................................................................... 521 Wywołanie powłoki bash Dostosowanie znaku zachęty Symbole specjalne ANSI Polecenia wbudowane i słowa kluczowe Wbudowane zmienne powłoki Opcje instrukcji set Opcje instrukcji shopt Zmiana sposobu działania powłoki za pomocą instrukcji set, shopt i zmiennych środowiskowych Operatory wyrażeń warunkowych Przekierowanie strumieni wejścia-wyjścia Opcje i znaki specjalne instrukcji echo Instrukcja printf Formatowanie daty i czasu z wykorzystaniem funkcji strftime Znaki wzorców dopasowywania Operatory rozszerzonego mechanizmu dopasowywania do wzorca extglob Symbole specjalne polecenia tr Składnia pliku konfiguracyjnego mechanizmu readline Polecenia trybu emacs Polecenia sterujące trybu vi Tabela wartości ASCII Spis treści

521 522 522 523 526 530 531 533 545 546 546 548 552 554 555 555 555 558 559 561 |

11

B Przykłady dołączone do oprogramowania bash . .....................................................565 Przykłady zapisane w katalogu startup-files

565

C Przetwarzanie poleceń . ..............................................................................................573 Etapy przetwarzania polecenia

573

D Kontrola wersji . ..........................................................................................................579 CVS Subversion RCS Inne rozwiązania

580 585 591 596

E Kompilacja powłoki bash ...........................................................................................599 Pobranie pakietu bash Rozpakowanie archiwum Co zawiera archiwum? Do kogo się zwrócić?

599 599 600 604

Skorowidz . .............................................................................................................................607

12

|

Spis treści

Przedmowa

Każdy nowoczesny system operacyjny udostępnia przynajmniej jedną powłokę, a część systemów nawet kilka powłok. Niektóre powłoki korzystają z interfejsów tekstowych, tak jak powłoka opisywana w tej książce, inne bazują na interfejsach graficznych — przykładem mogą tu być aplikacje Windows Explorer lub Macintosh Finder. Niektórzy użytkownicy posługują się powłoką tylko w czasie uruchamiania wybranych programów i nie korzystają z niej aż do czasu wylogowania. Jednak większość z nich na pracy z powłoką spędza znaczny czas. W takim przypadku zdobycie dodatkowych informacji na jej temat pozwala na skrócenie czasu realizacji zadań i zwiększenie efektywności pracy. Niezależnie od tego, czy użytkownik systemu jest jego administratorem, programistą, czy po prostu osobą wykorzystującą ten system do wykonywania określonych zadań, z pewnością nie raz znajdzie się w sytuacji, w której nawet prosty skrypt (a niekiedy całkiem skomplikowany) będzie mógł skrócić czas jego pracy i uczynić wynik powtarzalnym i bardziej wiarygodnym. Nawet tak drobna zmiana, jak zdefiniowanie aliasu w celu skrócenia nazwy polecenia, pozwala na istotne zwiększenie efektywności działań. Zagadnienie to wraz z wieloma innymi zostanie omówione w dalszej części książki. Podobnie jak w większości języków programowania, tak w skryptach powłoki każde zadanie można zrealizować różnymi metodami. W wielu przypadkach jest tylko jedno rozwiązanie, które można nazwać najlepszym, ale zazwyczaj opracowanie dwóch lub trzech równie efektywnych i efektownych nie stanowi większego problemu. To, które zostanie wybrane, zależy jedynie od preferencji programisty, jego kreatywności oraz stopnia zaznajomienia z poszczególnymi poleceniami i technikami programistycznymi. Twierdzenie to jest prawdziwe zarówno w odniesieniu do nas, autorów, jak i do wszystkich Czytelników. W większości omawianych w książce przypadków wybieraliśmy jedną metodę, którą później implementowaliśmy. Czasami jednak opisywaliśmy kilka rozwiązań i wyjaśnialiśmy, dlaczego naszym zdaniem jedno jest lepsze od pozostałych. W sporadycznych przypadkach prezentujemy gotowy kod dla wielu rozwiązań, pozostawiając kwestię wyboru odpowiedniego rozwiązania osobom, które będą z niego korzystały w danym środowisku systemowym. Autor skryptu często staje przed dylematem, czy napisać kod w sposób zwięzły, uwzględniający różne sztuczki programistyczne, czy pozostawić go w formie łatwiejszej do przeanalizowania. Prezentując poszczególne rozwiązania, wybieraliśmy drugą wersję — czytelniejszą formę zapisu kodu. Doświadczenie uczy bowiem, że bez względu na to, jak bardzo dany skrypt wydaje się oczywisty w chwili jego pisania, po ośmiu lub osiemnastu miesiącach i dziesięciu kolejnych projektach każdy programista, przeglądając swoje dzieło, zaczyna się zastanawiać, o co w nim chodzi. Opracowanie przejrzystego kodu i sporządzenie dla niego właściwej dokumentacji zawsze procentuje w przyszłości. 13

Dla kogo przeznaczona jest ta książka? Książka ta jest przeznaczona dla każdego, kto posługuje się systemami Unix lub Linux, w tym również dla administratorów systemów, którzy każdego dnia korzystają z wielu platform. Po zapoznaniu się z nią tworzenie skryptów powłoki stanie się znacznie łatwiejsze, a to z kolei zagwarantuje szybszą i łatwiejszą realizację zadań i powtarzalność uzyskiwanych wyników. Dla każdego? Tak. Nowi użytkownicy powłoki docenią szczególnie rozdziały poświęcone automatyzacji wielokrotnie wykonywanych czynności, podstawianiu wartości parametrów i dostosowywaniu środowiska do własnego stylu pracy. Doświadczeni użytkownicy i administratorzy systemów będą mogli poznać nowe sposoby rozwiązywania często występujących problemów. Wszyscy zaawansowani użytkownicy powłoki będą mogli skorzystać ze zbioru skryptów, przygotowanych w taki sposób, aby ich użycie nie wymagało wprowadzania jakichkolwiek dodatkowych instrukcji. Do grona adresatów książki zaliczają się więc: • Nowi użytkownicy systemów Unix i Linux, którzy nie znają zbyt dobrze powłoki, ale nie

chcą poprzestać na przesuwaniu kursora po ekranie i klikaniu.

• Doświadczeni użytkownicy i administratorzy systemów Unix i Linux poszukujący goto-

wych rozwiązań dla problemów związanych z opracowywaniem skryptów powłoki. • Programiści korzystający z systemów Unix i Linux (a także Windows), którzy chcą być bar-

dziej produktywni. • Nowi administratorzy systemu Unix i Linux oraz ci, którzy pracowali w systemie Windows,

chcący szybko zapoznać się z tajnikami nowych rozwiązań. • Doświadczeni użytkownicy i administratorzy systemu Windows, którzy chcą poznać znacz-

nie wydajniejsze środowisko skryptowe. Podstawowe i średnio zaawansowane techniki projektowania skryptów są zasadniczym tematem tej książki. Bardziej szczegółowe omówienie wymienionych zagadnień można znaleźć w publikacjach Camerona Newhama bash. Wprowadzenie (Helion 2006) oraz Nelsona H. F. Beebe’a i Arnolda Robbinsa Programowanie skryptów powłoki (Helion 2005). Intencją autorów książki jest przedstawienie gotowych rozwiązań powszechnie spotykanych problemów bez omawiania teorii, ale z podaniem zasad wykonania zadania. Informacje tutaj zawarte pozwalają na zaoszczędzenie sporej ilości czasu podczas opracowywania kodu i nauki składni instrukcji. Taka struktura książki wynika z tego, że sami autorzy zawsze chcieli zdobyć publikację, która będzie prezentowała praktyczne przykłady rozwiązań, do których w razie potrzeby będzie można się odwołać. Dzięki podobnym opracowaniom nie trzeba pamiętać o subtelnych różnicach między językiem powłoki a językami takimi jak Perl, C itp. Przyjęto założenie, że Czytelnik ma dostęp do systemu Unix lub Linux (lub skorzysta z porad zawartych w recepturze 1.15, „Korzystanie z powłoki bash bez jej pobierania”, lub recepturze 15.4, „Testowanie skryptów w środowisku VMware”) i nie są mu obce operacje logowania, wprowadzania podstawowych poleceń oraz korzystania z edytora tekstowego. Większość receptur nie wymaga pracy z prawami użytkownika root, choć część z nich, szczególnie dotycząca instalacji powłoki bash, nie może być implementowana z prawami zwykłego użytkownika systemu.

14

|

Przedmowa

O książce Niniejsza książka zawiera opis powłoki bash, czyli powłoki Bourne Again Shell projektu GNU będącej jednym z wielu elementów zbioru interpreterów Bourne’a, w skład którego wchodzą również: pierwotna powłoka Bourne’a (sh), powłoka Korn (ksh) oraz otwarta wersja powłoki Korn (pdksh). Inne, mniej znaczące wersje interpreterów (np. dash i zsh) nie zostały tutaj szczegółowo omówione, co nie zmienia faktu, że prezentowane w książce przykłady skryptów w większości są w nich poprawnie wykonywane. Książkę tę warto przeczytać od początku do końca, choć równie dobrze można sobie wybrać jedno zagadnienie, które wydaje się szczególnie interesujące. Założeniem autorów było opracowanie takiej publikacji, po którą każdy może sięgnąć, gdy nasuwają mu się pytania związane z realizowanym zadaniem i gdy potrzebuje szybkiej i precyzyjnej odpowiedzi na dane pytanie, a jednocześnie gwarantującej oszczędność czasu i pracy. Systemowi Unix towarzyszy idea udostępniania prostych narzędzi, które doskonale wykonują swoje zadania i które w razie potrzeby mogą być łączone ze sobą w celu realizacji bardziej złożonych operacji. Współdziałanie poszczególnych komponentów systemu uzyskuje się przez budowanie skryptów powłoki eliminujących konieczność zapamiętywania i wpisywania długich instrukcji zwanych potokami. Większość ze wspomnianych narzędzi została opisana zarówno w charakterze elementów języka skryptowego, jak i komponentów scalających inne programy w celu osiągnięcia określonego rezultatu. Niniejsza książka została napisana w aplikacji OpenOffice.org Writer w systemie Linux i Windows (zależnie od tego, który był uruchomiony), a jej pliki są przechowywane w repozytorium systemu Subversion (więcej informacji na ten temat znajduje się w dodatku D). Format Open Document Format usprawnia wiele prac związanych z przygotowaniem publikacji. Przede wszystkim ze względu na możliwość zachowania zgodności z innymi aplikacjami oraz wyodrębniania kodu (zagadnienie to jest tematem receptury 13.17, „Przetwarzanie plików niezawierających znaków nowego wiersza”).

Oprogramowanie GNU Zarówno powłoka bash, jak i wiele innych narzędzi opisanych w książce to elementy projektu GNU (http://www.gnu.org). Sama nazwa GNU jest rekurencyjnym akronimem od słów GNU’s Not Unix (GNU nie jest Uniksem). Projekt został uruchomiony w 1984 roku i ma na celu opracowanie darmowego systemu operacyjnego o działaniu zbliżonym do systemu Unix. Nie wdając się w szczegółową analizę tematu, można stwierdzić, że system, który powszechnie nazywa się Linuksem, jest w praktyce jądrem uzupełnionym o różne programy użytkowe. Narzędzia GNU stanowią właśnie uzupełnienie dla jądra systemu, choć tę samą funkcję mogą pełnić programy opracowywane w ramach innych projektów. Samo jądro systemu Linux nie jest elementem projektu GNU. Osoby biorące udział w projekcie GNU twierdzą, że system Linux powinien się w nazywać „GNU/Linux”, a ponieważ mają rację, niektóre dystrybucje (przede wszystkim Debian) korzystają z takiej nazwy. W ten sposób cel projektu GNU został osiągnięty, choć z pewnością sukces nie należy tylko do osób związanych z projektem GNU. W ramach projektu GNU powstało wiele niezwykle użytecznych programów, w tym również powłoka bash. Niemal każde opisywane w książce narzędzie ma swój odpowiednik w zbiorze O książce

|

15

rozwiązań GNU. Programy GNU charakteryzują się zazwyczaj znacznie większą liczbą funkcji i opcji niż inne narzędzia, ale czasami również nieznacznie odmiennym sposobem działania. Zagadnienie to zostało omówione w recepturze 15.3, „Tworzenie przenośnych skryptów powłoki”. Winę za różnice ponoszą także dostawcy komercyjnych wersji systemu Unix, opracowywanych głównie w latach 80. i 90. ubiegłego wieku. Problemy odmienności oprogramowania GNU, Unix i Linux są opisywane w wielu publikacjach i nie będą tematem receptur tej książki. Uznaliśmy jednak, że warto zamieścić krótką informację wyjaśniającą to zagadnienie. Więcej informacji na ten temat można znaleźć na stronie http://www.gnu.org.

Uwagi na temat przykładów kodu Opublikowane w książce fragmenty kodu, które można uruchomić w powłoce, są prezentowane w następujący sposób: $ ls a.out $

cong.txt

def.conf

file.txt

more.txt

zebra.list

Pierwszym znakiem jest często znak dolara ($) informujący, że instrukcja jest wprowadzana w wierszu poleceń powłoki bash (znak zachęty można zmieniać dowolnie, o czym informuje receptura 16.2, „Dostosowanie znaku zachęty”). Znak zachęty jest wyświetlany automatycznie przez powłokę. Do wprowadzenia pozostaje więc pozostała część wiersza. Analogicznie ostatni wiersz listingu często składa się z pojedynczego znaku dolara, który informuje, że wykonywane polecenie się zakończyło i użytkownik odzyskał możliwość wprowadzania kolejnych instrukcji. Nieco więcej problemów związanych jest z właściwą interpretacją znaku (#). W wielu plikach systemów Unix i Linux, także w skryptach powłoki bash, symbol # oznacza początek komentarza. W takim znaczeniu jest również wykorzystywany w publikowanych listingach skryptów. Jednak wyświetlony jako znak powłoki (zamiast znaku $) oznacza, że dany użytkownik pracuje z prawami użytkownika root. Tylko jeden spośród prezentowanych przykładów wymaga użycia konta root, więc różnica znaczeniowa nie powinna być uciążliwa w czasie czytania książki. Warto jednak o niej pamiętać. Listingi, które nie zawierają znaku zachęty, prezentują zawartość skryptu powłoki. W niektórych przypadkach są poprzedzone instrukcją cat i można wówczas w jej wierszu zauważyć nazwę pliku (skryptu lub danych) wykorzystanego do wykonania omawianego zadania. $ cat plik_danych statyczny nagłówek – wiersz 1. statyczny nagłówek – wiersz 2. 1 jakaś 2 inna 3 treść

Wiele spośród omawianych skryptów i funkcji zostało udostępnionych do pobrania. Szczegółowe informacje na ten temat znajdują się w końcowej części „Przedmowy”. Pierwszy wiersz takich skryptów zawiera definicję #!/usr/bin/env bash. Gwarantuje ona większą przenośność kodu, niż występujące często w skryptach systemów Linux i Mac definicje #!/bin/bash. Zagadnienie to zostało szczegółowo opisane w recepturze 15.1, „Przenośność skryptu — problem wiersza #!”. W wielu przykładach kodu można zauważyć następujący komentarz: # plik receptury: nazwa_pliku

16

|

Przedmowa

Oznacza on, że dany skrypt został udostępniony na serwerze FTP wydawnictwa Helion. Po pobraniu i rozpakowaniu pliku archiwum (.tgz lub .zip) plik skryptu zostanie zapisany pod nazwą ./rXX/nazw_pliku, gdzie XX oznacza numer rozdziału, a ciąg nazwa_pliku jest nazwą danego skryptu.

Zbędne użycie polecenia cat Niektórzy użytkownicy systemu Unix czerpią satysfakcję z możliwości wykazania nieefektywności kodu napisanego przez kogoś innego. Zazwyczaj takie uwagi są uznawane za konstruktywną krytykę i przyjmowane z wdzięcznością. W konkursie na przykład najczęstszego niepotrzebnego użycia polecenia cat wygrałaby z pewnością instrukcja cat plik | grep coś. W tym przypadku polecenie grep coś plik w zupełności wystarcza. Uwzględnianie polecenia cat jest bezzasadne i wnosi dodatkowe obciążenie systemu, ponieważ wymaga uruchomienia podpowłoki. Innym podobnym przykładem jest użycie instrukcji cat plik | tr '[A-Z]' '[a-z]' zamiast tr '[A-Z]' '[a-z]' < plik. Niekiedy zastosowanie polecenia cat może być przyczyną błędu w wykonaniu skryptu (problem ten został omówiony w recepturze 19.8, „Potoki powołują podpowłoki”). Jednak w pewnych okolicznościach nadmiarowe wykorzystanie polecenia cat jest uzasadnione. Może ono bowiem pełnić funkcję instrukcji tymczasowej, demonstrującej działanie potoku, która zostanie później zastąpiona przez inne polecenia (choćby nawet przez polecenie cat –n). Często też dzięki niej nazwa pliku pozostaje z lewej strony listingu kodu, gdzie jest łatwiej zauważalna niż w przypadku zapisania jej za operatorem < (po prawej stronie listingu). Choć popieramy dążenie do uczynienie kodu jak najbardziej wydajnym, nie uważamy, że należy ten cel osiągnąć za wszelką cenę. Oczywiście nie usprawiedliwiamy bezzasadnego spowolniania skryptu, ale chcemy zwrócić uwagę na fakt, że procesory nie będą już nigdy wolniejsze. Zatem jeśli ktoś lubi korzystać z instrukcji cat, nie powinien zmieniać swojego przyzwyczajenia.

Uwaga na temat języka Perl Podjęliśmy świadomą decyzję o nieużywaniu kodu Perl w prezentowanych rozwiązaniach, o ile istnieje możliwość wykonania zadania w inny sposób. Wyjątkiem jest kilka receptur, w których zastosowanie instrukcji Perl okazało się zasadne. Sam język Perl jest szczegółowo opisywany w wielu innych publikacjach i nie mógłby tutaj zostać choćby w części przedstawiony. Opracowywane za jego pomocą aplikacje są znacznie bardziej złożone i pochłaniają znacznie więcej zasobów systemowych niż rozwiązania bazujące na skryptach powłoki. Poza tym ta książka dotyczy programowania w języku powłoki. Skrypty powłoki są pewnego rodzaju spoiwem między różnymi programami systemu Unix. Aplikacje Perl natomiast zawierają w sobie kod rozwiązań właściwy dla zewnętrznych programów systemu Unix. Dzięki temu są w niektórych przypadkach bardziej wydajne i przenośne, ale równocześnie znacznie mniej efektywne podczas uruchamiania zewnętrznych programów, które i tak są niezbędne do realizacji zadania. Wybór odpowiedniego rozwiązania często wynika z preferencji użytkownika. Ostatecznie zawsze dążmy do wykonania zadania, a dobór środków jest kwestią drugorzędną. Kolejne receptury książki zawierają opisy wielu użytecznych mechanizmów bazujących na powłoce bash i związanych z nią narzędziach. Gdy pojawia się konieczność realizacji określonego zadania, osoba nim obarczona musi zdecydować, które rozwiązanie wykorzysta. O książce

|

17

Inne źródła informacji • Książka Nathana Torkingtona i Toma Christiansena Perl. Receptury (Helion 2004). • Książka pod redakcją Larry’ego Walla Programming Perl (O’Reilly 2000). • Książka Damiana Conwaya Perl. Najlepsze rozwiązania (Helion 2006). • Książka Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001). • Książka Camerona Newhama bash. Wprowadzenie (Helion 2006). • Książka Nelsona H.F. Beebe’a i Arnolda Robbinsa Programowanie skryptów powłoki (Helion

2005).

Konwencje typograficzne W książce zastosowano następujące konwencje typograficzne: Czcionka pochyła Jest stosowana do zapisu nazw plików i katalogów, opcji menu, oznaczeń klawiszy, adresów poczty elektronicznej i adresów URL, a także pojęć definiowanych po raz pierwszy. Czcionka o stałej szerokości

Jest stosowana w listingach kodów oraz w przypadku słów kluczowych, nazw zmiennych, atrybutów, funkcji, opcji poleceń, parametrów poleceń, a także znaczników HTML. Pogrubiona czcionka o stałej szerokości

Jest stosowana do zapisu danych wprowadzanych przez użytkownika. Pochyła czcionka o stałej szerokości

Jest stosowana do oznacza fragmentów, które powinny zostać zastąpione danymi wprowadzanymi przez użytkownika. Ta ikona symbolizuje podpowiedź, sugestię lub ogólną uwagę.

Ta ikona symbolizuje ostrzeżenie.

Wykorzystanie przykładów kodu Celem niniejszej książki jest ułatwienie Czytelnikowi wykonywania jego obowiązków. Prezentowany kod można zatem wykorzystywać zarówno we własnych programach, jak i w dokumentacji do nich. Nie ma potrzeby występowania o zgodę autorów, chyba że znaczne fragmenty kodu będą wykorzystywane w dystrybuowanych przez Czytelnika produktach. Na przykład wykorzystanie kilku przykładowych skryptów w opracowywanej aplikacji nie wymaga pozwolenia autorów, ale sprzedawanie lub udostępnianie płyt CD z przykładami pochodzącymi

18

|

Przedmowa

z książki już takiego pozwolenia wymaga. Odpowiadanie na pytanie przez cytowanie fragmentów książki i prezentowanie zawartych w nich skryptów nie wymaga pozwolenia autorów, ale włączanie obszernych fragmentów kodu do dokumentacji produktu nie jest dozwolone bez takiej zgody. Będziemy wdzięczni za zamieszczenie odsyłacza do książki, ale nie wymagamy jego dołączenia. Odsyłacz zazwyczaj obejmuje tytuł publikacji, nazwiska autorów, nazwę wydawnictwa oraz numer ISBN — na przykład: bash. Receptury, Carl Albing, J.P. Vossen, Cameron Newham, Helion, ISBN 83-246-1378-1. W przypadku jakichkolwiek wątpliwości, czy sposób wykorzystania kodu wykracza poza opisane ramy, prosimy o kontakt za pośrednictwem poczty elektronicznej — [email protected].

Podziękowania Dziękujemy fundacji GNU Software Foundation i Brianowi Foksowi za napisanie basha. Dziękujemy również Chetowi Rameyowi za utrzymywanie i udoskonalanie oprogramowania bash od czasu wydania wersji 1.14, czyli od połowy lat 90. Szczególne podziękowania należą się Chetowi za udzielanie odpowiedzi na nasze pytania i zainteresowanie wstępną wersją książki.

Recenzenci Składamy podziękowania dla recenzentów książki, wśród których znaleźli się: Yves Eynard, Chet Ramey, William Shotts, Ryan Waldron i Michael Wang. Wszystkie wymienione osoby przekazały nam wiele cennych wskazówek, a niekiedy również przykładów alternatywnych rozwiązań. One również wskazały nam przeoczone błędy, podnosząc w ten sposób jakość publikacji. Ewentualne błędy i niedociągnięcia w tekście są tylko naszą winą, a nie ich. Doskonałym przykładem nieocenionego wkładu wymienionych osób może tu być trafne stwierdzenie jednego z recenzentów: „To zdanie nie może się zdecydować, co chce wyrazić”.

O’Reilly Dziękujemy całemu zespołowi wydawnictwa O’Reilly, a szczególnie Mike’owi Loukidesowi, Derekowi Di Mateo oraz Laurelowi Rumie.

Od autorów Carl Pisanie książki nigdy nie jest przedsięwzięciem jednoosobowym, choć zdarzały się momenty, że takim się stawało. Dziękuję J.P. i Cameronowi za wspólną pracę nad tym projektem. Nasze uzupełniające się talenty i terminarz spotkań pozwoliły uczynić tę książkę lepszą, niż mogłaby być, gdybyśmy pracowali oddzielnie. Dziękują również J.P. za duży wysiłek włożony w pracę administratora, który zapewnił nam potrzebną infrastrukturę. Dziękuję Mike’owi za wysłuchanie propozycji napisania książki bash. Receptury i za skontaktowanie mnie z J.P. i Cameronem, którzy mieli taki sam pomysł. Dziękuję

Podziękowania

|

19

również za wspieranie nas w trudnych momentach i pohamowywanie w chwilach szaleństwa. Opanowanie Mike’a i jego doradztwo techniczne były nieocenione. W czasie pisania książki byłem nieustannie wspierany przez żonę i dzieci, które dawały mi motywację i przestrzeń do pracy. Dziękuję im za to z całego serca. Jeszcze trudniejszym zadaniem niż napisanie książki było przygotowanie się do niej i poznanie opisywanych w niej zagadnień. Jestem wdzięczny dr Ralphowi Jorkowi za umożliwienie mi rozpoczęcia pracy z systemem Unix w czasie, w którym prawie nikt o takim systemie nie słyszał. Jego wizja przyszłości i udzielone rady procentowały przez czas znacznie dłuższy, niż początkowo sądziłem. Pracę nad tą książką dedykuję Rodzicom, Hankowi i Betty, którzy dali mi wszystko, co mieli najlepszego — życie, wiarę chrześcijańską, miłość, doskonałą edukację, poczucie przynależności i wszystkie dobre rzeczy, które każdy chciałby przekazać swoim dzieciom. Nie ma słów, którymi mógłbym wyrazić za to swoją wdzięczność.

J.P. Dziękuję Cameronowi za napisanie książki bash. Wprowadzenie, z której dowiedziałem się wielu ciekawych rzeczy, która była moim podstawowym źródłem informacji, zanim przystąpiłem do tego projektu, i w której można było znaleźć tak wiele użytecznych rozwiązań. Dziękuję Carlowi za wkład, bez którego praca nad niniejszą książką byłaby co najmniej cztery razy dłuższa i w połowie tak wartościowa. Dziękuję Mike’owi za nadzorowanie projektu oraz za dołączenie Carla do zespołu. Wspólne podziękowania składam Carlowi i Mike’owi za cierpliwość i zrozumienie mojej organizacji czasu i życia. Książkę dedykuję Tacie, który z pewnością by ją wyrzucił. Zawsze powtarzał mi, że w życiu liczą się tylko dwie rzeczy — co robisz i z kim się ożenisz. Udało mi się dobrze rozwiązać obydwa problemy. Książka jest więc również dedykowana Karen, w podziękowaniu za jej nieocenione wsparcie, cierpliwość i zrozumienie okazywane w czasie przedłużającego się procesu realizacji zadania. Bez niej komputery nie dostarczałyby mi tyle radości. Na końcu dziękuję Kate i Samowi za istotny wkład w rozwiązanie wspomnianych problemów z „zarządzaniem” życiem.

Cameron Chcę podziękować J.P. i Carlowi za ich doskonałą pracę, bez której ta książka prawdopodobnie by nie powstała. Chciałbym również podziękować J.P. za pomysł na opracowanie publikacji o bashu w formie receptur. Jestem pewien, że żałował podjętej decyzji w czasie długich godzin spędzonych nad klawiaturą, ale jednocześnie nie mam wątpliwości, że po wydaniu książki jest zadowolony z tego, że brał udział w jej tworzeniu. Na koniec po raz kolejny chciałbym podziękować Adamowi.

20

|

Przedmowa

ROZDZIAŁ 1.

Wprowadzenie do pracy z powłoką bash

Czym jest powłoka systemowa i dlaczego warto się nią zainteresować? Wszystkie aktualne systemy operacyjne (przez aktualne należy rozumieć systemy wydane po roku 1970) są wyposażone w pewien interfejs użytkownika, czyli r�iązanie umożliwiające wprowadzanie dowolnych poleceń, które są następnie wykonywa�� system operacyjny. W wielu systemach operacyjnych wspomniany interfejs def "ni wan��oleceń jest na stałe zin­ tegrowany z systemem i stanowi jedyny mechanizm wymian · �g1 między użytkownikiem a komputerem. W praktyce jednak jego działanie ogranicz� ynie do umożliwienia użyt­ . kownikowi wykonania polecenia. W końcu jakie jeszcz Vla miałby realizować?





Twórcy systemu operacyjnego Unix spopularyzo��ę oddzielenia powloki (modułu umoż­ liwiającego wprowadzanie poleceń) od pozostały� �entów platformy-systemu wejścia­ -wyjścia, harmonogramu zadań, mechanizmów��zania pamięcią i wszystkich innych kom­ ponentów, którymi system operacyjny za z d� imieniu użytkownika (i o których większość użytkowników woli nie myśleć). Powłoka ta się po prostu kolejnym programem-progra­ mem, którego zadaniem było urucham nnych programów na zlecenie użytkowników.





er

To jednak był dopiero początek r.f._�olwcji. Z uwagi na fakt, że powłoka funkcjonowała jako kolejny program systemu U i �, kto nie chciał korzystać ze standardowej powłoki sys­ temowej, mógł napisać włas zięki temu pod koniec pierwszej dekady obecności Uniksa na rynku dostępne były dw.1 o urencyjne rozwiązania-Bourne Shell, sh (będące bezpośred­ włoki Thomson) oraz C Shell, csh. Pod koniec drugiego dziesię­ nim następcą oryginal ciolecia Uniksa można było skorzystać z kilku dodatkowych programów-powłoki Korn (ksh) oraz pierwszych wersji powłoki bash. Pod koniec trzeciej dekady liczba różnych powłok była już z pewnością znacznie większa.

i�

Dzisiaj prawdopodobnie nikt nie zadaje sobie pytania, czy powinien używać powłok csh, bash, czy ksh. Użytkownicy korzystają ze standardowych powłok systemu Linux (albo BSD, Mac OS X, Solaris lub HP /UX). Niemniej oddzielenie powłoki od systemu operacyjnego umożliwia programistom (takim jak Brian Fox, twórca basha, i Chet Ramey, programista i administrator aktualnych wersji basha) opracowywanie kolejnych rozwiązań i udoskonalanie aktualnie wyko­ rzystywanych. Można bowiem utworzyć nową powłokę bez konieczności modyfikowania samego systemu operacyjnego. Wyeliminowany został więc problem konieczności uzyskania aprobaty dostawcy systemu operacyjnego, który musiałby wkompilować powłokę w jądro sys­ temu. Wystarczy przygotować pakiet powłoki, który będzie instalowany w taki sam sposób, jak każdy inny program.

21

Nadal jednak za uzasadnione można uznać twierdzenie, że jak na komponent odpowiedzialny jedynie za wykonywanie poleceń, sprawia sporo kłopotu. Jest w tym trochę prawdy-powloka, która umożliwia jedynie wprowadzanie poleceń, nie jest szczególnie interesująca. W rzeczywistości cią­ gły rozwój powłok systemu Unix wynika z dwóch nieustannie doskonalonych aspektów ich funkcjonowania-zapewnienia odpowiedniego komfortu pracy użytkownika i udostępniania rozwiązań programistycznych. Możliwości nowoczesnych powłok nie ograniczają się jedynie do akceptowanie poleceń. Nowoczesne powłoki charakteryzują się niezwykłą łatwością obsługi. Na przykład zapamiętują wcześniejsze polecenia i pozwalają na powtórne ich wykorzystanie. Umożliwiają również defi­ niowanie własnych skrótów poleceń, skrótów klawiaturowych itp. Dla doświadczonego użyt­ kownika wprowadzanie poleceń (z wykorzystaniem skrótów i mechanizmów uzupełniania instrukcji) jest znacznie efektywniejsze niż przenoszenie elementów w dziwacznym interfej­ sie okienkowym. Poza wygodną obsługą powłoki gwarantują dostępność mechanizmów programistycznych. Każdy z nas często musi wielokrotnie ponawiać określoną sekwencję poleceń. Dlatego już pod­ czas drugiego wpisywania tych samych instrukcji warto sobie zadać �anie, czy nie można by napisać programu, który zrobiłby to za nas. Oczywiście, że jest to ·� · e. Powłoka to także język programowania, który został opracowany specjalnie z my o onywaniu poleceń sys­ temowych. Jeśli więc trzeba przekształcić kilkaset plików 1ki MP3, korzystniejsze będzie napisanie programu powłoki (czyli skryptu powloki). ��resowanie wszystkich plików dziennika systemowego również wydaje się doskona�� �aniem dla skryptu. Wszystkie czynności, które są wykonywane z określoną czę � i ią, można zautomatyzować przez utworzenie skryptu powłoki. Oczywiście, dostępn nież znacznie efektywniejsze języki programowania, na przykład Perl, Python czy r(tit\r, ale powłoka uniksowa (niezależnie od konkretnej wersji) wydaje się dobrym ro wi �� na początek. W końcu wszyscy wiemy, w jaki sposób należy wprowadzać polece ·, ego więc nie mielibyśmy skomplikować nieco tych operacji?



� �VP



Dlaczego bash?



��Y

�� ��

0 •

Dlaczego niniejsza książka zn� poświęcona powłoce bash, a nie innemu rozwiązaniu? Wynika to z faktu, że bash jest i st w niemal każdym systemie operacyjnym. Może nie jest naj­ nowszym programem 1 czególnie atrakcyjnym wizualnie lub wyjątkowo wszechstronnym (choć z pewnością należy o grupy najbardziej wszechstronnych). Nie jest również jedynym roz­ wiązaniem rozpowszechnianym zgodnie z ideą otwartego kodu. Jest jednak bardzo popularny. Przyczyna takiego stanu rzeczy tkwi w przeszłości pakietu. Pierwsze powłoki były użytecz­ nymi narzędziami programistycznymi, ale niezbyt wygodnymi dla użytkowników. W powłoce C (C Shell) zwiększono komfort pracy (dodano między innymi możliwość ponawiania poleceń), jednak zastosowany język programowania nie był szczególnie przyjazny użytkownikowi. Wydana na początku lat 80. powłoka Kom zawierała wiele ułatwień w korzystaniu z programu i uspraw­ nień programistycznych. Wydawało się więc, że zostanie powszechnie zaakceptowana. Niestety początkowo powłoka ksh nie była udostępniana jako oprogramowanie typu open source, co znacznie utrudniało dołączanie jej do darmowych wersji systemu operacyjnego Linux (licencja powłoki Korn została zmieniona w roku 2000 i ponownie w 2005).

22

Rozdział 1. Wprowadzenie do pracy z powłoką bash

W końcówce lat 80. społeczność skupiona wokół systemu Unix uznała za celowe zestandary­ zowanie oprogramowania. Powołano więc grupę roboczą POSIX (sformowaną przez organi­ zację IEEE). Jej zadanie polegało na zestandaryzowaniu uniksowych bibliotek i narzędzi, w tym również powłoki. Początkowo standardowa powłoka bazowała na programie Kron Shell z 1988 roku uzupełnionym o kilka rozwiązań powłoki C i na kilku nowych pomysłach eliminujących zauważone braki. Prace nad oprogramowaniem bash rozpoczęły się jako element projektu GNU, zmierzającego do opracowania pełnego systemu POSIX, którego składnikiem naturalnie musiała być powłoka zgodna ze standardem POSIX. Powłoka bash zapewniała wszystkie rozwiązania programistyczne, których potrzebowali pro­ gramiści, oraz wszystkie lubiane przez użytkowników mechanizmy ułatwiające posługiwanie się nią. Początkowo była postrzegana jako alternatywa dla powłoki Korn. Jednak z upowszech­ nieniem się idei wolnego oprogramowania i wzrostem zainteresowania systemem Linux, po­ włoka bash szybko zyskała znacznie większą popularność niż ksh. W rezultacie oprogramowanie bash jest domyślną powłoką użytkownika we wszystkich zna­ nych nam dystrybucjach systemu Linux (ponieważ jednak istnieje kilkaset dystrybucji Linuksa, można przypuszczać, że pewne z nich bazują na innych powłokach)�az Mac OS X. Jest rów­ nież dostępna dla wszystkich innych systemów operacyjnych Unix, �·ąc w to również BSD Unix i Solaris. W rzadko spotykanych przypadkach, w który�op amowanie bash nie jest rozpowszechniane wraz z systemem operacyjnym, można je eb emów zainstalować. Po­ włoka ta jest dostępna nawet dla systemu Windows (za po� twem pakietu Cygwin). Sta­ nowi bowiem wszechstronny język programowania ora���ny interfejs użytkownika, który nie wymaga stosowania skomplikowanych skrótów k �rowych do przygotowywania roz"-. budowanych funkcji programistycznych.



,� �

!f"""� Czas poświęcony na zapoznanie się z zasada� Q ania powłoki bash z pewnością nie będzie



czasem straconym. Obecnie większość dom�ch powłok systemowych stanowi bowiem oryginalna powłoka Bourne'a oraz bash jest zgodna z powłoką Bourne'a). W każdym nowoczesnym systemie Unix lub syst�i� zgodnym z Uniksem z pewnością dostępna jest przynajmniej jedna z nich. Poza Y zg �ie z wcześniejszymi informacjami, jeśli nawet żadna z wymienionych powłok nie jest rc!zana wraz z systemem operacyjnym, można ją w łatwy sposób zainstalować. Trzeba -'l._" plik_wyjściowy

51

pozwala na odczytanie danych z pliku_wejściowego i zapisanie wyniku w pliku_wyjściowym. Jeżeli ciąg > plik_wyjściowy zostanie pominięty, dane zostaną przekazane do okna terminala. Jeżeli nie zostanie zdefiniowany człon < plik_wejściowy, program pobierze informacje z klawiatury. Program nie dysponuje informacjami o tym, dokąd przekazywany jest wynik jego działania oraz skąd pochodzą dostarczane dane. Dane wyjściowe można przekazać do dowolnego komponentu systemu (również do innego programu) dzięki mechanizmowi przekierowania zaimplementowanemu w powłoce bash. W dalszej części rozdziału zostaną zaprezentowane różne sposoby generowania danych wyjściowych oraz różne mechanizmy powłoki odpowiedzialne za przekazywanie tych danych w różne miejsca.

2.1. Przekazywanie danych wyjściowych do okna terminala Problem Chcemy za pomocą polecenia powłoki wyświetlić dane na ekranie.

Rozwiązanie Należy zastosować wbudowaną instrukcję echo. Wszystkie parametry polecenia zostaną wyświetlone na ekranie. Na przykład instrukcja: echo Proszę czekać.

spowoduje wyświetlenie tekstu: Proszę czekać.

co można zaobserwować, wprowadzając powyższą instrukcję w wierszu poleceń powłoki bash (o aktywnej sesji bash świadczy znak $): $ echo Proszę czekać. Proszę czekać. $

Analiza Polecenie echo jest jednym z najmniej skomplikowanych poleceń spośród wszystkich instrukcji powłoki bash. Jego zadanie sprowadza się do wyświetlenia na ekranie wszystkich parametrów wywołania. Korzystając z niego, trzeba jednak pamiętać o kilku rzeczach. Po pierwsze powłoka interpretuje wszystkie parametry dołączone do instrukcji echo (podobnie jak w przypadku wszystkich innych poleceń). Oznacza to, że podstawia wartości zmiennych, dopasowuje symbole wieloznaczne oraz wykonuje inne operacje na danych przed przekazaniem ich do samego polecenia echo. Po drugie, traktowanie tekstu jako parametrów powoduje, że ignorowane są wszystkie występujące między wyrazami znaki odstępu. Oto przykład: $ echo to jest tekst z wieloma to jest tekst z wieloma znakami spacji $

52

|

Rozdział 2. Standardowy strumień wyjściowy

znakami

spacji

Zazwyczaj elastyczność powłoki w odniesieniu do znaków odstępu występujących między parametrami jest bardzo pomocna. Jednak w przypadku instrukcji echo może być myląca.

Zobacz również • Polecenie help echo. • Polecenie help printf. • Receptura 2.3, „Formatowanie danych wyjściowych”. • Receptura 19.1, „Zapominanie o ustawieniu praw wykonywania”. • Punkt „Opcje i znaki specjalne instrukcji echo” w dodatku A. • Punkt „Instrukcja printf” w dodatku A.

2.2. Wyświetlanie tekstu z zachowaniem znaków spacji Problem Chcemy zachować w ciągu wyjściowym pierwotne znaki spacji.

Rozwiązanie Ciąg tekstowy należy otoczyć znakami cudzysłowu lub apostrofu. Dodanie znaków cudzysłowu lub apostrofu do przykładu z poprzedniej receptury spowoduje zachowanie zdefiniowanych odstępów między wyrazami: $ echo "to jest tekst to jest tekst z $

z wieloma znakami wieloma znakami spacji

spacji"

lub: $ 'to jest tekst to jest tekst $

z z

wieloma znakami wieloma znakami

spacji' spacji

Analiza Umieszczenie wyrazów między znakami cudzysłowu lub apostrofu powoduje, że są one traktowane jako jeden parametr polecenia. Parametrem jest wówczas ciąg tekstowy, którego powłoka nie musi analizować. W praktyce zastosowanie znaków ('') stanowi dla powłoki informację, że nie należy w ogóle interpretować danego ciągu tekstowego. W przypadku wykorzystania znaków cudzysłowu (") wykonane zostaną niektóre operacje podstawienia wartości (zmiennych, tyldy czy poleceń). W przedstawionym przykładzie takie podstawienia nie zostały zdefiniowane, więc powłoka nie zmienia treści parametru. W przypadku jakichkolwiek wątpliwości, zawsze można zastosować znaki apostrofu.

2.2. Wyświetlanie tekstu z zachowaniem znaków spacji

|

53

Zobacz również • Polecenie help echo. • Polecenie help printf. • Więcej informacji na temat podstawiania wartości zostało zamieszczonych w rozdziale 5. • Receptura 2.3, „Formatowanie danych wyjściowych”. • Receptura 15.6, „Przenośność instrukcji echo”. • Receptura 19.11, „Niestandardowe zachowanie instrukcji printf”. • Punkt „Opcje i znaki specjalne instrukcji echo” w dodatku A.

2.3. Formatowanie danych wyjściowych Problem Chcemy mieć większy wpływ na formatowanie i rozmieszczenie poszczególnych informacji w zestawieniu wynikowym.

Rozwiązanie Należy wykorzystać wbudowane polecenie printf. Zgodnie z poniższym przykładem: $ printf '%s = %d\n' Wierszy $LINES Wierszy = 24 $

lub $ printf '%-20.20s = %4.2f GHz\n' 'Częstotliwość' 1.92735 Częstotliwość = 1.93 GHz $

Analiza Wbudowane polecenie printf działa w taki sam sposób, jak instrukcja o tej samej nazwie w języku C. Pierwszy parametr odpowiada ciągowi formatu. Natomiast kolejne parametry są formatowane zgodnie z definicją formatowania (%). Wartości liczbowe występujące między znakiem % a specyfikatorem formatu (w przedstawionych przykładach są to znaki s, f i d) określają dodatkowe opcje formatowania. W przypadku specyfikatora typu zmiennopozycyjnego (f) pierwsza liczba (4 w zapisie 4.2) wyznacza długość całego pola. Druga wartość (2) określa liczbę cyfr, które powinny zostać wyświetlone po prawej stronie znaku przecinka. Warto zauważyć, że wynik został odpowiednio zaokrąglony. W przypadku ciągów tekstowych pierwsza liczba definiuje maksymalną długość pola danych, natomiast druga — minimalną długość tego pola. Ciąg zostanie obcięty (jeśli będzie dłuższy niż wynika z maksymalnego rozmiaru pola) lub uzupełniony o znaki spacji (jeśli składa się z mniej-

54

|

Rozdział 2. Standardowy strumień wyjściowy

szej liczby znaków, niż to wynika z definicji minimalnej długości). Jeżeli minimalna i maksymalna długość pola mają taką samą wartość, ciąg tekstowy na pewno będzie miał dokładnie taką długość, jaka została wyznaczona za pomocą tych wartości. Początkowy znak wartości ujemnej oznacza konieczność wyrównania ciągu do lewej strony (w obrębie pola). Brak tego znaku spowodowałby wyrównanie tekstu do prawej krawędzi pola. $ printf '%20.20s = %4.2f GHz\n' 'Częstotliwość' 1.92735 Częstotliwość = 1.93 GHz $

Tekstowy parametr może być otoczony znakami apostrofu lub nie. Chcąc zachować pierwotne znaki spacji (w prezentowanym jednowyrazowym ciągu nie były potrzebne żadne znaki spacji), należałoby uwzględnić znaki apostrofu. Podobnie trzeba ich użyć, jeśli konieczne jest zachowanie określonego znaczenia symboli specjalnych (w przedstawionym przykładzie nie było również tego typu znaków). Warto wypracować sobie nawyk otaczania znakami cudzysłowu lub apostrofu wszystkich ciągów tekstowych przekazywanych do instrukcji printf. Można w ten sposób wyeliminować ryzyko pominięcia ich, gdy będą potrzebne.

Zobacz również • Polecenie help printf. • http://www.opengroup.org/onlinepubs/009695399/functions/printf.html. • Książka Camerona Newhama bash. Wprowadzenie (Helion 2006) lub dowolna dokumentacja

funkcji printf języka C. • Receptura 15.6, „Przenośność instrukcji echo”. • Receptura 19.11, „Niestandardowe zachowanie instrukcji printf”. • Punkt „Instrukcja printf” w dodatku A.

2.4. Wyświetlanie wyniku bez znaku nowego wiersza Problem Chcemy wygenerować dane wyjściowe bez znaku nowego wiersza, który jest automatycznie dołączany przez polecenie echo.

Rozwiązanie Zadanie można ławo wykonać za pomocą instrukcji printf. Wystarczy usunąć końcowy symbol \n z ciągu formatu. W przypadku korzystania z polecenia echo trzeba do jego wywołania dodać opcję –n. $ printf "%s %s" znak zachęty znak zachęty$

lub $ echo -n znak zachęty znak zachęty$

2.4. Wyświetlanie wyniku bez znaku nowego wiersza

|

55

Analiza Z uwagi na brak symbolu nowego wiersza na końcu ciągu formatującego instrukcji printf (pierwszy parametr instrukcji), znak zachęty ($) został wyświetlony zaraz za tekstem wygenerowanym przez polecenie printf. Rozwiązanie to jest bardziej przydatne podczas pisania skryptów powłoki, które składają jeden wiersz listingu wynikowego, wykonując kilka niezależnych instrukcji, lub wyświetlają monit o wprowadzenie danych przez użytkownika. W przypadku korzystania z polecenia echo znak nowego wiersza można wyeliminować dwoma metodami. Pierwsza z nich polega na dodaniu opcji –n, która zapobiega automatycznemu dołączeniu znaku nowego wiersza. Instrukcja echo pozwala również na użycie pewnych symboli specjalnych, których znaczenie jest zbliżone do symboli specjalnych ciągów tekstowych języka C (np. symbol \n oznacza nowy wiersz). Aby wykorzystać wspomniane symbole specjalne, trzeba do instrukcji echo dodać opcję –e. Jednym z symboli jest \c. Nie powoduje on wyświetlenia jakiegokolwiek znaku na ekranie, ale uniemożliwia wstawienie końcowego znaku nowego wiersza. Kolejne rozwiązanie problemu polegałoby więc na zastosowaniu następującej instrukcji: $ echo –e 'cześć\c' cześć$

Ze względu na wszechstronność i elastyczność instrukcji printf, a także z uwagi na niewielki narzut związany z jej wywołaniem (co wynika z tego, że jest to polecenie wbudowane; mimo że w starszych wersjach interpretera bash i innych powłokach polecenie printf było niezależnym programem), instrukcja ta będzie wykorzystywana w wielu skryptach prezentowanych w dalszej części książki.

Zobacz również • Polecenie help echo. • Polecenie help printf. • http://www.opengroup.org/onlinepubs/009695399/functions/printf.html. • Receptura 2.3, „Formatowanie danych wyjściowych”. • Receptura 15.6, „Przenośność instrukcji echo”. • Receptura 19.11, „Niestandardowe zachowanie instrukcji printf”. • Punkt „Opcje i znaki specjalne instrukcji echo” w dodatku A. • Punkt „Instrukcja printf” w dodatku A.

2.5. Zapisywanie danych wyjściowych polecenia Problem Chcemy zachować wynik wykonania polecenia, zapisując go w pliku.

56

|

Rozdział 2. Standardowy strumień wyjściowy

Rozwiązanie Należy zastosować symbol >, informując powłokę o obowiązku skierowania strumienia wyjściowego do pliku. Oto przykład: $ echo do zapisania w pliku do zapisania w pliku $ echo do zapisania w pliku > plik.txt $

Dla pewności sprawdźmy, czy w pliku zostały zapisane dane wyjściowe polecenia: $ cat plik.txt do zapisania w pliku $

Analiza Pierwszy wiersz przedstawionego przykładu zawiera instrukcję echo z czterema parametrami. Każdy z tych parametrów jest wyświetlany na ekranie. W drugim wierszu kodu został wykorzystany symbol >, który zapisuje dane wyjściowe w pliku o nazwie plik.txt. Z tego powodu po wykonaniu polecenia echo na ekranie nie pojawia się żaden tekst. W drugiej części przykładu została użyta instrukcja cat odpowiedzialna za wyświetlenie zawartości pliku. Dzięki niej możemy się przekonać, że plik zawiera te informacje, które byłyby wyświetlone przez polecenie echo. Nazwa polecenia cat pochodzi od angielskiego słowa concatenation, oznaczającego łączenie ciągów tekstowych. Instrukcja cat powoduje bowiem złączenie treści kilku plików wymienionych jako parametry i wyświetlenie jej na ekranie. Na przykład po wykonaniu polecenia cat plik1 drugiplik kolejnyplik zawartość każdego z wymienionych plików zostałaby po kolei przekazana do okna terminala. Dzięki tego typu operacji można połączyć kilka części podzielonego wcześniej pliku. Wystarczy w tym celu skierować wynik wykonania instrukcji do kolejnego pliku: $ cat pierwsza.połowa druga.połowa > cały.plik

Przedstawione wcześniej polecenie cat plik.txt jest więc prostym przypadkiem złączenia tylko jednego pliku i wyświetlenia wyniku na ekranie. W praktyce, mimo że instrukcja cat może wykonywać bardziej zaawansowane operacje, jest wykorzystywana przede wszystkim do wyświetlania na ekranie zawartości wskazanego pliku.

Zobacz również • Polecenie man cat. • Receptura 17.21, „Numerowanie wierszy”.

2.5. Zapisywanie danych wyjściowych polecenia

|

57

2.6. Zapisywanie wyniku w plikach innych katalogów Problem Chcemy zapisać wynik wykonania polecenia w innym miejscu systemu plików niż bieżący katalog.

Rozwiązanie W definicji przekierowania strumienia wyjściowego należy uwzględnić ścieżkę dostępu do pliku. $ echo kilka dodatkowych danych > /tmp/echo.txt

lub $ echo kilka dodatkowych danych > ../../plik.docelowy

Analiza Nazwa pliku występująca za znakiem przekierowania strumienia (>) jest po prostu ścieżką dostępu do pliku. Jeśli nazwy nie poprzedza żaden kwalifikator, plik jest umieszczany w bieżącym katalogu. Jeżeli nazwa pliku rozpoczyna się od znaku ukośnika (/), jest to wówczas bezwzględna ścieżka dostępu, która wyznacza położenie pliku w drzewie katalogów systemu plików w odniesieniu do katalogu głównego (przez wszystkie katalogi pośrednie, przy założeniu, że prawa dostępu do poszczególnych katalogów umożliwiają przemieszczanie się w strukturze katalogów). W przykładzie została wykorzystana ścieżka /tmp, ponieważ odnosi się ona do powszechnie znanego katalogu, występującego we wszystkich systemach uniksowych. Zgodnie z przedstawionym kodem powłoka utworzy w katalogu /tmp plik echo.txt. Drugi przykład, zapisujący dane wyjściowe w pliku ../../plik.docelowy, wykorzystuje ścieżkę względną, w której ciąg .. jest specjalną nazwą katalogu, występującą wewnątrz każdego katalogu i odnoszącą się do katalogu nadrzędnego. Zatem odwołanie .. powoduje przejście o poziom wyżej w strukturze katalogów (w kierunku katalogu głównego). Najważniejszym wnioskiem z tego testu jest to, że możemy skierować dane wyjściowe do pliku, który jest umieszczony w zupełnie innym katalogu niż katalog, w którym jest wykonywane polecenie.

Zobacz również • Wprowadzenie do plików, katalogów i nazw złożonych ze znaków kropek w książce Came-

rona Newhama bash. Wprowadzenie (Helion 2006).

58

|

Rozdział 2. Standardowy strumień wyjściowy

2.7. Zapisywanie wyniku polecenia ls Problem Po zapisaniu danych wyjściowych polecenia ls z użyciem mechanizmu przekierowania okazuje się, że format zawartości pliku wynikowego odbiega od spodziewanego.

Rozwiązanie Korzystając z mechanizmu przekierowania strumienia wyjściowego w poleceniu ls, należy zastosować opcję –C. Poniżej został przedstawiony przykład wyświetlenia zawartości katalogu za pomocą instrukcji ls. $ ls a.out

config.txt

def.conf

kolejny.txt

lista.lst

plik.txt

Gdy jednak listing wynikowy zostanie zapisany w pliku z wykorzystaniem operatora >, a następnie zawartość pliku zostanie wyświetlona, na ekranie będzie można zobaczyć następującą treść: $ ls > /tmp/zapis.txt $ cat /tmp/zapis.txt a.out config.txt def.conf kolejny.txt lista.lst plik.txt $

W drugim eksperymencie do polecenia zostanie dodana opcja –C. $ ls -C > /tmp/zapis.txt $ cat /tmp/zapis.txt a.out config.txt def.conf $

kolejny.txt

lista.lst

plik.txt

Inne rozwiązanie polega na dodawaniu do instrukcji ls opcji -1 zawsze, gdy strumień wyjściowy nie podlega przekierowaniu. Listing wynikowy ma wówczas postać: $ ls -1 a.out config.txt def.conf kolejny.txt lista.lst plik.txt $

Wynik uzyskany w pierwszym przykładzie przekierowania pokrywa się z zaprezentowanym powyżej.

2.7. Zapisywanie wyniku polecenia ls

|

59

Analiza W momencie, gdy wydawałoby się, że poznaliśmy zasady przekierowywania strumienia wyjściowego i przetestowaliśmy je na prostej instrukcji ls, okazało się, że nie wszystko zadziałało zgodnie z oczekiwaniami. Z czego wynika problem? Mechanizm przekierowania z założenia ma być rozwiązaniem jednolitym dla wszystkich programów. Dzięki temu poszczególne aplikacje nie muszą być wyposażane w specjalny kod, który umożliwiłby przekazywanie wyników do pliku. Zastosowanie operatora > powoduje, że powłoka zajmuje się dostarczeniem danych we wskazane miejsce. Nie oznacza to jednak, że w programie nie może być uwzględniony kod wykrywający przypadki przekierowania. Taki program może działać w różny sposób, gdy przekierowanie zostało określone i gdy nie zostało zdefiniowane — taka sytuacja występuje w pracy instrukcji ls. Autorzy kodu polecenia ls doszli do wniosku, że gdy dane wyjściowe są wyświetlane na ekranie, to użytkownik prawdopodobnie będzie chciał, aby miały one format kolumnowy (opcja –C), ponieważ obszar ekranu jest dobrem deficytowym. Przyjęli również założenie, że w przypadku skierowania danych do pliku korzystniejsze będzie umieszczenie każdej nazwy w osobnym wierszu (opcja -1), gdyż taki format pozwala na łatwiejsze przetwarzanie zapisanych danych w przyszłości.

Zobacz również • Polecenie man ls. • Receptura 2.6, „Zapisywanie wyniku w plikach innych katalogów”.

2.8. Przekazanie danych wyjściowych i komunikatów o błędach do różnych plików Problem Wiemy, że program wygeneruje listing wynikowy. Nie chcemy jednak, żeby występowały w nim komunikaty o błędach. Chcemy zapisać komunikaty o błędach, ale oddzielenie ich od danych użytkowych jest dość trudne.

Rozwiązanie Należy skierować dane i komunikaty o błędach do różnych plików. $ mójprogram 1>dane.txt 2>błędy.txt

Inny, częściej stosowany sposób polega na wykorzystaniu składni: $ mójprogram >dane.txt 2>błędy.txt

60

|

Rozdział 2. Standardowy strumień wyjściowy

Analiza Zastosowanie przedstawionych instrukcji spowoduje utworzenie przez powłokę dwóch różnych plików. Pierwszy (dane.txt) będzie przechowywał ewentualne dane wyjściowe z programu mójprogram. Natomiast w pliku błędy.txt zostaną umieszczone komunikaty o błędach. W konstrukcjach 1> i 2> wartość liczbowa odpowiada deskryptorom plików. Zatem 1 odpowiada strumieniowi STDOUT (wyjściowemu), a 2 strumieniowi STDERR (błędów). Jeżeli nie zostanie określona żadna wartość liczbowa, powłoka przyjmuje domyślnie, że instrukcja dotyczy strumienia STDOUT.

Zobacz również • Receptura 2.6, „Zapisywanie wyniku w plikach innych katalogów”. • Receptura 2.13, „Odrzucanie danych wyjściowych”.

2.9. Przekazanie danych wyjściowych i komunikatów o błędach do tego samego pliku Problem Wykorzystując mechanizm przekierowania, można przekazać dane wyjściowe i komunikaty o błędach do różnych plików. Ale w jaki sposób można zapisać wynik i komunikaty o błędach w jednym pliku?

Rozwiązanie Należy użyć odpowiedniej składni instrukcji, która zapewnia przekierowanie strumienia błędów w to samo miejsce, do którego kierowany jest strumień wyjściowy. Forma zalecana: $ obydwa >& plik_wynikowy

lub $ obydwa &> plik_wynikowy

Starszy i nieco mniej przejrzysty sposób zapisu: $ obydwa > plik_wynikowy 2>&1

W przedstawionych przykładach ciąg obydwa odpowiada potencjalnemu programowi, który wygeneruje dane zarówno dla strumienia STDERR, jak i strumienia STDOUT.

Analiza Operatory &> i >& są skrótami informującymi powłokę o konieczności skierowania obydwu strumieni, STDOUT i STDERR, w to samo miejsce — wykonują więc założoną operację.

2.9. Przekazanie danych wyjściowych i komunikatów o błędach do tego samego pliku

|

61

W trzecim z przedstawionych przykładów element 1 wydaje się celem przekierowania, ale operator >& informuje, że wartość 1 należy interpretować jako deskryptor pliku, a nie nazwę pliku. W praktyce znaki 2>& stanowią jeden element składniowy, który oznacza, że standardowy strumień wyjściowy (2) zostanie przekierowany (>) do deskryptora pliku (&) występującego za operatorem (1). Znaki ciągu 2>& nie mogą być rozdzielane znakami spacji. W przeciwnym przypadku wartość 2 będzie potraktowana jako kolejny parametr, a znak & ma zupełnie inne znaczenie, gdy występuje oddzielnie (odpowiada za wykonanie polecenia w tle). Można przyjąć, że wszystkie operatory przekierowania wymagają podania określonej liczby po ich lewej stronie (np. 2>) oraz że jeśli liczba ta nie zostanie określona, domyślnie wprowadzana jest wartość 1 — deskryptor pliku standardowego strumienia wyjściowego. Nic nie stoi na przeszkodzie, żeby zdefiniować przekierowanie w odwrotnym kierunku (choć rozwiązanie to wydaje się mniej czytelne) i skierować standardowy strumień wyjściowy w to samo miejsce, do którego przekazywane są informacje ze strumienia błędów: $ obydwa 2>plik_wynikowy 1>&2

Cyfra 1 oznacza standardowy strumień wyjściowy, a cyfra 2 — standardowy strumień błędów. Zgodnie z wcześniejszymi informacjami możliwe jest także zastosowanie zapisu >&2, gdyż wartość 1 jest domyślną wartością dla operatora >. Jednak uwzględnienie deskryptora pliku znacznie poprawia czytelność instrukcji. Na uwagę zasługuje również kolejność danych w pliku wynikowym. Niekiedy komunikaty o błędach mogą zostać zapisane w pliku szybciej, niż pojawiłyby się na ekranie. Wynika to z braku mechanizmu buforowania dla strumienia błędów, a opisywany efekt nasila się, gdy informacje są zapisywane w pliku zamiast na ekranie.

Zobacz również • Receptura 2.6, „Zapisywanie wyniku w plikach innych katalogów”. • Receptura 2.13, „Odrzucanie danych wyjściowych”.

2.10. Dodawanie danych wyjściowych zamiast nadpisywania ich Problem Każde przekierowanie danych wyjściowych powoduje ponowne utworzenie pliku. Co zrobić, jeśli dane są przekierowywane po raz drugi (lub trzeci lub…), a poprzednie informacje nie mogą zostać utracone?

Rozwiązanie Symbol podwojonego znaku większości (>>) jest operatorem przekierowania w powłoce bash oznaczającym dołączenie danych wyjściowych.

62

|

Rozdział 2. Standardowy strumień wyjściowy

$ $ $ $ $ $

ls cd ls cd ls

> /tmp/ls.txt ../gdzieś >> /tmp/ls.txt ../inny_katalog >> /tmp/ls.txt

Analiza Pierwsza instrukcja wykonuje operację przekierowania polegającą na usunięciu pliku, jeśli ten istnieje, i utworzeniu nowego, w którym zostanie zapisany wynik polecenia ls. W drugim i trzecim wywołaniu instrukcji ls uwzględniony został podwójny znak większości (>>) informujący, że dane wyjściowe powinny zostać dołączone do treści pliku, a nie wykorzystane do utworzenia nowego pliku.

Zobacz również • Receptura 2.6, „Zapisywanie wyniku w plikach innych katalogów”. • Receptura 2.13, „Odrzucanie danych wyjściowych”.

2.11. Wykorzystanie jedynie początkowego lub końcowego fragmentu pliku Problem Chcemy wyświetlić lub przekazać do dalszego przetwarzania jedynie początkowy lub końcowy fragment pliku.

Rozwiązanie Należy zastosować polecenia head lub tail. Domyślnie instrukcja head przekazuje do strumienia wyjściowego dziesięć pierwszych wierszy wskazanego pliku, a instrukcja tail — dziesięć ostatnich. Jeżeli w ciągu wywołania zostanie zdefiniowanych więcej plików, wyodrębniane są stosowne wiersze z każdego z nich. Aby zmienić liczbę przekazywanych wierszy, wystarczy użyć opcji –liczba (np. -5). Polecenie tail jest również wyposażone w opcje –f i –F, które monitorują końcową część pliku i na bieżąco wyświetlają dopisywane do niego wiersze. Interesująca jest również opcja + tego polecenia. Została ona szczegółowo opisana w recepturze 2.12, „Pomijanie nagłówka pliku”.

Analiza Polecenia head i tail, podobnie jak cat, grep, sort, cut i uniq, są jednymi z najczęściej wykorzystywanych uniksowych narzędzi przetwarzania tekstu. Wiele osób po zapoznaniu się z tymi instrukcjami zaczyna się zastanawiać, jak w ogóle można było pracować bez ich stosowania.

2.11. Wykorzystanie jedynie początkowego lub końcowego fragmentu pliku

|

63

Zobacz również • Receptura 2.12, „Pomijanie nagłówka pliku”. • Receptura 7.1, „Wyszukiwanie określonego ciągu w pliku tekstowym”. • Receptura 8.1, „Sortowanie danych wyjściowych”. • Receptura 8.4, „Wycinanie fragmentów listingu wynikowego”. • Receptura 8.5, „Usuwanie zduplikowanych wierszy”. • Receptura 17.21, „Numerowanie wierszy”.

2.12. Pomijanie nagłówka pliku Problem W pliku występuje jeden wiersz nagłówka lub większa liczba takich wierszy. Musimy przetworzyć treść pliku z pominięciem jego nagłówka.

Rozwiązanie Należy zastosować polecenie tail ze specjalną opcją. Aby na przykład pominąć pierwszy wiersz pliku, wystarczy wprowadzić polecenie: $ tail Wiersz Wiersz Wiersz Wiersz

–n +2 plik 2 3 4 5

Analiza Parametr polecenia tail poprzedzony znakiem minus (-) wyznacza wartość przesunięcia względem końca pliku. Zatem polecenie tail -10 plik powoduje wyświetlenie dziesięciu ostatnich wierszy z pliku plik (co jednocześnie jest domyślnym sposobem działania polecenia). Wartość liczbowa poprzedzona znakiem plus (+) jest natomiast definicją przesunięcia wyznaczanego w odniesieniu do początku pliku. Instrukcja tail –n +1 plik odpowiada za wyświetlenie całego pliku (podobnie jak polecenie cat). Wykorzystanie opcji –n +2 powoduje pominięcie pierwszego wiersza itd.

Zobacz również • Polecenie man tail. • Receptura 13.11, „Utworzenie bazy danych MySQL”.

64

|

Rozdział 2. Standardowy strumień wyjściowy

2.13. Odrzucanie danych wyjściowych Problem Niekiedy nie ma potrzeby zapisywana danych wyjściowych w pliku. Co więcej zdarzają się sytuacje, w których w ogóle nie jesteśmy zainteresowani oglądaniem wyniku.

Rozwiązanie Dane wyjściowe powinny zostać skierowane do urządzenia /dev/null zgodnie z poniższym przykładem. $ find / -name mójplik –print 2> /dev/null

lub $ program >/dev/null 2>&1

Analiza Można oczywiście skierować niechciane dane wyjściowe do pliku, a po zakończeniu prac plik ten usunąć. Istnieje jednak łatwiejsze rozwiązanie. Systemy Unix i Linux dysponują specjalnym urządzeniem, które nie jest rzeczywistym komponentem sprzętowym, ale pewnego rodzaju koszem na śmieci, do którego wyrzuca się wszystkie niepotrzebne dane. Urządzenie to nazywa się /dev/null i znajduje zastosowanie w opisywanym rozwiązaniu. Wszelkie informacje przekazane do tego urządzenia są po prostu ignorowane i nie zajmują przestrzeni dyskowej. Mechanizm przekierowania dodatkowo ułatwia tę operację. W pierwszym zaprezentowanym przykładzie odrzucone zostały tylko informacje standardowego strumienia błędów. W drugim natomiast zignorowane zostały zarówno dane wyjściowe, jak i komunikaty o błędach. Zdarzają się sytuacje, w których system plików /dev okazuje się elementem przeznaczonym tylko do odczytu (na przykład z uwagi na dostępność urządzeń gwarantujących bezpieczeństwo danych). W takich przypadkach pozostaje jedynie rozwiązanie polegające na zapisie danych w pliku i usunięciu tego pliku.

Zobacz również • Receptura 2.6, „Zapisywanie wyniku w plikach innych katalogów”.

2.13. Odrzucanie danych wyjściowych

|

65

2.14. Zapisywanie i grupowanie danych wyjściowych większej liczby poleceń Problem Chcemy zarejestrować informacje za pomocą przekierowania, wprowadzając jednocześnie kilka instrukcji w jednym wierszu. $ pwd; ls; cd ../gdzieś; pwd; ls > /tmp/wszystko.txt

Mechanizm przekierowania obejmuje jednak tylko ostatnie polecenie — ostatnią instrukcją ls w wierszu. Dane wyjściowe wszystkich pozostałych poleceń są wyświetlane na ekranie (nie są przekierowane do pliku).

Rozwiązanie Użycie znaków nawiasu klamrowego ({}) pozwala na grupowanie poleceń. Dzięki temu operacja przekierowania dotyczy danych wyjściowych wygenerowanych przez wszystkie zgrupowane instrukcje. Oto przykład: $ { pwd; ls; cd ../gdzieś; pwd; ls; } > /tmp/wszystko.txt

W rozwiązaniu tym trzeba zwrócić szczególną uwagę na dwa elementy. Po pierwsze, nawiasy klamrowe są w rzeczywistości słowami kluczowymi, więc muszą być otoczone znakami spacji. Po drugie, na końcu ostatniego grupowanego polecenia musi występować średnik.

Opcjonalnie można wykorzystać znaki nawiasu okrągłego (), nakazując interpreterowi bash wykonanie poleceń w podpowłoce, a następnie przekierować strumień wyjściowy podpowłoki. $ (pwd; ls; cd ../gdzieś; pwd; ls) > /tmp/wszystko.txt

Analiza Choć obydwa rozwiązania wyglądają bardzo podobnie, są między nimi dwie istotne różnice — składniowa i znaczeniowa. Różnica składniowa polega na tym, że jeśli zostaną zastosowane nawiasy klamrowe, muszą one zostać otoczone znakami spacji, a listę poleceń musi zakończyć znak średnika. Wymóg ten nie obowiązuje w sytuacji, w której wykorzystywane są znaki nawiasu okrągłego. Ważniejsza jest tu jednak różnica znaczeniowa. Użycie nawiasów klamrowych powoduje po prostu zgrupowanie kilku instrukcji jako uproszczonego sposobu przekierowywania danych wyjściowych. Dzięki nim nie ma potrzeby przekierowywania strumieni wyjściowych poszczególnych poleceń. Z kolei zapisanie instrukcji pomiędzy znakami nawiasu okrągłego wymusza wykonanie ich w nowym egzemplarzu powłoki, zwanym podpowłoką. Środowisko pracy podpowłoki jest niemal identyczne ze środowiskiem powłoki bieżącej, tzn. wszystkie zmienne środowiskowe (w tym $PATH) mają te same wartości. Różnica polega jedynie na odmiennym przetwarzaniu przechwytywanych sygnałów (więcej informacji na ten temat znajduje się w recepturze 10.6, „Przechwytywanie przerwań”). Powoduje to jednak, że rozwiązanie bazujące na zastosowaniu podpowłoki znacznie różni się od zwykłego grupowania poleceń. 66

|

Rozdział 2. Standardowy strumień wyjściowy

Przedstawione w przykładzie polecenie cd jest wykonywane w podpowłoce (po jej utworzeniu), co oznacza, że katalog bieżący pierwotnej powłoki pozostaje niezmieniony — katalog bieżący nie uległ zmianie podobnie jak zmienne powłoki. W przypadku zastosowania nawiasów klamrowych, gdy zostanie zakończone wykonywanie polecenia, użytkownik jest przenoszony do nowego katalogu (w tym przykładzie do katalogu ../gdzieś). Wszystkie zmiany dokonane w wyniku realizacji instrukcji (np. podstawienie wartości zmiennych) odnoszą się do bieżącego egzemplarza powłoki. W obydwu rozwiązaniach uzyskujemy te same dane wyjściowe, ale na końcu powłoki pracują w zupełnie innych katalogach. Jednym z ciekawszych zastosowań nawiasów klamrowych jest formowanie zwięzłych bloków kodu warunkowego (zagadnienie to zostało opisane w recepturze 6.2, „Rozgałęzianie kodu w instrukcjach warunkowych”). Skrypt: if [ $result = 1 ]; then echo 'Wynikiem jest 1.' exit 0 else echo 'UCIEKAJ!' exit 120 fi

można skrócić do postaci: [ $result = 1 ] \ && { echo 'Wynikiem jest 1.'; exit 0; } \ || { echo 'UCIEKAJ!'; exit 120; }

Sposób zapisu zależy jedynie od preferencji programisty i tego, która postać jest według programisty bardziej czytelna.

Zobacz również • Receptura 6.2, „Rozgałęzianie kodu w instrukcjach warunkowych”. • Receptura 10.6, „Przechwytywanie przerwań”. • Receptura 15.11, „Pobieranie danych z innego komputera”. • Receptura 19.5, „Spodziewana zmiana eksportowanych wartości”. • Receptura 19.8, „Potoki powołują podpowłoki”. • Punkt „Wbudowane zmienne powłoki” w dodatku A (szczególnie zmienna BASH_SUBSHELL).

2.15. Łączenie dwóch programów przez wykorzystanie danych wyjściowych jako wejściowych Problem Chcemy wykorzystać dane wyjściowe jednego programu jako dane wejściowe dla drugiego programu.

2.15. Łączenie dwóch programów przez wykorzystanie danych wyjściowych jako wejściowych

|

67

Rozwiązanie Można skierować strumień wyjściowy jednego programu do tymczasowego pliku, a następnie wykorzystać ten plik jako dane wejściowe dla drugiego programu: $ cat pierwszy.plik drugi.plik > /tmp/cat.txt $ sort < /tmp/cat.txt ... $ rm /tmp/cat.txt

Można również wykonać tę operację w jednym kroku, przekazując bezpośrednio dane wyjściowe do następnego programu. Za połączenie odpowiada w takich przypadkach operator potoku (|). Oto przykład: $ cat pierwszy.plik drugi.plik | sort

Oczywiście, istnieje również możliwość sekwencyjnego złączenia kilku poleceń z wykorzystaniem kilku operatorów potoku: $ cat moje* | tr 'a-z' 'A-Z' | uniq | awk –f konwersja.awk | wc

Analiza Dzięki zastosowaniu operatora potoku nie musimy tworzyć pliku tymczasowego, zapamiętywać jego nazwy i usuwać go po zakończeniu pracy. Programy takie jak sort mogą pobierać dane wejściowe ze standardowego strumienia wejściowego (skierowane za pomocą operatora /tmp/wszystkie.pliki.źródłowe

Jednak przed zapoznaniem się z zawartością pliku wynikowego trzeba będzie trochę poczekać (naturalnie można wykorzystać polecenie tail –f, ale nie jest to rozwiązanie uniwersalne). Zamiast zwykłego przekierowania strumienia wyjściowego można tu zastosować instrukcję tee: find / -name '*.c' –print | tee /tmp/wszystkie.pliki.źródłowe

Ponieważ strumień wyjściowy polecenia tee nie jest przekierowywany, informacje zostaną wyświetlone na ekranie. Ich kopia natomiast zostanie skierowana do pliku z przeznaczeniem do wykorzystania w przyszłości (np. za pomocą instrukcji cat /tmp/wszystkie.pliki.źródłowe). Warto również zwrócić uwagę na fakt, że w prezentowanych przykładach nie jest uwzględnione przekierowanie strumienia błędów. Oznacza to, że wszystkie komunikaty o błędach, jakich należy się spodziewać w wyniku działania instrukcji find, zostaną wyświetlone na ekranie, ale nie w pliku wskazanym w poleceniu tee. Rozwiązaniem jest dodanie ciągu 2>&1 do polecenia find. find / -name '*.c' –print 2>&1 | tee /tmp/wszystkie.pliki.źródłowe

Spowoduje on uwzględnienie w pliku również komunikatów o błędach. Nie będą one w żaden sposób wydzielone, ale zostaną przechwycone.

Zobacz również • Polecenie man tee. • Receptura 18.5, „Powtórne wykorzystanie parametrów”. • Receptura 19.13, „Śledzenie przebiegu skryptów”.

70

|

Rozdział 2. Standardowy strumień wyjściowy

2.17. Łączenie dwóch programów z wykorzystaniem danych wyjściowych jako parametrów Problem Co zrobić, jeśli jeden z programów, do których chcielibyśmy skierować potok, nie obsługuje tego mechanizmu? Przykładem może tu być konieczność usunięcia plików za pomocą polecenia rm, który wymaga zdefiniowania plików do usunięcia jako parametrów instrukcji: $ rm mój.java twój.c ich.*

Instrukcja rm nie odczytuje informacji ze standardowego strumienia wejściowego, więc niemożliwe jest wykonanie polecenia: find . –name '*.c' | rm

Skoro instrukcja rm pobiera nazwy plików tylko w formie parametrów wiersza poleceń, to w jaki sposób można wstawić dane wynikowe wykonanej wcześniej instrukcji (np. echo lub ls) do wiersza poleceń?

Rozwiązanie Należy wykorzystać mechanizm podstawiania poleceń powłoki bash: $ rm $(find . –name '*.class') $

Analiza Znaki $() otaczają polecenie, które jest wykonywane w podpowłoce. Wynik wykonania tego polecenia jest wstawiany w miejsce frazy $(). Znaki nowego wiersza w danych wynikowych są zastępowane znakami spacji (w praktyce podczas rozdzielania słów wykorzystywany jest pierwszy znak zmiennej $IFS, który domyślnie jest znakiem spacji). Zatem kilka wierszy listingu wynikowego jest przekształcanych na kilka parametrów wiersza poleceń. Wcześniej stosowana składania bazowała na otoczeniu polecenia składowego znakami lewego apostrofu zamiast znakami $(). Jednak ze względu na łatwość zagnieżdżania poleceń i przejrzystość kodu preferowane jest rozwiązanie uwzględniające znaki $() zamiast ``. Nie zmienia to faktu, że pracując z systemem, częściej można spotkać się ze składnią bazującą na znakach ``. Dotyczy to przede wszystkim starszych skryptów, opracowanych przez osoby przyzwyczajone do pierwotnych powłok Bourne’a i C. W przedstawionym przykładzie wynik wykonania polecenia find, czyli lista nazw plików, zostanie przekształcony w listę parametrów polecenia rm. Ostrzeżenie: Wykonując tego typu polecenia, trzeba zachować szczególną ostrożność, ponieważ instrukcja rm nie wybacza błędów. Jeśli mechanizm find znajdzie więcej plików, niż zakładaliśmy, polecenie rm nieodwołalnie je usunie. To nie jest system Windows — nie można przywrócić plików z kosza. Aby zmniejszyć ryzyko usunięcia nieodpowiednich plików, warto wykorzystać

2.17. Łączenie dwóch programów z wykorzystaniem danych wyjściowych jako parametrów

|

71

instrukcję rm –i, która wymaga potwierdzenia każdego przypadku usunięcia pliku. Rozwiązanie to sprawdza się w odniesieniu do kilku plików, ale przy dużej ich liczbie jest bardzo czasochłonne i irytujące. Jeden ze sposobów na bezpieczne wykonanie podobnej operacji w powłoce bash polega na uprzednim zrealizowaniu samej wewnętrznej instrukcji. Użytkownik może wówczas sprawdzić, czy uzyskane wyniki polecenia są zgodne z jego oczekiwaniami, a następnie wykonać pełne zadanie. Oto przykład: $ find . –name '*.class' Pierwszy.class Drugi.class $ rm $(find . –name '*.class') $

W jednej z kolejnych receptur zostanie opisana metoda jeszcze większego uodpornienia kodu na pomyłki przez zastosowanie operatora !! zamiast powtórnego wprowadzania polecenia find (zagadnienie to zostało omówione w recepturze 18.2, „Powtarzanie ostatniego polecenia”).

Zobacz również • Receptura 18.2, „Powtarzanie ostatniego polecenia”. • Receptura 15.13, „Eliminacja błędów typu argument list too long”.

2.18. Wielokrotne przekierowania w jednym wierszu Problem Chcemy przekierować dane wyjściowe w kilka różnych miejsc.

Rozwiązanie Należy zastosować mechanizm przekierowania, wymieniając wszystkie pliki, które powinny zostać wykorzystane. Oto przykład: $ przekazanie 3>pierwszy.plik 4>drugi.plik 5>piąty.plik 6>kolejny.plik $

Nazwa przekazanie odpowiada skryptowi powłoki zawierającemu polecenia, których wyniki mają zostać przekierowane w różne miejsca. W treści skryptu można zawrzeć instrukcje typu echo opcja $OPTSTR >&5. Skrypt będzie wówczas kierował dane wyjściowe do plików o różnych deskryptorach, dzięki czemu program wywołujący może przekazać dane do różnych miejsc. Gdyby skrypt przekazanie był programem wykonywalnym języka C, mógłby przekazywać dane do strumieni o deskryptorach 3, 4, 5 i 6 bez konieczności wywoływania funkcji open().

72

|

Rozdział 2. Standardowy strumień wyjściowy

Analiza We wcześniejszej recepturze zostały zamieszczone informacje o tym, że każdy deskryptor pliku jest identyfikowany przez liczbę (której najmniejsza wartość to 0). Standardowemu strumieniowi wejściowemu odpowiada wartość 0, strumieniowy wyjściowemu 1, a strumieniowi błędów 2. Przekierowanie strumienia wyjściowego można więc zdefiniować w nieco bardziej rozwlekły sposób, wpisując ciąg 1> (a nie sam znak >) i nazwę pliku. Nie jest to jednak konieczne, gdyż skrócona postać zapisu jest wystarczająca. Oznacza to jednak, że powłoka może powołać dowolną liczbę deskryptorów plików i przypisać je do różnych plików. Program wywoływany przez powłokę na podstawie instrukcji wiersza poleceń może wówczas wykorzystać powołane wcześniej deskryptory plików bez konieczności powoływania ich w trakcie własnej pracy. Choć stosowanie tej techniki nie jest zalecanym rozwiązaniem, może być intrygujące.

Zobacz również • Receptura 2.6, „Zapisywanie wyniku w plikach innych katalogów”. • Receptura 2.8, „Przekazanie danych wyjściowych i komunikatów o błędach do różnych

plików”. • Receptura 2.13, „Odrzucanie danych wyjściowych”.

2.19. Zapisywanie danych wyjściowych, gdy wydaje się, że przekierowanie nie działa Problem Mimo zastosowania znaku > część (lub całość) danych wyjściowych jest wyświetlana na ekranie. Przykładem mogą tu być komunikaty o błędach generowane przez kompilator. $ gcc bledny.c bledny.c: In function `main': bledny.c:2: error: missing terminating " character bledny.c:3: error: syntax error before '}' token $

Załóżmy, że w celu zarejestrowania tych komunikatów strumień wyjściowy został skierowany do pliku. $ gcc bledny.c > komunikaty.txt bledny.c: In function `main': bledny.c:2: error: missing terminating " character bledny.c:3: error: syntax error before '}' token $

Wydaje się, że żadne informacje nie zostały przekazane do pliku. Co więcej, po szczegółowej analizie parametrów pliku docelowego okazuje się, że jest on pusty (rozmiar wynosi 0 B). $ ls -l komunikaty.txt -rw-r--r-- 1 root root 0 wrz $ cat komunikaty.txt $

2 17:16 komunikaty.txt

2.19. Zapisywanie danych wyjściowych, gdy wydaje się, że przekierowanie nie działa

|

73

Rozwiązanie Przekierowanie trzeba zdefiniować w następujący sposób: $ gcc bledny.c 2>komunikaty.txt $

Plik komunikaty.txt zawiera teraz informacje o błędach, które były poprzednio obserwowane na ekranie.

Analiza Z czego wynika ten problem? Każdy proces w systemach Unix i Linux rozpoczyna swoje działania z trzema aktywnymi deskryptorami plików — jednym przeznaczonym dla danych wejściowych, zwanym standardowym strumieniem wejściowym (STDIN); jednym przeznaczonym dla danych wyjściowych, zwanym standardowym strumienie wyjściowym (STDOUT), oraz jednym przeznaczonym dla komunikatów o błędach, zwanym standardowym strumieniem błędów (STDERR). Tylko od programisty zależy to, czy jego aplikacja będzie działała zgodnie z przyjętą konwencją i będzie przekazywała informacje o błędach do standardowego strumienia błędów, a wynikowe dane użytkowe do standardowego strumienia wyjściowego. Nie ma więc gwarancji, że każdy wygenerowany komunikat o błędzie rzeczywiście zostanie przekazany przez standardowy strumień błędów, choć większość powszechnie stosowanych programów funkcjonuje zgodnie z tymi założeniami. Przedstawione wcześniej komunikaty o błędach kompilacji nie zostały przekazane do pliku po zastosowaniu operatora przekierowania >, ponieważ jego działanie obejmuje tylko dane standardowego strumienia wyjściowego, a nie strumienia błędów. Każdy deskryptor pliku jest identyfikowany za pomocą wartości liczbowej, nie mniejszej niż 0. Standardowemu strumieniowi wejściowemu odpowiada liczba 0, standardowemu strumieniowi wyjściowemu odpowiada liczba 1, a standardowemu strumieniowi błędów odpowiada liczba 2. Oznacza to, że przekierowanie strumienia wyjściowego można zdefiniować w nieco bardziej rozwlekły sposób, wpisując ciąg 1> (a nie sam znak >) i nazwę pliku. Nie jest to jednak konieczne. Skrócona postać zapisu jest wystarczająca. Między standardowym strumieniem wyjściowym i strumieniem błędów występuje jeszcze jedna różnica — dane strumienia wyjściowego są buforowane, natomiast dane strumienia błędów nie są buforowane (każdy znak jest zapisywany oddzielnie, bez konieczności grupowania i zapisywania całej grupy). Oznacza to, że komunikaty o błędach są widoczne natychmiast, co zmniejsza ryzyko utracenia ich w przypadku awarii. Ceną za to jest obniżenie wydajności systemu. Nie można oczywiście stwierdzić, że wyświetlanie danych wyjściowych jest niepewne, ale w sytuacji awaryjnej (np. gdy program przedwcześnie kończy swoje działanie) informacje strumienia wyjściowego mogą nie zostać wyświetlone na ekranie. Ponieważ standardowy strumień wyjściowy podlega buforowaniu, rzeczywisty zapis danych następuje więc po zapełnieniu bufora lub w chwili zamknięcia pliku. Takie rozwiązanie jest efektywniejsze w przypadku często generowanych informacji. Jednak w przypadku konieczności zgłoszenia błędu wydajność rozwiązania nie jest szczególnie istotna. Co należy zrobić, aby mieć możliwość podglądania zapisywanej treści? Wystarczy zastosować polecenie tee, które zostało omówione w recepturze 2.16, „Zapisywanie kopii danych wyjściowych mimo wykorzystywania ich jako danych wejściowych”. $ gcc bledny.c 2>&1 | tee komunikaty.txt

74

|

Rozdział 2. Standardowy strumień wyjściowy

Powyższa instrukcja zapewnia przekierowanie danych standardowego strumienia wyjściowego do strumienia błędów i przekazanie za pomocą potoku obydwu rodzajów informacji do polecenia tee. Program tee przekaże dostarczone do niego dane do pliku komunikaty.txt oraz do standardowego strumienia wyjściowego, czyli na ekran (ponieważ dalsze przekierowanie nie zostało zdefiniowane). Mamy tu do czynienia ze szczególnym przypadkiem przekierowania. Zazwyczaj kolejność operacji przekierowania jest bardzo istotna. Porównajmy dwa poniższe polecenia: $ jakieśpolecenie >mój.plik 2>&1 $ jakieśpolecenie 2>&1 >mój.plik

W pierwszym przypadku standardowy strumień wyjściowy jest skierowany do pliku (mój.plik), a standardowy strumień błędów w to samo miejsce, co strumień wyjściowy. Wszystkie dane wynikowe zostaną więc zapisane w pliku mój.plik. W drugim przypadku rezultat jest inny. Standardowy strumień błędów jest bowiem skierowany do standardowego strumienia wyjściowego (który na tym etapie działania instrukcji odnosi się do ekranu), a następnie strumień wyjściowy zostaje skierowany do pliku mój.plik. W efekcie tylko komunikaty standardowego strumienia wyjściowego zostaną zarejestrowane w pliku. Komunikaty o błędach będą natomiast wyświetlane na ekranie. Jednak kolejność ta musi być odwrócona w zadaniach bazujących na mechanizmie potoku. Nie można przecież umieścić operatora przekierowania za symbolem potoku, ponieważ za operatorem potoku zapisane jest następne polecenie. Powłoka czyni więc wyjątek podczas realizacji instrukcji: $ jakieśpolecenie 2>&1 | innepolecenie

Ustala, że do potoku kierowane są dane standardowego strumienia wyjściowego. Przyjmuje jednocześnie, że zapis 2>&1 oznacza chęć dołączenia do danych wyjściowych informacji standardowego strumienia błędów, mimo iż kolejność operacji jest odwrócona. Efektem ubocznym takiego sposobu działania oraz składni poleceń potokowych jest to, że nie ma możliwości przekazania do potoku jedynie danych standardowego strumienia błędów — jeśli nie zamieni się uprzednio deskryptorów plików (zagadnienie to zostało opisane w kolejnej recepturze).

Zobacz również • Receptura 2.17, „Łączenie dwóch programów z wykorzystaniem danych wyjściowych

jako parametrów”. • Receptura 2.20, „Zamiana strumieni STDERR i STDOUT”.

2.20. Zamiana strumieni STDERR i STDOUT Problem Chcemy zamienić strumienie STDERR i STDOUT, aby można było kierować dane strumienia STDOUT do pliku dziennika, a informacje strumienia STDERR (za pomocą polecenia tee) na ekran i do pliku. Potoki odnoszą się jednak tylko do strumienia STDOUT. 2.20. Zamiana strumieni STDERR i STDOUT

|

75

Rozwiązanie Przed przekazaniem danych do potoku trzeba zamienić strumienie STDERR i STDOUT, wykorzystując do tego celu trzeci deskryptor pliku. $ ./mójskrypt 3>&1 1>dziennik.stdout 2>&3- | tee –a dziennik.stderr

Analiza Każde przekierowanie z użyciem deskryptorów plików powoduje zduplikowanie istniejącego deskryptora. Dzięki temu istnieje możliwość podmiany deskryptorów w sposób zbliżony do operacji zamiany dwóch wartości w dowolnym języku programowania — niezbędna jest trzecia, tymczasowa zmienna. Procedura ma następujący przebieg: kopiujemy A do C, kopiujemy B do A, kopiujemy C do B i otrzymujemy zamianę wartości zmiennych A i B. W przypadku deskryptorów plików operacja ta jest zapisywana w następujący sposób: $ ./mójskrypt 3>&1 1>&2 2>&3

Frazę 3>&1 należy odczytywać jako „przypisz deskryptorowi 3 tę samą wartość, jaką ma deskryptor pliku strumienia wyjściowego (1)”. W rezultacie deskryptor pliku 1 (tj. STDOUT) jest kopiowany i przypisywany deskryptorowi pliku 3, czyli tymczasowej zmiennej. Następnie duplikowany jest deskryptor pliku 2 (tj. STDERR) i udostępniany jako deskryptor STDOUT. Na końcu kopia deskryptora pliku 3 jest przypisywana strumieniowi STDERR. Ostatecznie deskryptory plików strumieni STDERR i STDOUT są zamienione względem siebie. Operację tę trzeba teraz dostosować do opisywanego problemu. Po wykonaniu kopii deskryptora STDOUT (deskryptor 3) możemy przekierować standardowy strumień wyjściowy do pliku dziennika, co pozwoli na przechwycenie danych wynikowych skryptu lub programu. Następnie możemy skopiować deskryptor pliku z tymczasowej zmiennej (deskryptor 3) do strumienia STDERR. Dołączenie potoku nie stanowi problemu, ponieważ potok jest połączony z (pierwotnym) strumieniem STDOUT. Po zapisaniu tego w formie instrukcji otrzymujemy kod przedstawiony wcześniej. $ ./mójskrypt 3>&1 1>dziennik.stdout 2>&3- | tee –a dziennik.stderr

Warto zwrócić uwagę na końcową frazę 2>&3-. Po zakończeniu pracy z deskryptorem pliku 3 jest on usuwany. Dzięki temu program nie pozostawia aktywnego deskryptora pliku. Jest to więc sposób na „posprzątanie” po sobie.

Zobacz również • Książka Roba Flickengera Linux Server Hacks, wydanie pierwsze, punkt „n>&m: Swap

STDOUT and STDERR”. • Receptura 2.19, „Zapisywanie danych wyjściowych, gdy wydaje się, że przekierowanie

nie działa”. • Receptura 10.1, „«Demonizowanie» skryptu”.

76

|

Rozdział 2. Standardowy strumień wyjściowy

2.21. Zabezpieczanie pliku przed przypadkowym nadpisaniem Problem Nie chcemy dopuścić do przypadkowego nadpisania zawartości pliku. Zdarzają się przecież przypadki pomyłek w nazwach plików, co może skutkować przekierowaniem danych wyjściowych do pliku, który nie powinien być modyfikowany.

Rozwiązanie Można przekazać do powłoki informację o konieczności zachowania szczególnej ostrożności w tego typu operacjach: $ set –o noclobber $

Aby wyłączyć tę opcję, wystarczy wykonać polecenie: $ set +o noclobber $

Analiza Opcja noclobber uniemożliwia powłoce bash nadpisywanie istniejących plików w operacjach przekierowania strumienia wyjściowego. Jeśli wskazany plik docelowy nie istnieje, mechanizm działa w standardowy sposób — interpreter bash tworzy plik i zapisuje w nim dane wynikowe. Jeżeli jednak plik istnieje, na ekranie zostanie wyświetlony komunikat o błędzie. Zamieszczone poniżej instrukcje demonstrują działanie mechanizmu. Na początku opcja zostanie wyłączona, co pozwoli na ustawienie powłoki w znanym stanie, niezależnie od domyślnej konfiguracji określonego systemu. $ set +o noclobber $ echo coś > mój.plik $ echo kolejna informacja > mój.plik $ set -o noclobber $ echo coś > mój.plik -bash: mój.plik: cannot overwrite existing file $ echo kolejna informacja >> mój.plik $

Pierwsze skierowanie danych do pliku mój.plik powoduje utworzenie go. Podczas wykonywania drugiej tego typu operacji powłoka bash nadpisuje dane (usuwa dane od pozycji 0 bajtów i rozpoczyna w tym miejscu zapis). Po ustawieniu opcji noclobber próba zapisu danych w pliku kończy się wygenerowaniem komunikatu o błędzie. Wykonując ostatni wiersz zaprezentowanego kodu, można się jednak przekonać, że operacja dołączenia danych (operator >>) jest realizowana poprawnie.

2.21. Zabezpieczanie pliku przed przypadkowym nadpisaniem

|

77

Opcja noclobber jest uwzględniana jedynie przez powłokę podczas nadpisywania danych pliku w operacji przekierowania strumienia wyjściowego. Nie wpływa na sposób przetwarzania pliku przez inne programy (więcej informacji na ten temat zostało zamieszczonych w recepturze 14.13, „Definiowanie praw dostępu”). $ $ $ $ $

echo niepotrzebne dane > jakiś.plik echo ważne dane > inny.plik set –o noclobber cp jakiś.plik inny.plik

Wykonanie powyższych instrukcji nie spowoduje wyświetlenia informacji o błędzie. Plik zostanie skopiowany i zastąpi plik dotychczasowy. Operacja jest wykonywana przez polecenie cp bez udziału powłoki.

Opisywane zagadnienie nie jest szczególnie istotne dla osób, które uważnie i bezbłędnie wprowadzają nazwy plików. Może mieć jednak znaczenie podczas generowania nazw plików za pomocą wyrażeń regularnych lub przekazywanych zmiennych, co zostało opisane w dalszych recepturach. Wyznaczone w ten sposób nazwy mogą być wykorzystywane również w zadaniach przekazywania danych wyjściowych. Opcja noclobber może się wówczas okazać ważnym elementem zabezpieczenia przed niepożądanymi efektami ubocznymi całej operacji.

Zobacz również • Przydatne źródła informacji na temat polecenia chmod i praw dostępu: • http://www.linuxforums.org/security/file_permissions.html, • http://www.faqs.org/docs/linux_intro/sect_03_04.html, • http://www.perlfect.com/articles/chmod.shtml. • Receptura 14.13, „Definiowanie praw dostępu”.

2.22. Celowe nadpisanie pliku Problem Opcja noclobber powinna być włączona zawsze, poza przypadkami, kiedy rzeczywiście chcemy nadpisać dane pliku informacjami strumienia wyjściowego. Czy można szybko zmienić ustawiony sposób działania powłoki bash?

Rozwiązanie Definiując przekierowanie strumienia wyjściowego, wystarczy zastosować operator >|. Powłoka bash zignoruje wówczas bieżące ustawienia (nawet jeśli opcja noclobber jest włączona) i nadpisze plik. Rozważmy następujący przykład: echo coś > mój.plik set -o noclobber echo kolejna informacja >| mój.plik cat mój.plik

$ $ $ $

78

|

Rozdział 2. Standardowy strumień wyjściowy

kolejna informacja $ echo jeszcze raz > mój.plik -bash: mój.plik: cannot overwrite existing file $

Wykonanie drugiej instrukcji echo nie powoduje żadnego błędu. Natomiast trzecia instrukcja echo, pozbawiona znaku pionowej kreski za znakiem większości, kończy się wyświetleniem przez powłokę komunikatu ostrzegawczego i pozostawieniem niezmienionego pliku docelowego.

Analiza Zastosowanie opcji noclobber nie zastępuje ograniczeń wynikających z praw dostępu do pliku. Jeżeli dany użytkownik nie ma prawa zapisywania plików w określonym katalogu, nie będzie mógł tego pliku utworzyć niezależnie od tego, czy wykorzysta operator >|, czy nie. Analogicznie musi dysponować prawem zapisu dla pliku, jeżeli chce nadpisać jego dane, bez względu na to, czy użyje operatora >|, czy nie. Po co więc stosuje się znak pionowej kreski? Prawdopodobnie dlatego, że znak wykrzyknika był już zarezerwowany do innych operacji powłoki, a pionowa kreska wizualnie przypomina wykrzyknik. Dlaczego jednak znak ! byłoby odpowiednim symbolem? Oczywiście z uwagi na wzmocnienie znaczenia polecenia i przekazanie go do powłoki w trybie rozkazującym — „wykonaj to bez względu na konsekwencje!”. Edytory tekstu, takie jak vi (i ex), wykorzystują znak ! w takim samym znaczeniu w poleceniach zapisu (:w! nazwa_pliku). Bez zastosowania znaku ! edytor odmówi nadpisania istniejącego pliku. Użycie tego znaku jest natomiast dla edytora rozkazem „zrób to!”.

Zobacz również • Receptura 14.13, „Definiowanie praw dostępu”.

2.22. Celowe nadpisanie pliku

|

79

80

|

Rozdział 2. Standardowy strumień wyjściowy

ROZDZIAŁ 3.

Standardowy strumień wejściowy

Standardowy strumień wejściowy jest równie ważny jak strumień wyjściowy. Umożliwia bowiem dostarczanie danych do przetwarzającego je programu lub do poleceń zarządzających pracą skryptu. Pierwsza część każdego programu komputerowego zawsze bazuje na operacjach wejścia-wyjścia.

3.1. Pobieranie danych wejściowych z pliku Problem Dane dla poleceń powłoki powinny być odczytywane z pliku.

Rozwiązanie Należy wykorzystać mechanizm przekierowania danych wejściowych. Zastosowanie operatora < spowoduje odczytanie danych z pliku. $ wc < mój.plik

Analiza Podobnie jak operator > powoduje zapisanie danych w pliku, operator < umożliwia pobranie informacji z pliku. Symbole przekierowania zostały dobrane w taki sposób, aby można było wizualnie ocenić kierunek przepływu danych. Wyznacza go grot strzałki. Wiele poleceń powłoki jako parametry wywołania pobiera nazwę lub nazwy plików. Jeśli jednak żadna nazwa pliku nie zostanie zdefiniowana, odczytywane są informacje standardowego strumienia wejściowego. Wspomniane polecenia mogą być definiowane jako polecenie nazwa_ ´pliku lub polecenie < nazwa_pliku. Wynik jest taki sam. Nie dotyczy to jednak instrukcji wc (przedstawionej w przykładzie), ale również polecenia cat i innych. Zagadnienie nie wydaje się skomplikowane i jest znane osobom, które korzystały wcześniej z wiersza poleceń systemu DOS. Jest bardzo istotne podczas tworzenia skryptów powłoki, gdyż znacznie zwiększa ich elastyczność i łatwość wykorzystania.

81

Zobacz również • Receptura 2.6, „Zapisywanie wyniku w plikach innych katalogów”.

3.2. Umieszczenie danych w skrypcie Problem Działanie skryptu wymaga dostarczenia danych, które nie powinny być zapisane w oddzielnym pliku.

Rozwiązanie Należy zastosować mechanizm osadzania dokumentu (ang. here-document), dostarczający tekst z wiersza polecenia, a nie z pliku. Wykorzystanie tego rozwiązania w skrypcie pozwala na zapisanie przetwarzanych danych wraz z kodem tego skryptu. Oto przykład skryptu powłoki zapisanego w pliku o nazwie wew: $ cat wew # # Oto osadzony dokument: # grep $1 /dev/null $ if [ $? –eq 0 ] ; then echo jest ; else echo nie ma ; fi nie ma $

Przekierowanie strumienia wyjściowego do urządzenia /dev/null powoduje odrzucenie danych wynikowych — wyrzucenie ich do Kosza. Rozwiązanie uwzględniające kierowanie danych do urządzenia /dev/null jest również bardzo przydatne podczas pisania skryptu, który powinien być wykorzystywany w różnych systemach Linux i Unix, operujących różnymi odmianami polecenia grep. Istnieje bowiem pewne prawdopodobieństwo, że wśród różnych wersji instrukcji grep znajdzie się również taka, która nie będzie udostępniała opcji –q.

Zobacz również • Polecenie man grep. • Polecenie man regex (w systemach Linux, Solaris, HP-UX) lub man re_format (w systemach

BSD i Mac) — dostarczają one szczegółowych danych na temat wykorzystywanej biblioteki wyrażeń regularnych.

• Książka Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001). • Receptura 19.5, „Spodziewana zmiana eksportowanych wartości”.

7.4. Wyszukiwanie ciągu tekstowego niezależnie od wielkości liter Problem Chcemy wyszukać w pliku dziennika określone komunikaty (np. error). Aby mieć pewność, że zostaną wyodrębnione wszystkie tego typu informacje, podczas przeszukiwania pliku nie powinna być uwzględniana wielkość liter.

Rozwiązanie Dodanie do polecenia grep opcji –i powoduje zignorowanie informacji o wielkości liter. $ grep –i error plik.dziennika

178

|

Rozdział 7. Dodatkowe narzędzia powłoki I

Analiza W operacji wyszukiwania bez uwzględniania wielkości liter za dopasowane zostaną uznane ciągi o treści ERROR, error, Error, a także wszystkie inne ich warianty, jak ErrOr lub eRrOr. Opisana opcja jest szczególnie użyteczna w analizowaniu dokumentów, które mogą zawierać tekst o różnej wielkości znaków — na przykład gdy wyszukiwane są wyrazy, które mogą występować na początku zdania (przez co pierwsza litera wyrazu jest wielką literą) lub gdy wyszukiwane są adresy poczty elektronicznej.

Zobacz również • Polecenie man grep. • Polecenie man regex (w systemach Linux, Solaris, HP-UX) lub man re_format (w systemach

BSD i Mac) — dostarczają one szczegółowych danych na temat wykorzystywanej biblioteki wyrażeń regularnych. • Książka Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001). • Więcej informacji na temat wyszukiwania danych znajduje się w rozdziale 9., w części

poświęconej poleceniu find. • Receptura 19.5, „Spodziewana zmiana eksportowanych wartości”.

7.5. Przeszukiwanie danych przekazywanych w potoku Problem Chcemy odszukać określony ciąg tekstowy, ale ciąg ten nie jest zapisany w pliku. Jest on natomiast fragmentem listingu wynikowego pewnego polecenia lub elementem danych wyjściowych potoku poleceń.

Rozwiązanie Dane wynikowe należy przekazać za pomocą potoku do polecenia grep. $ potok | poleceń | grep

Analiza Jeśli w wywołaniu polecenia grep nie zostanie uwzględniona nazwa pliku, program odczyta dane ze standardowego strumienia wejściowego. Podobnie działa większość poprawnie przygotowanych narzędzi powłoki. Ta cecha właśnie czyni je tak użytecznymi elementami składowymi skryptów. Jeżeli działaniem polecenia grep mają być również objęte komunikaty o błędach, trzeba pamiętać, aby dane standardowego strumienia błędów wcześniejszego polecenia zostały skierowane do standardowego strumienia wyjściowego: $ gcc błędnykod.c 2>&1 | grep –i error

7.5. Przeszukiwanie danych przekazywanych w potoku

|

179

Powyższa instrukcja jest próbą skompilowania hipotetycznego błędnego kodu języka C. Przed przekazaniem danych do potoku (|) informacje standardowego strumienia błędów zostały przekierowane do strumienia wyjściowego (2>&1). Za pomocą potoku wszystkie dane zostały dostarczone do polecenia grep. Polecenie to próbuje wyodrębnić wszystkie wiersze zawierające ciąg error, nie uwzględniając w tej operacji wielkości liter (-i). Nic nie stoi na przeszkodzie, żeby skrypt przechwycił również wynik wykonania instrukcji grep. W jakim celu miałby to robić? Aby umożliwić dalsze ograniczenie zbioru wyników przeszukiwania. Załóżmy, że zadaniem skryptu jest ustalenie adresu e-mail Piotra Nowaka. $ grep –i nowak poczta/* ... zbyt wiele danych do analizowania; w Polsce mieszka wielu Nowaków ... $ !! | grep –i piotr grep –i nowak poczta/* | grep –i piotr ... wynik łatwiejszy do przeanalizowania ... $ !! | grep –i nowy grep –i nowak poczta/* | grep –i piotr | grep –i nowy Piotr Nowak, nowy

Ten sam rezultat można uzyskać, powielając w każdym poleceniu wcześniejszą instrukcję grep. Tutaj został jednak zastosowany niezwykle użyteczny operator odwołania historycznego (!!). Umożliwia on ponowienie ostatniego polecenia bez konieczności jego ponownego wprowadzania. Możliwe jest również uzupełnienie instrukcji o dodatkowe odwołania umieszczone za operatorem !! (tak jak to zostało pokazane we wcześniejszym przykładzie). Powłoka wyświetla treść wykonywanego polecenia, więc można zobaczyć, jaki ostatecznie jest rezultat zastąpienia operatora !! kodem instrukcji (więcej informacji na ten temat znajduje się w recepturze 18.2, „Powtarzanie ostatniego polecenia”). Dzięki mechanizmowi przetwarzania potokowego użytkownik może w łatwy i szybki sposób rozbudowywać potok, analizując dane wyjściowe pośrednich instrukcji i podejmując decyzję o tym, czy konieczne jest dołączenie kolejnej instrukcji grep, czy nie. Oczywiście, ten sam wynik można by uzyskać za pomocą pojedynczego polecenia grep i odpowiednio dobranego wyrażenia regularnego. Tworzenie potoku jest jednak znacznie łatwiejszym rozwiązaniem.

Zobacz również • Polecenie man grep. • Polecenie man regex (w systemach Linux, Solaris, HP-UX) lub man re_format (w systemach

BSD i Mac) — dostarczają one szczegółowych danych na temat wykorzystywanej biblioteki wyrażeń regularnych. • Książka Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001). • Receptura 2.15, „Łączenie dwóch programów przez wykorzystanie danych wyjściowych

jako wejściowych”. • Receptura 18.2, „Powtarzanie ostatniego polecenia”. • Receptura 19.5, „Spodziewana zmiana eksportowanych wartości”.

180

|

Rozdział 7. Dodatkowe narzędzia powłoki I

7.6. Odrzucenie niepotrzebnych danych z procedury wyszukiwania Problem Procedura wyszukiwania zwraca więcej informacji niż jest potrzebnych. Wśród nich jest wiele danych, które nie powinny występować w listingu wynikowym.

Rozwiązanie Listing wynikowy należy przekazać za pomocą potoku do polecenia grep –v z wyrażeniem opisującym tę treść, która jest niepotrzebna. Przyjmijmy, że wyszukiwaniem został objęty plik dziennika komunikatów (np. messages) oraz że interesują nas jedynie wpisy z grudnia. W pliku dziennika daty grudniowe są opisane za pomocą trzyliterowego skrótu angielskiej nazwy miesiąca (December) — Dec. Ponieważ jednak nie można mieć pewności, że w każdym przypadku jest wykorzystywany dokładny skrót Dec, konieczne jest użycie opcji –i, która zagwarantuje wyodrębnienie wszystkich ewentualnych wpisów z grudnia: $ grep –i dec plik.dziennika

Wśród danych wynikowych są jednak uwzględniane również frazy wymienione poniżej: ... error on Jan 01: not a decimal number error on Feb 13: base convert to Decimal warning on Mar 22: using only decimal numbers error on Dec 16: poszukiwany komunikat error on Jan 01: not a decimal number ...

Najłatwiejszym rozwiązaniem byłoby w tym przypadku przekazanie wyniku za pomocą potoku do kolejnego polecenia grep, które odrzuciłoby wszystkie wystąpienia frazy decimal: $ grep –i dec plik.dziennika | grep –vi decimal

Łączenie w ten sposób kilku instrukcji nie jest czymś wyjątkowym (gdy w treści pojawiają się nowe nieoczekiwane wiersze). Umożliwia ono wyodrębnienie tylko tej frazy, która rzeczywiście jest potrzebna: $ grep –i dec plik.dziennika | grep –vi decimal | grep –vi decimate

Analiza Wadą zaprezentowanego rozwiązania jest to, że eliminuje ono również niektóre komunikaty z grudnia (czyli komunikaty użyteczne), jeśli zawierają one w swej treści słowo decimal (odrzucane przez instrukcję grep –v). Opcja –v bywa bardzo przydatna, ale musi być właściwie zastosowana — trzeba pamiętać, że może eliminować użyteczne informacje.

7.6. Odrzucenie niepotrzebnych danych z procedury wyszukiwania

|

181

W omawianym przypadku właściwsze byłoby wykorzystanie wyrażenia regularnego, które wyodrębniłoby wszystkie wiersze zawierające grudniową datę. Wzorzec dopasowania musiałby więc uwzględniać słowo Dec, po którym występuje znak spacji i dwie cyfry: $ grep 'Dec [0-9][0-9]' plik.dziennika

Taka instrukcja często nie realizuje jednak powierzonego jej zadania, ponieważ w przypadku dat o jednocyfrowej liczbie dni mechanizm syslog dodaje jeden znak spacji wypełnienia. Pierwszy nawias kwadratowy musi więc zostać uzupełniony o dodatkowy znak spacji: $ grep 'Dec [0-9 ][0-9]' plik.dziennika

Całe wyrażenie zostało otoczone znakami apostrofu, aby uniknąć błędnej interpretacji zawartych w nim znaków spacji i treści nawiasów kwadratowych. Warto wyrobić sobie nawyk otaczania znakami apostrofu wszystkich wartości, które potencjalnie mogą zostać niewłaściwie zinterpretowane przez powłokę. Oczywiście, tę samą instrukcję można by zapisać w następujący sposób: $ grep Dec\ [0-9\ ][0-9] plik.dziennika

Jednak dodanie znaków lewego ukośnika znacznie zmniejsza czytelność kodu — trudno ustalić, w którym miejscu kończy się treść wyszukiwanego ciągu tekstowego i zaczyna nazwa pliku.

Zobacz również • Polecenie man grep. • Polecenie man regex (w systemach Linux, Solaris, HP-UX) lub man re_format (w systemach

BSD i Mac) — dostarczają one szczegółowych danych na temat wykorzystywanej biblioteki wyrażeń regularnych. • Książka Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001). • Receptura 19.5, „Spodziewana zmiana eksportowanych wartości”.

7.7. Wyszukiwanie z użyciem bardziej rozbudowanych wzorców Problem Zaimplementowany w poleceniu grep mechanizm wyrażeń regularnych umożliwia definiowanie rozbudowanych wzorców dopasowania, które gwarantują realizację nawet najbardziej wyrafinowanych operacji wyszukiwania. Wyrażenia regularne pozwalają na definiowanie wzorców dopasowania dla ciągów tekstowych. Każda litera alfabetu zapisana we wzorcu odpowiada takiemu samemu znakowi w ciągu tekstowym. Na przykład litera A odpowiada znakowi A w ciągu tekstowym, litera B znakowi B itd. Nie ma tu żadnych niespodzianek. W wyrażeniach regularnych można jednak stosować również wiele symboli specjalnych, które samodzielnie lub w połączeniu z innymi symbolami pozwalają na tworzenie bardziej skomplikowanych wzorców.

182

|

Rozdział 7. Dodatkowe narzędzia powłoki I

Zgodnie z wcześniejszą informacją każdy znak, który nie ma specjalnego znaczenia, odpowiada za dopasowania takiego samego znaku w analizowanym ciągu tekstowym (A odpowiada znakowi A itd.). Istotne jest również umiejscowienie znaków — wzorzec AB uznaje za dopasowane ciągi, w których za literą A występuje litera B. To również wydaje się oczywiste. Pierwszym ze znaków specjalnych jest znak kropki (.). Odpowiada on jednemu dowolnemu znakowi w analizowanym ciągu tekstowym. Wzorzec .... gwarantuje więc dopasowanie czterech dowolnych znaków, wzorzec A. zapewnia wyodrębnienie ciągu składającego się z litery A i dowolnego następującego po niej znaku, a wzorzec .A. uznaje za dopasowany ciąg, w którym występuje dowolny znak, za nim litera A i kolejny dowolny znak (niekoniecznie taki sam, jak pierwszy). Znak gwiazdki (*) definiuje zero lub większą liczbę powtórzeń poprzedzającego go znaku. Zatem wzorzec A* opisuje zero lub więcej znaków A, a wzorzec .* wyodrębnia zero lub więcej dowolnych znaków (np. abcdefg, aaaaabc, jik;lfs lub nawet pusty ciąg tekstowy). Jakie jest więc znaczenie wzorca ..*? Odpowiada on jednemu dowolnemu znakowi, za którym występuje zero lub więcej dowolnych znaków (innymi słowy opisuje jeden lub więcej dowolnych znaków). Pusty ciąg tekstowy nie spełnia więc tego założenia. Znak ^ wyznacza początek wiersza tekstu, a znak dolara ($) koniec tego wiersza. Wzorzec o treści ^$ uznaje za dopasowany pusty wiersz tekstu (początek i koniec wiersza bez jakichkolwiek znaków w środku). Co zrobić, jeśli we wzorcu musi być uwzględniony rzeczywisty znak kropki, dolara czy inny symbol specjalny? Wystarczy poprzedzić go znakiem lewego ukośnika (\). Wzorzec atom. wyodrębnia ciąg atom, za którym może wstępować jeden dowolny znak. Natomiast wzorzec atom\. odnosi się do ciągu atom, za którym jest dołączony znak kropki (czyli wyszukuje słowo występujące na końcu zdania lub po prostu zakończone znakiem kropki). Zbiór znaków otoczony znakami nawiasu kwadratowego (np. [abc]) odpowiada jednemu dowolnemu znakowi ze zbioru (np. a lub b, lub c). Jeśli pierwszym znakiem za znakiem nawiasu otwierającego jest symbol ^, za dopasowany jest uznawany każdy znak, który nie występuje na wymienionej liście. Na przykład zapis [AaĄąEeĘęIiOoÓóUuYy] odnosi się do dowolnej z samogłosek, a wzorzec [^AaĄąEeĘęIiOoÓóUuYy] dopasowuje każdy znak, który nie jest samogłoską. Nie można jednak stwierdzić, że w drugim z wymienionych przypadków za dopasowane zostaną uznane spółgłoski. Wśród znaków spełniających kryteria dopasowania są zarówno spółgłoski, jak i wszystkie znaki interpunkcyjne i znaki specjalne (które nie są ani samogłoskami, ani spółgłoskami). Na szczególną uwagę zasługuje jeszcze jeden mechanizm — mechanizm powtórzeń zapisywany jako \{n,m\}. Symbol n wyznacza minimalną liczbę powtórzeń, a m maksymalną ich liczbę. Zapis typu \{n\} oznacza „dokładnie n powtórzeń”, a zapis \{n,\} wskazuje co najmniej m powtórzeń. Na przykład wyrażenie regularne o treści A\{5\} umożliwia dopasowanie ciągu pięciu liter A. Natomiast wyrażenie A\{5,\} opisuje pięć lub więcej liter A.

7.7. Wyszukiwanie z użyciem bardziej rozbudowanych wzorców

|

183

7.8. Wyszukiwanie numeru NIP Problem Chcemy opracować wzorzec opisujący numer identyfikacji podatkowej (NIP). Identyfikator ten składa się z dziesięciu cyfr zapisywanych w grupach po trzy, trzy, dwie i dwie cyfry (np. 123-456-78-90). Ponieważ niekiedy jest on zapisywany bez znaków myślnika, wyrażenie musi uznawać taki zapis za opcjonalny.

Rozwiązanie $ grep '[0-9]\{3\}-\{0,1\}[0-9]\{3\}-\{0,1\}[0-9]\{2\}-\{0,1\}[0-9]\{2\}' plik.danych

Analiza Wyrażenia regularne o podobnej treści są często żartobliwie nazywane wyrażeniami tylko do zapisu, gdyż analiza sposobu ich działania jest niezwykle trudna, jeśli nie niemożliwa. Podzielimy więc ten zapis na elementy składowe, aby można było lepiej się mu przyjrzeć. Wykorzystując wyrażenia regularne w kodzie skryptu, trzeba pamiętać o dołączeniu komentarza, który wyjaśni przeznaczenie danej instrukcji. Dodanie kilku znaków spacji do wyrażenia poprawiłoby jego czytelność, ale równocześnie zmieniłoby znaczenie — aby ciąg tekstowy został uznany za zgodny ze wzorcem, musiałby zawierać znaki spacji we wskazanych miejscach. Pamiętając o tym, rozdzielmy poszczególne człony wyrażenia znakami spacji jedynie na czas analizy tegoż wyrażenia: [0-9]\{3\}

-\{0,1\}

[0-9]\{3\}

-\{0,1\}

[0-9]\{2\}

-\{0,1\}

[0-9]\{2\}

Pierwsza sekcja przekazuje informację „dowolna cyfra” powtarzana „dokładnie trzy razy”. Następna sekcja przekazuje informację „znak myślnika” występujący „zero razy lub jeden raz”. Trzecia i czwarta grupa symboli ma takie samo znaczenie jak pierwsza i druga sekcja wyrażenia. Piąty człon jest definicją „dowolnej cyfry” powtarzanej dwa razy. W szóstej części został opisany „znak myślnika” występujący „zero razy lub jeden raz”. Ostatnia część wyrażenia odnosi się do kolejnej grupy dwóch cyfr.

Zobacz również • Polecenie man regex (w systemach Linux, Solaris, HP-UX) lub man re_format (w systemach

BSD i Mac) — dostarczają one szczegółowych danych na temat wykorzystywanej biblioteki wyrażeń regularnych. • Podrozdział 3.2 z książki Nelsona H.F. Beebe’a i Arnolda Robbinsa Programowanie skryptów

powłoki (Helion 2005) zawiera informacje na temat wyrażeń regularnych i narzędzi nimi operujących. • Książka Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001). • Receptura 19.5, „Spodziewana zmiana eksportowanych wartości”.

184

|

Rozdział 7. Dodatkowe narzędzia powłoki I

7.9. Wykorzystanie polecenia grep do wyszukiwania informacji w zarchiwizowanych plikach Problem Chcemy przeszukać grupę zarchiwizowanych plików. Czy trzeba je wcześniej rozpakować?

Rozwiązanie Nie ma takiej potrzeby, jeśli w systemie są zainstalowane narzędzia zgrep, zcat lub gzcat. Program zgrep jest pewną odmianą polecenia grep, które obejmuje swoim działaniem zarówno pliki skompresowane, jak i nieskompresowane (obsługiwane formaty kompresji zależą od systemu operacyjnego). Często znajduje zastosowanie w operacjach wyszukiwania komunikatów usługi syslog w systemie Linux. Mechanizm rotacji dzienników pozostawia bieżący plik nieskompresowany (można go więc wykorzystać), ale analiza dzienników archiwalnych wymaga zastosowania polecenia: $ zgrep 'wyszukiwany tekst' /var/log/messages*

Instrukcja zcat jest po prostu wersją polecenia cat, które może operować skompresowanymi i nieskompresowanymi plikami (obsługiwane formaty kompresji zależą od systemu operacyjnego). Może poprawnie interpretować większą liczbę formatów kompresji niż program zgrep i może być stosowana w większej liczbie systemów operacyjnych. Bywa wykorzystywana w procedurach odzyskiwania uszkodzonych plików archiwum, ponieważ jej zadanie polega na wyświetleniu wszystkich możliwych do uzyskania informacji, a nie poprzestawaniu na wygenerowaniu komunikatu o błędzie, jak to ma miejsce w przypadku polecenia gunzip lub innych podobnych narzędzi. Program gzcat działa podobnie jak zcat. Obydwa rozwiązania różnią się rodzajami licencji (komercyjna i darmowa) oraz stopniem zgodności z wcześniejszymi wersjami oprogramowania. $ zcat /var/log/messages.1.gz

Analiza Do bezpośredniego wyświetlania zawartości skompresowanych plików można również wykorzystać polecenie less. Co czyni je bardzo użytecznym. Więcej informacji na ten temat znajduje się w recepturze 8.15, „Dodatkowe funkcje polecenia less”.

Zobacz również • Receptura 8.6, „Kompresja plików”. • Receptura 8.7, „Rozpakowywanie plików”. • Receptura 8.15, „Dodatkowe funkcje polecenia less”.

7.9. Wykorzystanie polecenia grep do wyszukiwania informacji w zarchiwizowanych plikach

|

185

7.10. Zachowanie części listingu wynikowego Problem Chcemy zachować określone fragmenty listingu wynikowego i odrzucić pozostałą część.

Rozwiązanie Poniższa instrukcja zapewnia wyświetlenie pierwszego wyrazu z każdego wiersza danych wejściowych: $ awk '{print $1}' plik.wejściowy

Wyrazy są rozdzielane dowolnymi znakami odstępu. Narzędzie awk odczytuje informacje z pliku o podanej nazwie lub dostarczone za pomocą standardowego strumienia wejściowego (jeśli nazwa pliku nie została określona). Możliwe jest więc również przekierowanie danych z pliku w następujący sposób: $ awk '{print $1}' < plik.wejściowy

Nic również nie stoi na przeszkodzie, żeby dane zostały przekazane za pomocą potoku: $ cat plik.wejściowy | awk '{print $1}'

Analiza Program awk można wykorzystywać na wiele różnych sposobów. Najłatwiejszą metodą jest wyświetlenie jednego pola lub większej liczby pól ze zbioru danych wejściowych. Poszczególne pola są wydzielane przez znaki odstępu (lub inne znaki określone za pomocą opcji –F) i numerowane, począwszy od wartości 1. Pole o indeksie $0 reprezentuje cały wiersz danych wejściowych. Program awk udostępnia kompletny język programowania, a tworzone za jego pomocą skrypty bywają bardzo skomplikowane. Przedstawione rozwiązanie jest jednym z podstawowych.

Zobacz również • Polecenie man awk. • http://www.faqs.org/faqs/computer-lang/awk/faq. • Książka Arnolda Robbinsa Effective awk Programming (O’Reilly 2001). • Książka Arnolda Robbinsa i Dale’a Dougherty’ego sed i awk (Helion 2002).

186

|

Rozdział 7. Dodatkowe narzędzia powłoki I

7.11. Zachowanie fragmentu wiersza wynikowego Problem Chcemy zachować tylko część wiersza wynikowego (np. pierwsze i ostatnie słowa). Przykładem może tu być chęć wyświetlenia tego fragmentu listingu wynikowego polecenia ls, który zawiera prawa dostępu do plików oraz nazwy tych plików. Wszystkie pozostałe dane generowane po wprowadzeniu instrukcji ls –l nie są potrzebne. Sama instrukcja ls nie udostępnia żadnych opcji, które pozwoliłyby na ograniczenia listingu wynikowego.

Rozwiązanie Wynik wykonania polecenia ls należy przekazać za pomocą potoku do programu awk, który wyodrębni potrzebne pola. $ ls -l | awk '{print $1, $NF}' razem 32 -rwxrw-r-- checkfile -rwxrw-r-- checkstr -rwxrw-r-- dashes -rwxrw-r-- dbinit.1 -rw-rw-r-- dbinit.2 -rwxr--r-- rpncalc -rwxrw-r-- strvsnum -rw-rw-r-- trackmatch $

Analiza Przeanalizujmy dane dostarczane przez polecenie ls –l. Jeden wiersz listingu ma treść zbliżoną do zamieszczonej poniżej. drwxr-xr-x

34 uzytkownik grupa

4096 2005-01-15 09:54 var

Doskonale nadaje się ona do przetwarzania za pomocą programu awk (domyślnie sekcje rozdzielane za pomocą znaków odstępu są uznawane przez program awk za oddzielne pola). W listingu wynikowym instrukcji ls –l prawa dostępu są zapisane w pierwszym polu, a pole ostatnie zawiera nazwę pliku. Do wyświetlenia nazwy pliku została wykorzystana pewna sztuczka. Poszczególne pola są numerowane kolejno przez polecenie awk i opisywane za pomocą symboli $1, $2, $3 itd. Program awk udostępnia również wbudowaną zmienną NF, która przechowuje informację o liczbie pól bieżącego wiersza. Wartość zmiennej $NF jest zatem jednocześnie numerem ostatniego pola (np. listing wynikowy instrukcji ls składa się ośmiu kolumn, więc w zmiennej NF jest przechowywana wartość 8; odwołanie $NF odnosi się z kolei do ósmego pola wiersza wejściowego, które w prezentowanym przykładzie jest nazwą pliku). Odczytując wartość zmiennej, awk nie dołącza się znaku $ (tak, jak ma to miejsce w skryptach powłoki). Ciąg NF jest poprawnym odwołaniem do zmiennej. Dodanie znaku $ ma na celu zmianę znaczenia wartości z „informacji o liczbie pól bieżącego wiersza” na „ostatnie pole bieżącego wiersza”.

7.11. Zachowanie fragmentu wiersza wynikowego

|

187

Zobacz również • Polecenie man awk. • http://www.faqs.org/faqs/computer-lang/awk/faq. • Książka Arnolda Robbinsa Effective awk Programming (O’Reilly 2001). • Książka Arnolda Robbinsa i Dale’a Dougherty’ego sed i awk (Helion 2002).

7.12. Odwrócenie kolejności słów w każdym wierszu Problem Chcemy wyświetlić wyrazy wiersza wejściowego w odwrotnej kolejności.

Rozwiązanie $ awk '{ > for (i=NF; i>0; i--) { > printf "%s ", $i; > } > printf "\n" > }'

Oczywiście, znaków > nie należy wprowadzać, są one wyświetlane przez powłokę w celu poinformowania użytkownika o tym, że polecenie nie zostało zakończone (interpreter oczekuje na wprowadzenie kończącego znaku apostrofu). Kod programu awk jest otoczony znakami apostrofu, więc powłoka umożliwia zdefiniowanie kilku wierszy instrukcji, rozpoczynając każdą od znaku >, aż do chwili wprowadzenia zamykającego znaku apostrofu. W celu zwiększenia czytelności kodu został on zapisany w kilku wierszach, choć można go również wprowadzić w następujący sposób: $ awk '{for (i=NF; i>0; i--) {printf "%s ", $i;} printf "\n" }'

Analiza Składnia pętli for w programach awk jest bardzo zbliżona do zapisu instrukcji for w języku C. Użytkownik dysponuje również mechanizmem printf (przeznaczonym do formatowania danych wyjściowych) działającym analogicznie jak jego odpowiednik w języku C (oraz jak polecenie powłoki o takiej samej nazwie). Pętla for została wykorzystana do iteracyjnego pobrania i wyświetlenia wyrazów, w kolejności od ostatniego do pierwszego. Celowo pierwsza instrukcja printf nie została uzupełniona o symbol \n, gdyż dzięki temu ciąg wynikowy został wyświetlony w jednym wierszu, a nie w kilku. Znak nowego wiersza jest dodawany po zakończeniu kodu pętli. Odwołanie do zmiennej $i w programie awk znacznie różni się od odwołań charakterystycznych dla interpretera bash. Umieszczenie odwołania $i w skrypcie powłoki oznacza pobranie wartości zapisanej w zmiennej i. W programach awk, podobnie jak w innych językach programowania, odwołanie do wartości przechowywanej w zmiennej i sprowadza się do podania nazwy tej zmiennej, czyli samej litery i. Jakie jest więc znaczenie ciągu $i w kodzie awk? Wartość zapisana w zmiennej jest pobierana jako liczba, a wyrażenie rozpoczynające się od znaku dolara

188

|

Rozdział 7. Dodatkowe narzędzia powłoki I

zostaje zinterpretowane jako odwołanie do pola (lub słowa) wiersza wejściowego — odwołanie do i-tego pola. Ponieważ instrukcja pętli zapewnia zmniejszanie wartości i od numeru odpowiadającego ostatniemu polu do numeru pierwszego pola, słowa są na ekranie wyświetlane w kolejności od ostatniego do pierwszego.

Zobacz również • Polecenie man 1 printf. • Polecenie man awk. • http://www.faqs.org/faqs/computer-lang/awk/faq. • Książka Arnolda Robbinsa Effective awk Programming (O’Reilly 2001). • Książka Arnolda Robbinsa i Dale’a Dougherty’ego sed i awk (Helion 2002). • Punkt „Instrukcja printf” w dodatku A.

7.13. Sumowanie zbioru wartości Problem Chcemy zsumować kilka wartości liczbowych, które nie zawsze są zapisywane w oddzielnych wierszach.

Rozwiązanie Zarówno do wydzielania liczb, jak i ich sumowania można wykorzystać narzędzie awk. Oto przykład zsumowania liczb odpowiadających rozmiarom plików, udostępnianych przez polecenie ls –l. $ ls –l | awk '{sum += $5} END {print sum}'

Analiza Sumowaniu podlega piąte pole listingu wygenerowanego przez instrukcję ls –l. Wiersz wspomnianego listingu ma treść zbliżoną do poniższej: -rw-r--r--

1 marek

users

116 2005-09-18 13:17 plikdanych

Pojedynczy wiersz składa się więc z następujących pól: praw dostępu, liczby dowiązań, nazwy właściciela, nazwy grupy, rozmiaru (wyrażonego w bajtach), daty, czasu oraz nazwy pliku. Ponieważ opracowywane rozwiązanie odnosi się jedynie do wielkości pliku, w programie awk zostało wykorzystane odwołanie do stosownego pola ($5). Zaproponowana instrukcja obejmuje dwa bloki programu awk ujęte w nawiasy klamrowe ({}). Kod programu awk może się bowiem składać z wielu niezależnych bloków. Blok instrukcji poprzedzony słowem kluczowym END jest wykonywany tylko jeden raz, po zakończeniu pozostałej części programu. W analogiczny sposób można oznaczyć blok kodu słowem BEGIN, co gwarantuje wykonanie go przed odczytaniem danych wejściowych. Sekcja BEGIN bywa użyteczna podczas inicjowania wartości zmiennych i mogła zostać wykorzystana również w prezentowanym 7.13. Sumowanie zbioru wartości

|

189

rozwiązaniu do wyzerowania wartości zmiennej sum. W tym przypadku został jednak wykorzystany fakt, że mechanizm awk gwarantuje powołanie pustej zmiennej. Analizując listing wynikowy polecenia ls –l, nietrudno zauważyć, że w jego pierwszym wierszu znajduje się informacja o całkowitej liczbie bloków dyskowych, która nie jest zgodna z formatem pozostałych wierszy (wiersz razem). Istnieją dwie metody rozwiązania tego problemu. Można udawać, że tego wiersza nie ma — ta technika została wykorzystana w przedstawionym przykładzie. W zbędnym wierszu nie występuje piąte pole, więc odwołanie do pola $5 zwróci pusty ciąg, który nie zmieni wartości sumy. Metoda bardziej uniwersalna polega natomiast na wykluczeniu niepotrzebnego pola. Wystarczy w tym celu przed dostarczeniem danych do programu awk przekazać je do polecenia grep. $ ls -l | grep -v '^razem' | awk '{sum += $5} END {print sum}'

Podobną operację można zdefiniować w samym kodzie awk: $ ls -l | awk '/^razem/{getline} {sum += $5} END {print sum}'

Ciąg ^razem jest wyrażeniem regularnym, które odpowiada literom r, a, z, e, m występującym na początku wiersza (znak ^ wskazuje początek wiersza). Skojarzony z wyrażeniem blok kodu zostanie wykonany dla każdego wiersza danych wejściowych, który spełnia kryteria dopasowania tego wyrażenia. Drugi blok kodu (zwiększenie wartości zmiennej sum) nie jest poprzedzony żadnym tekstem, co stanowi dla awk informację o obowiązku wykonania instrukcji w odniesieniu do każdego wiersza danych wejściowych (niezależnie od tego, czy treść wiersza spełnia kryteria dopasowania wzorca). Celem całego przedsięwzięcia jest jednak przecież wykluczenie wiersza ze słowem razem z operacji sumowania. Z tego względu w bloku wyrażenia ^razem została zapisana instrukcja getline, która pobiera jeden wiersz z danych wejściowych. Instrukcje drugiego bloku kodu są więc wykonywane w odniesieniu do nowego wiersza danych. Po wykonaniu polecenia getline wcześniejsze wyrażenie regularne nie jest ponownie interpretowane. Analizowane są natomiast wyrażenia występujące za instrukcją getline. Kolejność bloków kodu awk jest tu bardzo ważna.

Zobacz również • Polecenie man awk. • http://www.faqs.org/faqs/computer-lang/awk/faq. • Książka Arnolda Robbinsa Effective awk Programming (O’Reilly 2001). • Książka Arnolda Robbinsa i Dale’a Dougherty’ego sed i awk (Helion 2002).

7.14. Zliczanie wartości tekstowych Problem Chcemy zliczyć wystąpienia kilku różnych ciągów tekstowych, w tym również ciągów, których wartość nie jest uprzednio znana. Innymi słowy, zadanie nie polega na ustaleniu liczby znanych fraz, ale na wyznaczeniu liczby pewnych ciągów tekstowych, które występują w bloku danych i w czasie zliczania są nieznane użytkownikowi. 190

|

Rozdział 7. Dodatkowe narzędzia powłoki I

Rozwiązanie Do zliczania trzeba wykorzystać tablice asocjacyjne (zwane też tablicami mieszającymi) narzędzia awk. Przykładem może być poniższy kod, który ustala, ile plików należących do różnych użytkowników jest zapisanych w systemie. Nazwa użytkownika jest trzecim elementem każdego wiersza wygenerowanego przez polecenie ls –l. Pole to ($3) zostanie wykorzystane jako indeks w tablicy, a związana z tym indeksem wartość będzie inkrementowana. # # plik receptury: asar.awk # NF > 7 { user[$3]++ } END { for (i in user) { printf "liczba plików użytkownika %s: %d\n", i ,user[i] } }

Wywołanie programu awk odbiega nieco od wcześniejszych operacji tego typu. Ponieważ kod skryptu jest dość rozbudowany, został on zapisany w oddzielnym pliku. Aby przekazać do programu awk informację o konieczności odczytania treści skryptu z pliku, trzeba dołączyć do instrukcji opcję –f: $ ls -lR /usr/local | awk liczba plików użytkownika liczba plików użytkownika liczba plików użytkownika $

-f asar.awk apache: 237 marek: 1068 root: 1464

Analiza Danymi wejściowymi dla skryptu jest listing generowany przez polecenie ls –lR. Warunek NF>7 odpowiada za kwalifikowanie informacji do dalszego przetwarzania, odrzucając wiersze, które nie zawierają nazwy pliku, puste wiersze rozdzielające podkatalogi oraz wiersze z informacjami o liczbie zajętych bloków dyskowych. Eliminacja jest możliwa dzięki temu, że w wymienionych wierszach nie ma tylu pól, ile występuje w użytecznych liniach. Zapisane przed nawiasem klamrowym wyrażenie NF>7 nie jest otoczone znakami ukośnika, co oznacza, że nie jest ono wyrażeniem regularnym. Jest natomiast wyrażeniem logicznym, takim samym jakie stosuje się w instrukcjach warunkowych if. Wynikiem może być więc jedynie wartość true lub false. Zmienna NF jest specjalną wbudowaną zmienną, która przechowuje informację o liczbie pól aktualnie przetwarzanego wiersza danych wejściowych. Zatem instrukcje zapisane wewnątrz nawiasu klamrowego zostaną wykonane tylko w przypadku, w którym w wierszu danych wejściowych występuje więcej niż siedem pól. Najważniejszą instrukcją skryptu jest następujący wiersz: user[$3]++

Jako indeks w tablicy została tu wykorzystana nazwa właściciela pliku (np. root). Taka struktura danych nazywa się tablicą asocjacyjną. Podobne przeznaczenie mają tablice mieszające, lecz ich zadanie polega na przypisywaniu każdemu unikatowemu ciągowi tekstowemu odpowiedniego 7.14. Zliczanie wartości tekstowych

|

191

indeksu liczbowego. Cała procedura obsługi tablic asocjacyjnych jest wykonywana przez mechanizm awk automatycznie. Programista nie musi nawet definiować instrukcji porównywania ciągów lub przeszukiwania tablic. Mogłoby się wydawać, że po utworzeniu tablicy asocjacyjnej, wystąpią problemy z pozyskaniem z niej wartości. W języku awk została jednak przewidziana specjalna wersja pętli for, która doskonale nadaje się do tego typu zadań. Standardową instrukcję for(i=0; i big) { big=user[i];} } return big } NF > 7 { user[$3]++ } END {

192

|

Rozdział 7. Dodatkowe narzędzia powłoki I

# skalowanie maxm = max(user); for (i in user) { #printf "liczba plików użytkownika %s: %d\n", i, user[i] scaled = 60 * user[i] / maxm ; printf "%-10.10s [%8d]:", i, user[i] for (i=0; i&1 &

lub nohup skryptdemona >>/var/log/admin.log 2>&1 . Jak rozwiązać problem strumienia STDIN? Najbardziej „eleganckim” sposobem jest zamknięcie deskryptora pliku. Składnia stosownego polecenia w powłoce bash przypomina operację przekierowania, ale z wykorzystaniem znaku myślnika zamiast nazwy pliku (0&2 }

W zasadniczej treści skryptu można wówczas wykorzystać poniższe instrukcje: if [ $# -lt 1 ] then usage fi

Analiza Funkcje mogą być definiowane na wiele różnych sposobów ([ function ] nazwa() polecenia-składowe [ przekierowania ]). Każda z przedstawionych poniżej definicji funkcji jest poprawna. function usage() { printf "użycie: %s [ -a | -b ] plik1 ... plikn\n" $0 >&2 } function usage { printf "użycie: %s [ -a | -b ] plik1 ... plikn\n" $0 >&2 } usage() { printf "użycie: %s [ -a | -b ] plik1 ... plikn\n" $0 >&2 } usage () { printf "użycie: %s [ -a | -b ] plik1 ... plikn\n" $0 >&2 }

238

|

Rozdział 10. Dodatkowe mechanizmy skryptowe

W treści nagłówku musi występować słowo kluczowe function lub nazwa funkcji musi zostać zakończona znakami nawiasu. W przypadku wykorzystania słowa function znaki() są opcjonalne. Uwzględnianie słowa kluczowego function jest rozwiązaniem zalecanym, ponieważ znacznie zwiększa czytelność kodu, a ponadto ułatwia wykonywanie instrukcji grep (np. aby sporządzić listę funkcji wykorzystywanych w skrypcie, wystarczy wprowadzić polecenie grep '^function' skrypt). Definicja funkcji musi być zamieszczona w początkowej części skryptu, a przynajmniej przed jej wywołaniem. Sama definicja jest w praktyce interpretowana jako kolejne polecenie powłoki bash, a funkcja zostaje uznana za zdefiniowaną po wykonaniu tego polecenia. Próba wywołania funkcji przed jej zdefiniowaniem powoduje wygenerowanie komunikatu o treści command not found. Z tego względu zawsze warto zapisywać kod funkcji przed jakimikolwiek innymi poleceniami skryptu. Działanie zaprezentowanej funkcji nie jest szczególnie skomplikowane — ogranicza się do wykonania instrukcji printf. Niemniej dzięki zapisaniu treści komunikatu tylko w jednym miejscu ewentualne dodanie nowej opcji do skryptu będzie wymagało wprowadzenia zmian tylko w jednej instrukcji, a nie w kilku miejscach skryptu. Poza ciągiem formatu jedynym parametrem polecenia printf jest zmienna $0, która przechowuje nazwę uruchomionego skryptu. Aby wyodrębnić jedynie ostatni człon ścieżki dostępu do skryptu, można zastosować wyrażenie $(basename $0). Ponieważ generowany komunikat jest informacją o błędzie, dane wyjściowe instrukcji printf zostały przekierowane do standardowego strumienia błędów. Podobne przekierowanie można również zdefiniować poza blokiem funkcji, co gwarantuje przekazanie wszystkich wygenerowanych w niej danych do odpowiedniego strumienia. function usage() { printf "użycie: %s [ -a | -b ] plik1 ... plikn\n" $0 >&2 } >&2

Zobacz również • Receptura 7.1, „Wyszukiwanie określonego ciągu w pliku tekstowym”. • Receptura 16.13, „Utworzenie lepszego polecenia cd”. • Receptura 16.14, „Utworzenie katalogu i przejście do niego w jednym kroku”. • Receptura 19.14, „Unikanie komunikatów «command not found» podczas korzystania

z funkcji”.

10.5. Wykorzystanie funkcji. Parametry i zwracane wartości Problem Chcemy skorzystać z funkcji, do której trzeba przekazać pewne wartości. W jaki sposób przekazuje się parametry do funkcji? Jak przekazać wynik wykonania funkcji? 10.5. Wykorzystanie funkcji. Parametry i zwracane wartości

|

239

Rozwiązanie W przeciwieństwie do składni wielu języków programowania parametry funkcji powłoki nie mogą być otoczone znakami nawiasu. Wszystkie parametry funkcji bash muszą zostać wymienione bezpośrednio za nazwą tej funkcji, a poszczególne z nich muszą zostać rozdzielone znakami spacji (podobnie jak w przypadku wywoływania skryptu lub polecenia). Jeśli to konieczne, muszą również zostać odpowiednio zabezpieczone znakami apostrofu lub lewego ukośnika! # definicja funkcji function max () { ... } # # wywołanie funkcji # max 128 $SIM max $VAR $SIM

Aby zwrócić wynik wykonania funkcji, można wykorzystać dwa rozwiązania. Pierwsze polega na przypisaniu odpowiedniej wartości do zmiennej w kodzie samej funkcji. Zmienne te są zmiennymi globalnymi, dostępnymi w całym skrypcie, chyba że zostaną zadeklarowane jako lokalne dla funkcji za pomocą słowa kluczowego local. # plik receptury: func_max.1 # definicja funkcji function max () { local HIDN if [ $1 -gt $2 ] then BIGR=$1 else BIGR=$2 fi HIDN=5 }

Oto przykład użycia funkcji: # wywołanie funkcji max 128 $SIM # wykorzystanie wyniku echo $BIGR

Drugi sposób polega na zastosowaniu instrukcji echo lub printf w celu przekazania danych wynikowych do standardowego strumienia wyjściowego. Jednak w przypadku wykorzystania tej techniki wywołanie funkcji musi być zapisane wewnątrz konstrukcji $( ). Dane wynikowe mogą wówczas zostać przechwycone i potraktowane jako wynik wykonania instrukcji. W przeciwnym przypadku zostaną wyświetlone na ekranie. # plik receptury: func_max.2 # definicja funkcji function max () { if [ $1 -gt $2 ] then echo $1 else echo $2 fi }

240 |

Rozdział 10. Dodatkowe mechanizmy skryptowe

Oto przykład użycia funkcji: # wywołanie funkcji BIGR=$(max 128 $SIM) # wykorzystanie wyniku echo $BIGR

Analiza Dołączenie parametrów do wywołania funkcji przypomina wywołanie dowolnego skryptu powłoki. Poszczególne parametry są po prostu kolejnymi słowami wiersza polecenia. Odwołanie do parametrów w kodzie funkcji ma taki sam charakter, jak odwołanie do parametrów wiersza polecenia — wymaga zastosowania składni $1, $2 itd. Nie dotyczy to jedynie zmiennej $0, która zawsze przechowuje nazwę wykonywanego skryptu. Po zakończeniu funkcji zmienne $1, $2 nadal przechowują parametry przekazane do skryptu podczas uruchomienia. Bardzo interesująca dla opisywanego zagadnienia jest również tablica $FUNCNAME. Sama nazwa $FUNCNAME odpowiada odwołaniu do zerowego elementu tej tablicy, który przechowuje informację na temat aktualnie wykonywanej funkcji. Innymi słowy odwołanie $FUNCNAME pełni w funkcji tę samą rolę co zmienna $0 w skrypcie (z pominięciem całej ścieżki dostępu do pliku). Pozostałe elementy tablicy odpowiadają stosowi wywołania funkcji, z funkcją główną (main) zapisaną w końcowym elemencie. Opisana zmienna tablicowa jest dostępna tylko w czasie wykonywania funkcji. W prezentowanym przykładzie powołana została zmienna $HIDN (nieistotna dla pracy skryptu), której zasięg ogranicza się jedynie do kodu funkcji. Przypisanie jej jakiejkolwiek wartości w ramach funkcji nie umożliwia wykorzystania tej wartości w innym miejscu skryptu. Jest to więc zmienna lokalna funkcji — zostaje powołana w chwili wywołania funkcji i jest usuwana wraz z zakończeniem pracy funkcji. Technika zwracania wartości polegająca na przypisywaniu wyników zmiennym jest znacznie bardziej efektywna i pozwala na przekazywanie dużych ilości danych (można powoływać wiele zmiennych), ale ma pewne wady. Wymaga zgodności nazw zmiennych w funkcji i pozostałej części skryptu, co z kolei znacznie utrudnia zarządzanie skryptem. Druga z metod — wykorzystanie strumienia wyjściowego jako mechanizmu przekazywania wyniku — nie narzuca obowiązku dopasowania nazw zmiennych. Jednak jej zastosowanie jest bardzo ograniczone, ponieważ w przypadku zwrócenia większej ilości informacji w zasadniczej części skryptu trzeba zawrzeć instrukcje właściwego ich interpretowania. Które rozwiązanie jest więc korzystniejsze? Podobnie jak we wszystkich zagadnieniach inżynierskich, tak i w tym trzeba przeanalizować wszystkie „za” i „przeciw”, a następnie wybrać technikę najwłaściwszą dla określonych potrzeb.

Zobacz również • Receptura 1.6, „Cudzysłowy i apostrofy w instrukcjach powłoki”. • Receptura 16.4, „Chwilowa zmiana wartości $PATH”.

10.5. Wykorzystanie funkcji. Parametry i zwracane wartości

|

241

10.6. Przechwytywanie przerwań Problem Chcemy przygotować skrypt, który będzie przechwytywał sygnały systemowe i odpowiednio na nie reagował.

Rozwiązanie Aby wyznaczyć procedury przechwytywania sygnałów, należy wykorzystać narzędzie trap. Najpierw jednak warto się zapoznać z wykazem sygnałów, które mogą zostać przechwycone. Stosowny listing generuje polecenie trap –l (lub kill –l). Jego zawartość może się różnić w poszczególnych systemach operacyjnych. # NetBSD $ trap –l 1) SIGHUP 5) SIGTRAP 9) SIGKILL 13) SIGPIPE 17) SIGSTOP 21) SIGTTIN 25) SIGXFSZ 29) SIGINFO # Linux $ trap -l 1) SIGHUP 5) SIGTRAP 9) SIGKILL 13) SIGPIPE 18) SIGCONT 22) SIGTTOU 26) SIGVTALRM 30) SIGPWR 36) SIGRTMIN+2 40) SIGRTMIN+6 44) SIGRTMIN+10 48) SIGRTMIN+14 52) SIGRTMAX-12 56) SIGRTMAX-8 60) SIGRTMAX-4 64) SIGRTMAX

2) 6) 10) 14) 18) 22) 26) 30)

SIGINT SIGABRT SIGBUS SIGALRM SIGSTP SIGTTOU SIGVTALRM SIGUSR1

3) 7) 11) 15) 19) 23) 27) 31)

SIGQUIT SIGEMT SIGSEGV SIGTERM SIGCONT SIGIO SIGPROF SIGUSR2

4) 8) 12) 16) 20) 24) 28) 32)

SIGILL SIGFPE SIGSYS SIGURG SIGCHLD SIGXCPU SIGWINCH SIGPWR

2) 6) 10) 14) 19) 23) 27) 31) 37) 41) 45) 49) 53) 57) 61)

SIGINT SIGABRT SIGUSR1 SIGALRM SIGSTOP SIGURG SIGPROF SIGSYS SIGRTMIN+3 SIGRTMIN+7 SIGRTMIN+11 SIGRTMIN+15 SIGRTMAX-11 SIGRTMAX-7 SIGRTMAX-3

3) 7) 11) 15) 20) 24) 28) 34) 38) 42) 46) 50) 54) 58) 62)

SIGQUIT SIGBUS SIGSEGV SIGTERM SIGTSTP SIGXCPU SIGWINCH SIGRTMIN SIGRTMIN+4 SIGRTMIN+8 SIGRTMIN+12 SIGRTMAX-14 SIGRTMAX-10 SIGRTMAX-6 SIGRTMAX-2

4) 8) 12) 17) 21) 25) 29) 35) 39) 43) 47) 51) 55) 59) 63)

SIGILL SIGFPE SIGUSR2 SIGCHLD SIGTTIN SIGXFSZ SIGIO SIGRTMIN+1 SIGRTMIN+5 SIGRTMIN+9 SIGRTMIN+13 SIGRTMAX-13 SIGRTMAX-9 SIGRTMAX-5 SIGRTMAX-1

Następnie można zdefiniować pułapki (ang. trap) i odpowiednie procedury obsługi sygnałów. Jeśli praca skryptu zostanie przerwana w wyniku przesłania sygnału numer_sygnału, kod zwracany po zakończeniu skryptu będzie miał wartość 128+numer_sygnału. Poniżej zostały przedstawione instrukcje, które gwarantują przechwycenie kilku sygnałów, ale bez ustalania, który z nich został przesłany. Gdyby definicja pułapki miała treść trap '' ABRT EXIT HUP INT TERM QUIT skrypt byłby niezwykle trudny do przerwania, ponieważ ignorowałby wszystkie wymienione sygnały. $ cat trudny_do_przerwania #!/bin/bash trap ' echo "Pokonałeś mnie! $?" ' ABRT EXIT HUP INT TERM QUIT trap ' echo "Do zobaczenia... $?"; exit ' USR1

242 |

Rozdział 10. Dodatkowe mechanizmy skryptowe

sleep 10 $ ./trudny_do_przerwania ^CPokonałeś mnie! 130 Pokonałeś mnie! 130 $ ./trudny_do_przerwania & [1] 3735 $ kill -USR1 %1 Sygnał użytkownika 1 Do zobaczenia... 138 Pokonałeś mnie! 0 [1]+ Done

./trudny_do_przerwania

$ ./trudny_do_przerwania & [1] 3744 $ kill %1 Zakończony Pokonałeś mnie! 143

Oto bardziej interesujący przykład: #!/usr/bin/env bash # plik receptury: hard_to_kill function trapped { if [ "$1" = "USR1" ]; then echo "Pokonałeś mnie sygnałem $1!" exit else echo "Odebrany sygnał: $1" fi } trap trap trap trap trap trap trap trap

"trapped "trapped "trapped "trapped "trapped "trapped "trapped "trapped

ABRT" EXIT" HUP" INT" KILL" QUIT" TERM" USR1"

ABRT EXIT HUP INT KILL QUIT TERM USR1

# Nie działa zgodnie z oczekiwaniami # Specjalna pułpka

# Zawieszenie działania bez zakłócania "zewnętrznego" mechanizmu # obsługi pułapek; Uśpienie skryptu podobne do użycia instrukcji sleep while (( 1 )); do : # symbol : oznacza pusty rozkaz done

Spróbujmy przerwać działanie następującego skryptu: $ ./hard_to_kill ^COdebrany sygnał: INT ^COdebrany sygnał: INT ^COdebrany sygnał: INT ^Z [1]+ Stopped

./hard_to_kill

$ kill -TERM %1 [1]+ Stopped Odebrany sygnał: TERM

./hard_to_kill

10.6. Przechwytywanie przerwań

| 243

$ jobs [1]+ Stopped

./hard_to_kill

$ bg [1]+ ./hard_to_kill & $ jobs [1]+ Running

./hard_to_kill &

$ kill -TERM %1 Odebrany sygnał: TERM $ kill -HUP %1 Odebrany sygnał: HUP $ kill -USR1 %1 Pokonałeś mnie pułapką USR1! Odebrany sygnał: EXIT [1]+

Done

./hard_to_kill

Analiza Przede wszystkim trzeba pamiętać, że przechwycenie sygnału –SIGKILL (-9) nie jest możliwe. Sygnał ten bowiem natychmiast przerywa działanie aplikacji, zanim zostanie wykonana procedura obsługi tego sygnału. Przedstawiony skrypt okazuje się więc łatwiejszy do przerwania niż mogło się początkowo wydawać. Trzeba mieć jednak świadomość tego, że wysłanie sygnału –SIGKILL nie pozwala programowi na poprawne zakończenie działania i wykonanie stosownych czynności porządkowych. Dlatego należy unikać wykorzystywania instrukcji kill –KILL, jeżeli inne polecenie wykona tę samą czynność. Składnia instrukcji trap jest następująca: trap [-lp] [parametr] [sygnał [sygnał]]

Pierwszy parametr (niebędący opcją) polecenia trap obejmuje kod, który zostanie wykonany po odebraniu sygnału. Zgodnie z przedstawionymi przykładami kod ten może być samodzielną instrukcją lub wywołaniem funkcji. W większości praktycznych zastosowań tego mechanizmu właściwe wydaje się zdefiniowanie jednej lub kilku funkcji obsługi przerwań, które zapewnią prawidłowe zakończenie pracy skryptu. Jeśli wspomnianym parametrem jest ciąg pusty, wskazany sygnał bądź sygnały zostaną zignorowane. Zapisanie jako parametru znaku myślnika (-) i uwzględnienie jednego lub większej liczby sygnałów powoduje przywrócenie domyślnego sposobu obsługi tych sygnałów, gwarantowanego przez powłokę. Opcja –l odpowiada za wyświetlenie listy nazw sygnałów (co zostało pokazane we wcześniejszej części receptury). Natomiast opcja –p pozwala na wyświetlenie listy bieżących pułapek i odpowiadających im procedur obsługi sygnałów. Pisząc skrypt wykorzystujący procedury obsługi sygnałów, warto poświęcić trochę czasu na alfabetyczne uszeregowanie nazw tych sygnałów. Dzięki temu kod jest znacznie łatwiejszy do analizowania i przeszukiwania. Zgodnie z zamieszczonymi wcześniej informacjami kod zwracany po zakończeniu skryptu (w wyniku dostarczenia do niego odpowiedniego sygnału) ma wartość 128+numer_sygnału. Programista dysponuje również trzema pseudosygnałami, które znajdują zastosowanie w różnych zadaniach. Sygnał DEBUG ma znaczenie podobne do sygnału EXIT, ale jest wykorzystywany 244 |

Rozdział 10. Dodatkowe mechanizmy skryptowe

przed każdym poleceniem w celach uruchomieniowych (w operacjach debugowania). Sygnał RETURN jest generowany w chwili wznowienia wykonywania skryptu po wywołaniu funkcji lub instrukcji source (.). Sygnał ERR jest natomiast przesyłany po błędnym zakończeniu pojedynczego polecenia. Więcej szczegółowych informacji na ten temat znajduje się w dokumentacji powłoki bash. Czytając tę dokumentację, warto zwrócić szczególną uwagę na wykorzystanie wbudowanej instrukcji declare oraz opcji set –o functrace. Niektóre rozwiązania standardu POSIX odnoszące się do mechanizmu trap odbiegają od domyślnych rozwiązań powłoki. Zgodnie z dokumentacją interpretera bash „uruchomienie powłoki bash z opcją –posix lub wykonanie instrukcji set –o posix w czasie pracy powłoki powoduje zachowanie zgodności ze standardem POSIX 1003.2 w obszarach, w których domyślny sposób działania powłoki odbiega od zapisów standardu”. Oznacza to przede wszystkim zmianę sposobu wyświetlania nazw sygnałów przez program kill i trap — pominięcie początkowych liter SIG — oraz zmianę listingu wynikowego polecenia kill –l. Ponadto program trap przetwarza wówczas parametry w bardziej rygorystyczny sposób, wymagając na przykład dodania początkowego znaku – w celu przywrócenia domyślnego sposobu obsługi sygnałów. Innymi słowy, wymaga wprowadzenia polecenia trap – USR1 zamiast trap USR1. Znak – warto jednak uwzględniać nawet wtedy, gdy nie jest to konieczne, gdyż w ten sposób zwiększa się czytelność kodu.

Zobacz również • Polecenie help trap. • Receptura 1.16, „Dodatkowa dokumentacja powłoki bash”. • Receptura 10.1, „«Demonizowanie» skryptu”. • Receptura 14.11, „Bezpieczne pliki tymczasowe”. • Receptura 17.7, „Czyszczenie ekranu po wylogowaniu”.

10.7. Zmiana definicji poleceń za pomocą aliasów Problem Chcemy zmienić nieznacznie definicję polecenia, na przykład po to, by zawsze było ono wykonywane ze wskazaną opcją (często polecenie ls jest wykonywane z opcją –a, a instrukcja rm z opcją –i).

Rozwiązanie Należy wykorzystać mechanizm aliasów, dostępny (tylko) w powłokach interaktywnych. Instrukcja alias jest dostatecznie „inteligentna”, aby przedstawiony poniżej zapis nie stał się definicją pętli nieskończonej. alias ls='ls –a'

10.7. Zmiana definicji poleceń za pomocą aliasów

| 245

Aby zapoznać się z listą obecnie wykorzystywanych aliasów, wystarczy wprowadzić polecenie alias bez jakichkolwiek opcji. W niektórych dystrybucjach systemów operacyjnych sesje powłoki bash są wstępnie wyposażone w kilka aliasów.

Analiza Działanie mechanizmu aliasów polega na zwykłej podmianie ciągów tekstowych. Operacja zastępowania ciągu zachodzi we wczesnej fazie przetwarzania polecenia, więc wszystkie inne zadania podmiany wartości odnoszą się do instrukcji opisanej aliasem. Chcąc na przykład zdefiniować pojedynczą literę h jako polecenie, które wyświetli zawartość katalogu domowego, wystarczy wprowadzić następujący wiersz: alias h='ls $HOME'

lub wiersz: alias h='ls ~'

W pierwszej z wymienionych instrukcji bardzo istotne jest użycie znaków apostrofu. Zapobiegają one bowiem interpretacji zmiennej $HOME i wstawieniu konkretnej wartości w definicji aliasu. Podmiana wartości ciągu zostanie natomiast wykonana w chwili uruchomienia polecenia i tylko wtedy zostanie pobrana wartość zmiennej $HOME. Dzięki temu ewentualna zmiana wartości zmiennej $HOME spowoduje uwzględnienie jej w aliasie polecenia. Gdyby zamiast znaków apostrofu zostały wykorzystane znaki cudzysłowu, podmiana wartości zmiennej nastąpiłaby natychmiast po wprowadzeniu instrukcji i w treści aliasu zawarty byłby ciąg przechowywany normalnie w zmiennej $HOME. Można się o tym przekonać, wpisując instrukcję alias bez parametrów — na ekranie jest wówczas wyświetlana lista zdefiniowanych aliasów. Jest wśród nich wiersz: ... alias h='ls /home/kontoużytkownika' ...

Gdy działanie aliasu jest niezgodne z oczekiwaniami użytkownika, można usunąć alias, wprowadzając polecenie unalias i nazwę niepotrzebnej definicji. Oto przykład: unalias h

Wykonanie powyższej instrukcji spowoduje usunięcie aliasu utworzonego przez prezentowane wcześniej polecenia. Istnieje również możliwość usunięcia wszystkich aliasów bieżącej sesji powłoki. Służy do tego instrukcja unalias –a. Co się jednak stanie, gdy ktoś utworzy alias dla polecenia unalias? Poprzedzenie wywołania znakiem lewego ukośnika nie pozwala na wykorzystanie aliasu. Zatem właściwym rozwiązaniem tego problemu będzie wprowadzenie instrukcji \unalias –a. Aliasy nie obsługują parametrów. Nie można więc zamieścić definicji typu: # To rozwiązanie jest błędne. Parametry nie są obsługiwane $ alias='mkdir $1 && cd $1'

Różnica między zmiennymi $1 i $HOME polega na tym, że zmienna $HOME jest zdefiniowana (w pewien sposób) podczas definiowania aliasu. Wartość zmiennej $1 jest natomiast przekazywana w czasie późniejszej pracy. Takie rozwiązanie jest więc niepoprawne i trzeba je zastąpić funkcjami.

246 |

Rozdział 10. Dodatkowe mechanizmy skryptowe

Zobacz również • Więcej informacji na temat przetwarzania instrukcji wiersza poleceń znajduje się w do-

datku C. • Receptura 10.4, „Definiowanie funkcji”. • Receptura 10.5, „Wykorzystanie funkcji. Parametry i zwracane wartości”. • Receptura 14.4. „Usuwanie wszystkich aliasów”. • Receptura 16.14, „Utworzenie katalogu i przejście do niego w jednym kroku”.

10.8. Pomijanie aliasów i funkcji Problem Zdefiniowaliśmy alias lub funkcję, aby przesłonić wywołanie rzeczywistego polecenia, ale teraz chcemy wywołać to rzeczywiste polecenie.

Rozwiązanie Aby wykonać polecenie, które jest wbudowanym poleceniem powłoki bash, wystarczy wykorzystać instrukcję builtin. Użycie polecenia command pozwala na zignorowanie aliasów i funkcji podczas uruchamiania programu zewnętrznego. Jeśli problem dotyczy jedynie konieczności pominięcia aliasu, ale z uwzględnieniem definicji funkcji, należy poprzedzić nazwę polecenia znakiem lewego ukośnika. Chcąc sprawdzić, jakiego rodzaju kod zostanie wykonany, wystarczy wpisać instrukcję type (ewentualnie z opcją –a). Oto kilka przykładów zastosowania wymienionych rozwiązań: $ alias echo='echo ~~~' $ echo test ~~~ test $ \echo test test $ builtin echo test test $ type echo echo is aliased to `echo ~~~' $ unalias echo $ type echo echo is a shell builtin $ type -a echo

10.8. Pomijanie aliasów i funkcji

|

247

echo is a shell builtin echo is /bin/echo $ echo test test

Poniżej znajduje się definicja funkcji, która zostanie przeanalizowana w dalszej części receptury. function cd () { if [[ $1 = "..." ]] then builtin cd ../.. else builtin cd $1 fi }

Analiza Wewnętrzne mechanizmy polecenia alias zapobiegają powstawaniu pętli nieskończonych po wprowadzeniu definicji typu alias ls='ls –a' lub alias echo='echo ~~~'. Z tego względu w pierwszym z prezentowanych przykładów nie trzeba było zapisywać żadnych instrukcji po prawej stronie operatora przypisania w instrukcji alias, aby zagwarantować odwołanie do rzeczywistego polecenia echo. Wykonanie polecenia type w odniesieniu do instrukcji echo zdefiniowanej jako alias powoduje, że na ekranie zostaje wyświetlona nie tylko informacja o tym, że mamy do czynienia z aliasem, ale również sama definicja aliasu. W analogiczny sposób można zapoznać się z treścią funkcji. Polecenie type –a wybrane_polecenie odpowiada za wyświetlenie wszystkich definicji (aliasów, poleceń wbudowanych, funkcji i poleceń zewnętrznych) dla instrukcji wybrane_polecenie (działanie to ulega zmianie po dodaniu opcji –p). Funkcja przedstawiona w ostatnim z przykładów nadpisuje definicję polecenia cd, przygotowując pewien skrót klawiaturowy dla użytkownika. Zgodnie z założeniem powinna ona zinterpretować parametr ... jako rozkaz przejścia o dwa katalogi wyżej w strukturze katalogów, tj. zastąpić polecenie cd ../.. (więcej informacji na ten temat znajduje się w recepturze 16.13, „Utworzenie lepszego polecenia cd”). Wszystkie inne parametry instrukcji będą traktowane w sposób standardowy. Działanie funkcji polega jedynie na wyszukiwaniu ciągu ... i zastępowaniu go poprawnym zapisem. Ale w jaki sposób można wskazać w kodzie funkcji (lub poza nim), że powinno zostać wywołane rzeczywiste polecenie cd, które odpowiada za zmianę katalogu? Instrukcja builtin stanowi dla powłoki informację, że polecenie występujące za nią jest wbudowanym poleceniem powłoki i nie należy wykorzystywać aliasów ani funkcji. W przedstawionym rozwiązaniu instrukcja builtin została zapisana w kodzie funkcji, lecz można ją wykorzystywać w dowolnej części skryptu, w której niezbędne jest odwołanie się do rzeczywistego polecenia, z pominięciem ewentualnych przesłaniających to polecenie funkcji. Jeżeli nazwa funkcji pokrywa się z nazwą pliku wykonywalnego (np. ls), a nie z nazwą instrukcji wbudowanej, jej wywołanie (lub wywołanie aliasu) można pominąć przez wskazanie pełnej ścieżki dostępu do pliku wykonywalnego (np. /bin/ls). W przypadku gdy nie znamy tej ścieżki, wystarczy poprzedzić polecenie instrukcją command. Powłoka bash zignoruje wówczas ewentualne funkcje i aliasy o takiej samej nazwie, jak nazwa polecenia. Oczywiście, do zlokalizowania

248 |

Rozdział 10. Dodatkowe mechanizmy skryptowe

pliku wykonywalnego instrukcji i tak zostanie wykorzystana wartość zmiennej $PATH. Jeżeli więc uruchamianie niewłaściwego polecenia ls wynika z błędnego zdefiniowania wartości zmiennej $PATH, dodanie instrukcji command nic nie zmieni.

Zobacz również • Polecenie help builtin. • Polecenie help command. • Polecenie help type. • Receptura 14.4, „Usuwanie wszystkich aliasów”. • Receptura 16.13, „Utworzenie lepszego polecenia cd”.

10.8. Pomijanie aliasów i funkcji

| 249

250

|

Rozdział 10. Dodatkowe mechanizmy skryptowe

ROZDZIAŁ 11.

Przetwarzanie informacji o dacie i czasie

Choć przetwarzanie informacji o dacie i czasie wydaje się łatwe, wcale takie nie jest. Niezależnie od tego, czy rzecz dotyczy zwykłego skryptu powłoki, czy bardzo rozbudowanego programu, operacje na wartościach daty i czasu są bardzo skomplikowane. Problem wynika przede wszystkim z mnogości formatów wyświetlania tego typu parametrów, konieczności rozróżniania czasu letniego i zimowego, lat przestępnych itd. Załóżmy na przykład, że dysponujemy listą kontraktów wraz z informacjami o dacie ich podpisania. Chcemy określić daty wygaśnięcia poszczególnych z nich. Zadanie wcale nie jest trywialne, jakby się mogło początkowo wydawać. Trzeba bowiem sprawdzić, czy w czasie obowiązywania kontraktu występują lata przestępne. Czy zmiana czasu z zimowego na letni (lub odwrotnie) odgrywa istotną rolę? Jak sformatować dane wyjściowe, aby zapis dat był jednoznaczny? Czy zapis 7/4/07 oznacza 7 kwietnia 2007 r., czy 4 lipca 2007 r.? Daty i czas są nieodłącznym elementem komputerowego przetwarzania danych. Prędzej czy później każdy programista musi zająć się właściwą ich obsługą — zarządzając systemem, tworząc aplikacje, analizując dzienniki pracy, pisząc skrypty itd. Celem niniejszego rozdziału jest dostarczenie informacji, które ułatwiają posługiwanie się ciągami daty i czasu. Dla komputerów precyzyjne odmierzanie czasu nie stanowi żadnego problemu, szczególnie jeśli mogą one synchronizować zegary za pomocą protokołu NTP z krajowymi i międzynarodowymi serwerami czasu. Problemem nie jest również rozróżnienie czasu letniego i zimowego obowiązującego w danym kraju. W skryptach powłoki przetwarzanie informacji o dacie należy do zadań uniksowego polecenia date (choć właściwsze byłby stosowanie polecenia date z projektu GNU, będącego standardem dla systemów Linux). Instrukcja date pozwala na wyświetlanie dat w różnych formatach oraz na wykonywanie obliczeń na wartościach dat. Warto tu wspomnieć o tym, że narzędzie gawk (program awk projektu GNU) wykorzystuje tę samą funkcję formatowania strftime, która jest stosowana w poleceniu date projektu GNU. Sam mechanizm gawk nie zostanie jednak tutaj opisany (poza jednym przypadkiem jego użycia). Niemniej zalecamy stosowanie polecenia date w wersji GNU, gdyż jest ono znacznie łatwiejsze w użyciu i udostępnia bardzo użyteczną opcję –d. Warto jednak pamiętać o istnieniu programu gawk, gdyż jest on dostępny w większości systemów operacyjnych w przeciwieństwie do polecenia date w wersji zgodnej z projektem GNU.

251

11.1. Formatowanie dat podczas wyświetlania Problem Chcemy sformatować ciągi daty i czasu, które mają zostać wyświetlone.

Rozwiązanie Należy wykorzystać polecenie date z uwzględnieniem specyfikacji formatu dla funkcji strftime. Zestawienie obsługiwanych symboli formatu zostało zamieszczone w dodatku A, w punkcie „Formatowanie daty i czasu z wykorzystaniem funkcji strftime”, oraz na stronach podręcznika systemowego funkcji strftime. # $ $ $ $

Ustawienie zmiennych środowiskowych może być bardzo pomocne w pracy wielu skryptów STRICT_ISO_8601='%Y-%m-%dT%H:%M:%S%z' # http://greenwichmeantime.com/info/iso.htm ISO_8601='%Y-%m-%d %H:%M:%S %Z' # Format zbliżony do ISO, ale czytelniejszy ISO_8601_1='%Y-%m-%d %T %Z' # Symbol %T odpowiada zapisowi %H:%M:%S DATEFILE='%Y%m%d%H%M%S' # Wygodne podczas definiowania nazw plików

$ date "+$ISO_8601" 2007-10-11 09:19:13 CET $ gawk "BEGIN {print strftime(\"$ISO_8601\")}" 2007-10-11 09:19:49 CET # Wynik taki sam jak w przypadku użycia zmiennej $ISO_8601 $ date '+%Y-%m-%d %H:%M:%S %Z' 2007-10-11 09:20:23 CET $ date -d '2005-11-06' "+$ISO_8601" 2005-11-06 00:00:00 CET $ date "+Uruchomienie programu: $ISO_8601" Uruchomienie programu: 2007-10-11 09:21:15 CET $ printf "%b" "Uruchomienie programu: $(date '+$ISO_8601')\n" Uruchomienie programu: $ISO_8601 $ echo "Możliwa zmiana nazwy pliku: mv plik.log plik_$(date +$DATEFILE).log" Możliwa zmiana nazwy pliku: mv plik.log plik_20071011092239.log

Analiza Wydawać by się mogło, że dołączenie znaku + do treści zmiennej środowiskowej ułatwi wprowadzanie późniejszych poleceń. W praktyce jednak różne systemy w różny sposób interpretują znak + w treści zmiennej. Z tego względu zalecane jest dodawanie wspomnianego znaku bezpośrednio do polecenia date. Instrukcja date obsługuje wiele innych opcji formatowania poza wymienionymi. Pełna lista symboli formatujących znajduje się na stronach podręcznika systemowego polecenia date oraz funkcji strftime() (man 3 strftime).

252

|

Rozdział 11. Przetwarzanie informacji o dacie i czasie

Domyślnie strefa czasowa jest strefą czasu lokalnego, zdefiniowaną w ustawieniach systemu. Symbol %z jest niestandardowym rozszerzeniem stosowanym w poleceniu date projektu GNU i może być błędnie interpretowany w niektórych systemach. Format ISO8601 jest zalecanym standardem wyświetlania dat i czasu. Z tego względu, jeśli jest to możliwe, należy zawsze stosować ten format zapisu. Jego przewaga nad innymi sposobami prezentacji dat wynika z tego że: • jest to powszechnie stosowany standard, • jest jednoznaczny, • jest łatwy do odczytania, a jednocześnie łatwy do interpretacji w kodzie programu (na przy-

kład dzięki zastosowaniu poleceń awk lub cut), • zapewnia poprawne sortowanie zestawień kolumnowych lub nazw plików.

Jeżeli to możliwe, należy unikać stosowania formatów DD/MM/RR lub MM/DD/RR bądź jeszcze gorszego zapisu D/M/RR lub M/D/RR. Wymienione formaty nie gwarantują właściwego sortowania danych, a poza tym są niejednoznaczne, gdyż w zależności od reguł obowiązujących w danym kraju pierwsza liczba może zostać uznana za oznaczenie dnia lub miesiąca, co powoduje znaczne problemy we właściwej interpretacji dat w kodzie skryptu. Niewłaściwe jest również zapisywanie czasu w formacie innym niż 24-godzinny. To także może być przyczyną istotnych problemów w interpretacji wartości.

Zobacz również • Polecenie man date. • http://www.cl.cam.ac.uk/~mgk25/iso-time.html. • http://www.qsl.net/g1smd/isopdf.htm. • http://wwp.greenwichmeantime.com/info/iso.htm. • Punkt „Formatowanie daty i czasu z wykorzystaniem funkcji strftime” w dodatku A.

11.2. Dostarczanie domyślnej wartości daty Problem Chcemy, aby skrypt dostarczał domyślną wartość daty i wymagał jej potwierdzenia przez użytkownika.

Rozwiązanie Dzięki poleceniu GNU date można wygenerować najbardziej prawdopodobny ciąg daty i przypisać go zmiennej, a następnie umożliwić użytkownikowi wprowadzenie stosownych zmian. #!/usr/bin/env bash # plik receptury: default_date # Wykorzystanie czasu południa zapobiega błędowi, który mógłby powstać # w przypadku uruchomienia skryptu na kilka sekund przed zmianą daty i spowodować

11.2. Dostarczanie domyślnej wartości daty

|

253

# przesunięcie o jeden dzień START_DATE=$(date -d 'last week Monday 12:00:00' '+%Y-%m-%d') while [ 1 ]; do printf "%b" "Data początkowa to: read answer

$START_DATE. Czy jest poprawna? (T/nowa data) "

# Każda wartość inna niż "T" lub "t" jest uznawana za nową datę. # Można użyć wyrażenia "[Tt]*", aby umożliwić użytkownikowi wpisanie "tak"... # Sprawdzenie, czy data jest zgodna z formatem: YYYY-MM-DD case "$answer" in [Tt]) break ;; [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) printf "%b" "Zastępuję $START_DATE datą $answer\n" START_DATE="$answer" ;; *)

printf "%b" "Błędna data, spróbuj jeszcze raz...\n" ;;

esac done END_DATE=$(date -d "$START_DATE +7 days" '+%Y-%m-%d') echo "Data początkowa: $START_DATE" echo "Data końcowa: $END_DATE"

Analiza Nie wszystkie wersje polecenia date udostępniają opcję –d, ale wersja projektu GNU pozwala na jej użycie. Jeśli to możliwe, należy zainstalować i wykorzystywać program date z repozytorium GNU. Jeżeli skrypt nie jest uruchamiany pod nadzorem użytkownika lub jest wykonywany automatycznie (np. przez mechanizm cron), kod weryfikacji wartości musi zostać usunięty. Zasady formatowania ciągów daty i czasu podczas wyświetlania zostały opisane w recepturze 11.1, „Formatowanie dat podczas wyświetlania”. Skrypty zbliżone do przedstawionego są często wykorzystywane do generowania zapytań SQL. Ich zadanie polega wówczas na przygotowaniu zapytania SQL, które utworzy raport z określonego przedziału czasu.

Zobacz również • Polecenie man date. • Receptura 11.1, „Formatowanie dat podczas wyświetlania”. • Receptura 11.3, „Automatyczne generowanie dat z określonego zakresu”.

254 |

Rozdział 11. Przetwarzanie informacji o dacie i czasie

11.3. Automatyczne generowanie dat z określonego zakresu Problem Znając wartość daty (na przykład wygenerowanej przez skrypt z receptury 11.2, „Dostarczanie domyślnej wartości daty”), chcemy w sposób automatyczny wygenerować kolejną.

Rozwiązanie Polecenie date projektu GNU jest niezwykle wszechstronne i użyteczne, ale przeznaczenie opcji –d nie zostało zbyt dobrze udokumentowane. W niektórych systemach jest ono opisane w podręczniku systemowym na stronie dotyczącej funkcji getdate (warto zapoznać się z jej treścią). Oto kilka przykładów zastosowania wspomnianej opcji: $ date '+%Y-%m-%d %H:%M:%S %z' 2007-10-11 10:52:18 +0200 $ date -d 'today' '+%Y-%m-%d %H:%M:%S %z' 2007-10-11 10:52:30 +0200 $ date -d 'yesterday' '+%Y-%m-%d %H:%M:%S %z' 2007-10-10 10:52:47 +0200 $ date -d 'tomorrow' '+%Y-%m-%d %H:%M:%S %z' 2007-10-12 10:52:57 +0200 $ date -d 'Monday' '+%Y-%m-%d %H:%M:%S %z' 2007-10-15 00:00:00 +0200 $ date -d 'this Monday' '+%Y-%m-%d %H:%M:%S %z' 2007-10-15 00:00:00 +0200 $ date -d 'last Monday' '+%Y-%m-%d %H:%M:%S %z' 2007-10-08 00:00:00 +0200 $ date -d 'next Monday' '+%Y-%m-%d %H:%M:%S %z' 2007-10-15 00:00:00 +0200 $ date -d 'last week' '+%Y-%m-%d %H:%M:%S %z' 2007-10-04 10:53:56 +0200 $ date -d 'next week' '+%Y-%m-%d %H:%M:%S %z' 2007-10-18 10:54:12 +0200 $ date -d '2 weeks' '+%Y-%m-%d %H:%M:%S %z' 2007-10-25 10:54:22 +0200 $ date -d '-2 weeks' '+%Y-%m-%d %H:%M:%S %z' 2007-09-27 10:54:33 +0200 $ date -d '2 weeks ago' '+%Y-%m-%d %H:%M:%S %z' 2007-09-27 10:54:47 +0200 $ date -d '+4 days' '+%Y-%m-%d %H:%M:%S %z' 2007-10-15 10:55:01 +0200

11.3. Automatyczne generowanie dat z określonego zakresu

|

255

$ date -d '-6 days' '+%Y-%m-%d %H:%M:%S %z' 2007-10-05 10:55:10 +0200 $ date -d '2000-01-01 +12 days' '+%Y-%m-%d %H:%M:%S %z' 2000-01-13 00:00:00 +0100 $ date -d '3 months 1 day' '+%Y-%m-%d %H:%M:%S %z' 2008-01-12 10:55:44 +0100

Analiza Opcja –d pozwala na określenie daty zastępującej datę bieżącą (now). Niestety, nie wszystkie wersje instrukcji date umożliwiają wykorzystanie opcji –d. Jest ona jednak dostępna w poleceniu date projektu GNU i dlatego, jeśli to możliwe, warto pobrać i zainstalować tę właśnie wersję programu. Wyniki zastosowania opcji –d mogą niekiedy wprawić użytkownika w zdumienie. Polecenia zamieszczone poniżej działają zgodnie z przewidywaniami. $ date '+%a %Y-%m-%d' nie 2007-10-14 $ date -d 'today' '+%a %Y-%m-%d' nie 2007-10-14 $ date -d 'Sunday' '+%a %Y-%m-%d' nie 2007-10-14 $ date -d 'last Sunday' '+%a %Y-%m-%d' nie 2007-10-07 $ date -d 'this Sunday' '+%a %Y-%m-%d' nie 2007-10-14

Jednak wykonując polecenie w niedzielę, użytkownik spodziewa się wyświetlenia daty kolejnej niedzieli (next Sunday), tymczasem otrzymuje aktualną datę: $ date -d 'next Sunday' '+%a %Y-%m-%d' nie 2007-10-14

Mylące może być również stosowanie parametru this week (ten tydzień) i specyfikatorów dnia. Jeśli wskazywany dzień nie należy do przeszłości, bieżący tydzień jest interpretowany jako przyszły tydzień. Wykonanie poniższego polecenia w sobotę 2007-10-13 powoduje wyświetlenie wartości, której użytkownik z pewnością by się nie spodziewał: $ date -d 'this week Friday' '+%a %Y-%m-%d' pią 2007-10-19

Opcja –d jest niezwykle użyteczna, ale w przypadku wykorzystania jej w kodzie skryptu trzeba dokładnie przetestować działanie programu i zapewnić odpowiednie mechanizmy sprawdzania błędów. Osoby, które nie dysponują systemami wyposażonymi w instrukcję date projektu GNU, powinny się zapoznać z artykułem Shell Croner: Date-Related Shell Function, opublikowanym w sierpniu 2005 r. w witrynie UnixReview. Zostało tam opisanych pięć wymienionych poniżej funkcji powłoki.

256

|

Rozdział 11. Przetwarzanie informacji o dacie i czasie

pn_month

Kolejne i wcześniejsze miesiące, wyznaczane względem bieżącego dnia. end_month

Koniec bieżącego miesiąca. pn_day

Kolejne i wcześniejsze dni, wyznaczane względem bieżącego dnia. cur_weekday

Nazwa bieżącego dnia tygodnia. pn_weekday

Nazwa kolejnego lub wcześniejszego dnia tygodnia, wyznaczana względem bieżącego dnia.

Dwie kolejne funkcje zostały dodane niedawno: pn_day_nr

Kolejne i wcześniejsze dni, wyznaczane względem bieżącego dnia (bez rekurencji). days_between

Liczba dni występujących między dwoma datami. Funkcje pn_month, end_month i cur_weekday są niezależne od pozostałych. Natomiast funkcja pn_day wykorzystuje w działaniu funkcje pn_month i end_month, a funkcja pn_weekday bazuje na funkcjach pn_day i cur_weekday.

Zobacz również • Polecenie man date. • Polecenie man getdate. • http://www.unixlabplus.com/unix-prog/date_function. • Receptura 11.2, „Dostarczanie domyślnej wartości daty”.

11.4. Przekształcenie daty i czasu w znacznik czasu Problem Chcemy przekształcić wartości daty i czasu w znaczniki czasowe (odpowiadające liczbie sekund, które upłynęły od godziny 00:00:00 UTC 1 stycznia 1970 r.), które ułatwiają wykonywanie operacji arytmetycznych na wartościach daty i czasu.

Rozwiązanie Należy wykorzystać polecenie date z pakietu GNU z niestandardową opcją –d oraz ze standardowym specyfikatorem formatu %s: # Znacznik dla aktualnego czasu $ date '+%s' 1192268179

11.4. Przekształcenie daty i czasu w znacznik czasu

|

257

# Wyznaczenie znacznika dla innej daty wymaga użycia niestandardowej opcji -d $ date -d '2005-11-05 12:00:00 +0000' '+%s' 1131192000

Analiza Opisany problem nie jest tak łatwy do rozwiązania, jeśli w systemie nie jest dostępna instrukcja date w wersji GNU. Jeżeli więc istnieje taka możliwość, należy pobrać i zainstalować program date z repozytorium GNU. W przeciwnym razie można spróbować wykonać tę samą operację za pomocą instrukcji języka Perl. Poniżej zostały zaprezentowane trzy różne sposoby wygenerowania znacznika dla aktualnej wartości czasu. $ perl -e 'print time, qq(\n);' 1192270091 # Taka sama operacja jak powyżej $ perl -e 'use Time::Local; print timelocal(localtime()). qq(\n);' 1192270162 $ perl -e 'use POSIX qw(strftime); print strftime("%s", localtime()) . qq(\n);' 1192270127

Przekształcenie określonej daty w znacznik za pomocą języka Perl jest jeszcze bardziej skomplikowane z uwagi na strukturę wartości daty i czasu obowiązującą w skryptach Perl. Zliczanie lat rozpoczyna się bowiem od 1900 roku, a miesięcy (ale nie dni) od 0, a nie od 1. Składnia polecenia jest następująca: timelocal(sek, min, godz, dzień, miesiąc-1, rok-1900). Zatem przekształcenie daty 2005-11-05 06:59:49 w znacznik czasu wymaga wykonania instrukcji: # Gdy wartość czasu odpowiada lokalnej strefie czasowej: $ perl -e 'use Time::Local; print timelocal("49", "59", "06", "05", "10", "105"). qq(\n);' 1131170389 # Gdy wartość czasu odpowiada czasowi UTC: $ perl -e 'use Time::Local; print timegm("49", "59", "06", "05", "10", "105"). qq(\n);' 1131173989

Zobacz również • Polecenie man date. • Receptura 11.5, „Przekształcanie znaczników czasu w ciągi dat i czasu”. • Punkt „Formatowanie daty i czasu z wykorzystaniem funkcji strftime” w dodatku A.

11.5. Przekształcanie znaczników czasu w ciągi dat i czasu Problem Chcemy przekształcić znacznik czasu w ciąg daty i czasu łatwy do zinterpretowania przez człowieka.

258

|

Rozdział 11. Przetwarzanie informacji o dacie i czasie

Rozwiązanie Trzeba wykorzystać polecenie GNU date z formatem daty wyznaczonym w sposób opisany w recepturze 11.1, „Formatowanie dat podczas wyświetlania”. EPOCH='1131173989' $ date -d "1970-01-01 UTC $EPOCH seconds" +"%Y-%m-%d %T %z" 2005-11-05 07:59:49 +0100 $ date --utc --date "1970-01-01 UTC $EPOCH seconds" +"%Y-%m-%d %T %z" 2005-11-05 06:59:49 +0000

Analiza Ponieważ znacznik czasu w praktyce odpowiada liczbie sekund, które upłynęły od 1 stycznia 1970 r. (czyli od daty 1970-01-01T00:00:00), wystarczy dodać do tej daty określoną liczbę sekund i wyświetlić datę i czas w formacie, który jest odpowiedni do analizowania przez użytkownika. Jeżeli w systemie nie został zainstalowany program date w wersji GNU, można zastosować jedno z przedstawionych poniżej poleceń języka Perl. EPOCH='1131173989' $ perl -e "print scalar(gmtime($EPOCH)), qq(\n);" Sat Nov 5 06:59:49 2005

# UTC

$ perl -e "print scalar(localtime($EPOCH)), qq(\n);" Sat Nov 5 07:59:49 2005

# Czas lokalny

$ perl -e "use POSIX qw(strftime); print strftime('%Y-%m-%d %H:%M:%S', localtime($EPOCH)), qq(\n);" 2005-11-05 07:59:49

Zobacz również • Polecenie man date. • Receptura 11.1, „Formatowanie dat podczas wyświetlania”. • Receptura 11.4, „Przekształcanie daty i czasu w znacznik czasu”. • Punkt „Formatowanie daty i czasu z wykorzystaniem funkcji strftime” w dodatku A.

11.6. Pobranie daty poprzedniego lub kolejnego dnia w języku Perl Problem Chcemy pobrać datę poprzedniego lub następnego dnia, mimo że w systemie nie jest zainstalowany program date z repozytorium GNU. Jest natomiast dostępny interpreter Perl.

11.6. Pobranie daty poprzedniego lub kolejnego dnia w języku Perl

|

259

Rozwiązanie Rozwiązanie polega na wykorzystaniu poniższych poleceń Perl z odpowiednią liczbą sekund, które są dodawane do ustalonej daty lub od niej odejmowane. # Dzień poprzedni o tym samym czasie (odejmowanie wartości) $ perl -e "use POSIX qw(strftime); print strftime('%Y-%m-%d', localtime(time 86400)), qq(\n);" # Dzień następny o tym samym czasie (dodawanie wartości) $ perl -e "use POSIX qw(strftime); print strftime('%Y-%m-%d', localtime(time + 86400)), qq(\n);"

Analiza Przedstawione rozwiązanie jest szczególnym przypadkiem zastosowania receptur opisywanych wcześniej. Niemniej z uwagi na to, że jest ono często wykorzystywane, warto je rozpatrzyć oddzielnie. W recepturze 11.7, „Obliczanie daty i czasu” zostało wymienionych kilka wartości, które przydają się podczas definiowania tego typu obliczeń.

Zobacz również • Receptura 11.2, „Dostarczanie domyślnej wartości daty”. • Receptura 11.3, „Automatyczne generowanie dat z określonego zakresu”. • Receptura 11.4, „Przekształcenie daty i czasu w znacznik czasu”. • Receptura 11.5, „Przekształcanie znaczników czasu w ciągi dat i czasu”. • Receptura 11.7, „Obliczanie daty i czasu”. • Punkt „Formatowanie daty i czasu z wykorzystaniem funkcji strftime” w dodatku A.

11.7. Obliczanie daty i czasu Problem Musimy wykonać pewne obliczenia z użyciem wartości dat i czasu.

Rozwiązanie Jeżeli nie można uzyskać zadawalającego wyniku za pomocą polecenia date (zgodnie z informacjami zawartymi w recepturze 11.3, „Automatyczne generowanie dat z określonego zakresu”), konieczne jest przekształcenie wartości daty i czasu w znacznik czasu (w sposób opisany w recepturze 11.4, „Przekształcenie daty i czasu w znacznik czasu”), wykonanie stosowanych obliczeń i przekształcenie wyniku z powrotem do postaci ciągu o wybranym formacie (zgodnie z opisem zawartym w recepturze 11.5, „Przekształcanie znaczników czasu w ciągi dat i czasu”).

260

|

Rozdział 11. Przetwarzanie informacji o dacie i czasie

Osoby, które nie dysponują systemami wyposażonymi w instrukcję date projektu GNU, powinny się zapoznać z artykułem Shell Croner: Date-Related Shell Function, opublikowanym w sierpniu 2005 r. w witrynie UnixReview. Więcej informacji na temat wspomnianych funkcji znajduje się w recepturze 11.3, „Automatyczne generowanie dat z określonego zakresu”.

Załóżmy, że problem dotyczy dat zarejestrowanych w dzienniku systemowym, które mają niewłaściwe wartości (przyjmijmy, że zegar systemowy się spóźniał). W rzeczywistości takie zdarzenie nie powinno wystąpić, ponieważ obecnie niemal wszyscy administratorzy wykorzystują protokół synchronizacji czasu NTP. Oto przykład rozwiązania problemu: CORRECTION='172800'

# liczba sekund odpowiadająca dwóm dniom

# W tym miejscu należy umieścić instrukcje wydorębniające ciąg daty i # zapisujące go w zmiennej $bad_date # Załóżmy, że rzecz dotyczy poniższej daty: bad_date='Jan 2 05:13:05' # Format syslog #Przekształcenie w znacznik czasowy z wykorzystaniem polecenia GNU date bad_epoch=$(date -d "$bad_date" '+%s') # Korekcja błędu good_epoch=$(( bad_epoch + $CORRECTION )) # Przekształcenie skorygowanej daty w ciąg czytelny dla człowieka good_date=$(date -d "1970-01-01 UTC $good_epoch seconds") # GNU date good_date_iso=$(date -d "1970-01-01 UTC $good_epoch seconds" +'%Y-%m-%d %T') date echo echo echo echo echo echo

"Błędna data: "Błędny znacznik: "Korekta: "Poprwny znacznik: "Poprawna data: "Poprawna data (ISO):

# GNU

$bad_date" $bad_epoch" +$CORRECTION" $good_epoch" $good_date" $good_date_iso"

# W tym miejscu należy umieścić instrukcje wykorzystujące # poprawioną wartość daty

Uwaga na zapis roku! Niektóre aplikacje systemu Unix (np. ls lub syslog), starając się uprościć zapis, w pewnych okolicznościach pomijają wartość roku. Czasami fakt ten trzeba uwzględnić podczas wyznaczania współczynnika korekcji. Z tego względu w przypadku przetwarzania dat z dużego zakresu lub z różnych stref czasowych może zaistnieć konieczność podzielenia listingu na kilka plików i niezależnego ich przeanalizowania.

Analiza Wykonując jakiekolwiek operacje arytmetyczne na datach, najkorzystniejsze wydaje się przeprowadzenie zamierzonej operacji na znacznikach czasu, a następnie przekształcenie ich w ciągi o dowolnym formacie. Nie trzeba wówczas pamiętać o odpowiednim przeliczaniu godzin, dni, tygodni czy lat. Wystarczy po prostu zdefiniować działanie dodawania lub odejmowania. Wykorzystanie znaczników dodatkowo eliminuje problemy związane z latami przestępnymi, a sprowadzenie wszystkich wartości czasu do jednej strefy czasowej (zazwyczaj jest to strefa UTC,

11.7. Obliczanie daty i czasu

|

261

nazywana również GMT) pozwala na uniknięcie komplikacji wynikających z operowania datami właściwymi dla różnych stref czasowych. W tabeli 11.1 zostało przedstawionych kilka użytecznych wartości znaczników czasu. Tabela 11.1. Tabela konwersji typowych wartości znacznika czasu Sekundy

Minuty

60

Godziny

Dni

1

300

5

600

10

3 600

60

1

18 000

300

5

36 000

600

10

86 400

1 440

24

1

172 800

2 880

48

2

604 800

10 080

168

7

1 209 600

20 160

336

14

2 592 000

43 200

720

30

31 536 000

525 600

8 760

365

Zobacz również • http://www.jpsdomain.org/networking/time.html. • Receptura 11.3, „Automatyczne generowanie dat z określonego zakresu”. • Receptura 11.4, „Przekształcenie daty i czasu w znacznik czasu”. • Receptura 11.5, „Przekształcanie znaczników czasu w ciągi dat i czasu”. • Receptura 13.12, „Wyodrębnianie określonych pól listingu danych”.

11.8. Obsługa stref czasowych, czasu letniego oraz lat przestępnych Problem Trzeba uwzględnić strefy czasowe, czas letni oraz lata przestępne.

Rozwiązanie Zadania tego nie należy rozwiązywać własnymi metodami. Problem jest znacznie trudniejszy, niż by się mogło wydawać. Warto skorzystać z mechanizmów, które zostały już opracowane i są od lat wykorzystywane. Z bardzo dużym prawdopodobieństwem można stwierdzić, że każdy potencjalny problem związany z przetwarzaniem dat jest rozwiązany w jednej z bazu-

262

|

Rozdział 11. Przetwarzanie informacji o dacie i czasie

jących na instrukcji GNU date i zamieszczonych w tym rozdziale receptur. Jeśli nie, z pewnością pomocny okaże się jeden z wielu doskonałych modułów języka Perl, przeznaczonych do przetwarzania dat i czasu. Powyższe ostrzeżenia warto potraktować poważnie. Opracowanie poprawnie działającego algorytmu przetwarzania dat jest prawdziwym koszmarem. Użycie dostępnego narzędzia oszczędza każdemu programiście wielu cierpień.

Zobacz również • Receptura 11.1, „Formatowanie dat podczas wyświetlania”. • Receptura 11.3, „Automatyczne generowanie dat z określonego zakresu”. • Receptura 11.4, „Przekształcenie daty i czasu w znacznik czasu”. • Receptura 11.5, „Przekształcanie znaczników czasu w ciągi dat i czasu”. • Receptura 11.7, „Obliczanie daty i czasu”.

11.9. Wykorzystanie polecenia date i mechanizmu cron do uruchomienia skryptu w wybranym dniu Problem Chcemy zapewnić wykonywanie skryptu w określonym dniu tygodnia danego miesiąca (na przykład w drugą środę), a większość mechanizmów cron nie umożliwia tego typu operacji.

Rozwiązanie W treści wykonywanego polecenia trzeba uwzględnić dodatkowy kod warunkowy. Osoby korzystające z mechanizmu Vixie Cron (crontab) dostępnego w większości systemów Linux, mogą dostosować do własnych potrzeb jedną z poniższych instrukcji. Użytkownicy innych programów typu cron być może będą musieli zastąpić nazwy dni tygodnia numerami dni, zgodnie ze składnią obowiązującą w danej aplikacji harmonogramu zadań — niekiedy w miejscu nazwy dnia tygodnia (+%a) trzeba wstawić numer dnia (+%w) w przedziale 0 – 6 lub 1 – 7 bądź skrót nazwy dnia właściwy dla określonych ustawień lokalizacyjnych. # Vixie Cron # Min Godz DzMies Mies DzTyg Program # 0-59 0-23 1-31 1-12 0-7 # Uruchomienie programu w pierwszą środę miesiąca o godzinie 23:00 00 23 1-7 * Wed [ "$(date '+%a')" == "śro" ] && /ścieżka/do/programu parametry programu # Uruchomienie programu w drugi czwartek miesiąca o godzinie 23:00 00 23 8-14 * Thu [ "$(date '+%a')" == "czw" ] && /ścieżka/do/programu # Uruchomienie programu w trzeci piątek miesiąca o godzinie 23:00 00 23 15-21 * Fri [ "$(date '+%a')" == "pią" ] && /ścieżka/do/programu

11.9. Wykorzystanie polecenia date i mechanizmu cron do uruchomienia skryptu w wybranym dniu

|

263

# Uruchomienie programu w czwartą sobotę miesiąca o godzinie 23:00 00 23 22-27 * Sat [ "$(date '+%a')" == "sob" ] && /ścieżka/do/programu # Uruchomienie programu w piątą niedzielę miesiąca o godzinie 23:00 00 23 28-31 * Sun [ "$(date '+%a')" == "nie" ] && /ścieżka/do/programu

Żaden z dni tygodnia nie powtarza się zawsze pięć razy w miesiącu. Zatem definiując zadania na piąty tydzień miesiąca, trzeba dobrze się zastanowić, czy takie działanie jest uzasadnione.

Analiza Większość wersji mechanizmu cron (włączenie z Vixie Cron) nie pozwala na zaplanowanie określonego działania na n-ty dzień miesiąca. Rozwiązanie problemu polega więc na wyznaczeniu zadania na pewien zakres dni, w którym występuje również wspomniany n-ty dzień, i dołączeniu instrukcji warunkowej, zapewniającej wykonanie zadania we właściwym dniu. Druga środa tygodnia musi występować między 8. a 14. dniem miesiąca. Polecenie jest więc wykonywane każdego dnia w podanym zakresie, a zawarta w nim instrukcja warunkowa sprawdza, czy aktualny dzień jest środą. Jeśli tak, realizuje określone zadanie. W tabeli 11.2 zostały wymienione zakresy dni wykorzystane w przykładzie. Tabela 11.2. Zakresy dni każdego tygodnia w miesiącu Tydzień

Zakres dni

Pierwszy

1–7

Drugi

8 – 14

Trzeci

15 – 21

Czwarty

22 – 27

Piąty (z ograniczeniem opisanym w uwadze)

28 – 31

Rozwiązanie wydaje się aż zbyt proste, ale wystarczy przeanalizować dowolną kartę z kalendarza, aby przekonać się, że jest poprawne: $ cal 10 2007 październik 2007 ni po wt śr cz pi so 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

Zobacz również • Polecenie man 5 crontab. • Polecenie man cal.

264 |

Rozdział 11. Przetwarzanie informacji o dacie i czasie

ROZDZIAŁ 12.

Skrypty usprawniające pracę użytkownika

W poprzednich rozdziałach książki zostało przedstawionych wiele krótkich skryptów wraz z omówieniem składni wykorzystanej przy ich tworzeniu. Ich głównymi cechami, z konieczności, były niewielka złożoność i ograniczony zakres działania. W tym rozdziale zostanie zaprezentowanych kilka znacznie bardziej rozbudowanych przykładów, które będzie można zastosować w codziennej pracy z systemem operacyjnym do realizacji zadań niezwiązanych z administrowaniem tym systemem. Mamy nadzieję, że okażą się przydatne. Liczymy również na to, że każda osoba analizująca ich treść będzie mogła dowiedzieć się czegoś nowego na temat powłoki bash i postara się dostosować daną aplikację do własnych potrzeb.

12.1. Na początek coś łatwego — wyświetlanie myślników 12.1. Na początek coś łatwego — wyświetlanie myślników

Problem Zadanie wyświetlenia linii zbudowanej ze znaków myślnika nie wydaje się skomplikowane — i takim nie jest. Niemniej po przygotowaniu wstępnego skryptu okazuje się, że warto go rozbudować o dodatkowe opcje. Czy uwzględnić możliwość definiowania długości linii? Czy pozwolić na zastąpienie znaku myślnika symbolem podanym przez użytkownika? Twierdząca odpowiedź na każde tego typu pytanie powoduje zwiększenie liczby funkcji. Czy można zatem przygotować kod, który uwzględni powyższe założenia, ale jednocześnie nie będzie zanadto skomplikowany.

Rozwiązanie Oto przykład skryptu: 1 2 3 4 5 6

#!/usr/bin/env bash # plik receptury: dash # dash - wyświetlenie wiersza znaków myślnika # opcje: # liczba znaków (domyślnie 72) # -c X znak X zastępuje znak myślnika #

265

7 8 9 10 11

function usagexit () { printf "użycie: %s [-c X] [#]\n" $(basename $0) exit 2 } >&2

12 13 14 15 16 17 18 19 20 21 22 23

LEN=72 CHAR='-' while (( $# > 0 )) do case $1 in [0-9]*) LEN=$1;; -c) shift CHAR=$1;; *) usagexit;; esac shift done

24 25 26 27

if (( LEN > 4096 )) then echo "zbyt długa linia" >&2 exit 3

28 29 30 31 32 33 34 35

fi # przygotowanie ciągu o odpowiedniej długości DASHES="" for ((i=0; i&2 # # USAGE USAGE()

Rozdział 12. Skrypty usprawniające pracę użytkownika

Rysunek 12.1. Strona internetowa albumu wygenerowanego przez skrypt mkalbum 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

{ ERROUT "użycie: $(basename $0) \n" } # EMIT(thisph, startph, prevph, nextph, lastph) EMIT() { THISPH="../$1" STRTPH="${2%.*}.html" PREVPH="${3%.*}.html" NEXTPH="${4%.*}.html" LASTPH="${5%.*}.html" if [ -z "$3" ] then PREVLINE=' Poprzednie ' else PREVLINE=' Poprzednie ' fi if [ -z "$4" ] then NEXTLINE=' Następne ' else

12.2. Przeglądanie albumu ze zdjęciami

|

269

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

270

|

NEXTLINE=' Następne ' fi cat "${PHILE%.*}.html"

108 109 110 111 112 113 114

# utworzenie dowiązania symbolicznego do ostatniego zdjęcia ln -s "${PHILE%.*}.html" ./last.html # utworzenie dowiązania symbolicznego do pliku indeksu ln -s "${FIRST%.*}.html" ./index.html

Analiza Choć w internecie dostępnych jest wiele tanich lub nawet darmowych programów do przeglądania zdjęć, wykorzystanie powłoki bash do utworzenia prostego albumu daje możliwość zapoznania się z niezwykłą wszechstronnością skryptowych aplikacji i jest doskonałym tematem do wnikliwej analizy mechanizmów skryptowych. Skrypt rozpoczyna się od specjalnej wersji komentarza (1. wiersz) wskazującej plik wykonywalny, który musi zostać wykorzystany do uruchomienia skryptu. Kilka kolejnych wierszy zajmują komentarze opisujące przeznaczenie programu. Dodawanie komentarzy jest naprawdę bardzo istotną czynnością. Gdy będzie trzeba przeanalizować kod skryptu po 3 dniach lub 13 miesiącach od jego napisania, nawet najbardziej ogólny komentarz będzie pomocny w zrozumieniu sposobu działania aplikacji. Za blokiem komentarzy występuje definicja funkcji ERROUT (wiersze 14. – 17.). Jej działanie jest zbliżone do działania funkcji printf (sama funkcja printf jest wywoływana w kodzie funkcji ERROUT), ale z uwzględnieniem mechanizmu przekierowania danych wyjściowych do standardowego strumienia błędów. Dzięki temu nie trzeba pamiętać o stosowaniu operatorów przekierowania podczas definiowania każdej instrukcji printf. Choć zazwyczaj operator przekierowania jest umieszczany na końcu polecenia, w tym przypadku (w wierszu 17.) jest on zdefiniowany na końcu funkcji. Jest to informacja dla powłoki bash, że wszystkie dane wygenerowane w kodzie funkcji należy przekierować we wskazany sposób. Funkcja USAGE (wiersze 21. – 24.), mimo że obejmuje kod, który nie musiał być zapisany w niezależnej funkcji, znacznie usprawnia wyświetlanie informacji o sposobie wykorzystania skryptu. W komunikacie o zasadach użycia skryptu nie została zapisana na stałe nazwa tego skryptu, lecz specjalna zmienna $0. Takie rozwiązanie zapewnia poprawne działanie mechanizmu nawet po zmianie nazwy programu. Zmienna $0 przechowuje bowiem pełną ścieżkę dostępu do pliku skryptu (np. /usr/local/bin/mkalbm), która jest wykorzystywana podczas przygotowywania komunikatu dla użytkownika. Zastosowanie dodatkowej funkcji basename (wiersz 23.) pozwala na usunięcie początkowej treści ścieżki dostępu i pozostawienie samej nazwy pliku. Funkcja EMIT (wiersze 27. – 65.) jest znacznie bardziej rozbudowanym blokiem kodu. Jej zadanie polega na wygenerowaniu dokumentu HTML dla każdej strony albumu. Każda strona albumu jest zatem niezależną (statyczną) stroną internetową z odsyłaczami do pierwszego, poprzedniego, następnego i ostatniego zdjęcia. Funkcja EMIT nie pobiera zbyt wielu parametrów. Wymaga dostarczenia jedynie nazw plików zdjęć, do których zostaną utworzone odsyłacze. Wspomniane nazwy plików są zamieniane na nazwy strony, które w prezentowanym skrypcie różnią się od nazw zdjęć jedynie rozszerzeniem (pliki stron mają zmienione rozszerzenie na html). Jeśli więc zmienna $2 będzie przechowywała nazwę zdjęcie001.jpg, instrukcja ${2%.*}.html zmieni nazwę na zdjęcie001.html.

12.2. Przeglądanie albumu ze zdjęciami

|

271

Z uwagi na dużą ilość generowanego kodu HTML, zamiast niekończących się sekwencji poleceń printf została wykorzystana instrukcja cat oraz mechanizm osadzanego dokumentu (wiersz 46.). Dzięki temu można w treści skryptu zamieścić gotowy blok kodu HTML uwzględniający podmianę wartości zmiennych powłoki. Polecenie cat po prostu kopiuje (dołącza) dane standardowego strumienia wejściowego (STDIN) do standardowego strumienia wyjściowego (STDOUT). W opisywanym rozwiązaniu dane wejściowe zostały przekierowane z bloku tekstu, tj. z osadzonego dokumentu. Dzięki temu, że ogranicznik osadzonego dokumentu nie został uzupełniony o znaki apostrofu lub lewego ukośnika (zamiast ciągu EOF nie został zdefiniowany ciąg 'EOF' lub \EOF), powłoka wykonuje operacje podmiany wartości zmiennych, co z kolei pozwala na wstawienie nagłówka strony oraz odsyłaczy do innych dokumentów albumu. Do funkcji EMIT można było również przekazać nazwę pliku wynikowego, do którego funkcja skierowałaby dane wyjściowe. Jednak operacja przekierowania nie wydaje się logiczną częścią funkcji EMIT (w przeciwieństwie do funkcji ERROUT, której celem jest przekierowanie informacji). Jej zadanie polega jedynie na utworzeniu dokumentu HTML. Dalszy sposób przetwarzania dokumentu jest odrębnym zagadnieniem. Mechanizm przekierowania danych w powłoce bash jest tak łatwy w użyciu, że nic nie stoi na przeszkodzie, aby wykonać tę operację w oddzielnym kroku. Poza tym znacznie łatwiej debuguje się skrypt, gdy funkcja przekazuje informacje wyjściowe do strumienia STDOUT. Dwa ostatnie polecenia (wiersze 110. i 113.) tworzą dowiązania symboliczne do pierwszego i ostatniego zdjęcia. Dzięki temu skrypt nie musi ustalać nazw plików pierwszego i ostatniego dokumentu albumu. Generując każdą stronę HTML, wykorzystuje na stałe zdefiniowane nazwy index.html i last.html. Zadanie to jest wykonywane jako ostatnie, ponieważ ostatnia przetwarzana nazwa pliku odnosi się do ostatniego zdjęcia albumu i można wówczas utworzyć dowiązanie symboliczne do tego pliku. Nazwa pierwszej strony jest znana na początku pracy skryptu, ale ze względu na przejrzystość kodu obydwie operacje zostały zdefiniowane obok siebie.

Zobacz również • http://www.w3schools.com. • Książka Chucka Musiano i Billa Kennedy’ego HTML i XHTML. Przewodnik encyklopedyczny

(Helion 2001). • Receptura 3.2, „Umieszczenie danych w skrypcie”. • Receptura 3.3, „Wyeliminowanie nietypowego działania osadzonych dokumentów”. • Receptura 3.4, „Wcinanie osadzonych dokumentów”. • Receptura 5.13, „Pobieranie wartości domyślnych”. • Receptura 5.14, „Ustawianie wartości domyślnych”. • Receptura 5.18, „Modyfikacja fragmentów ciągu tekstowego”. • Receptura 5.19, „Wykorzystanie zmiennych tablicowych”. • Receptura 9.5, „Wyszukiwanie plików bez względu na wielkość liter występujących w na-

zwach”. • Receptura 16.9, „Własny zbiór narzędzi — dodanie ścieżki ~/bin”.

272

|

Rozdział 12. Skrypty usprawniające pracę użytkownika

12.3. Zapis danych w odtwarzaczu MP3 Problem Dysponujemy plikami MP3, które powinny zostać zapisane w odtwarzaczu MP3. Problem tkwi w tym, że łączny rozmiar tych plików przekracza pojemność odtwarzacza. Czy istnieje sposób na zapisanie utworów muzycznych w odtwarzaczu bez konieczności przeciągania i upuszczania poszczególnych plików do czasu zapełnienia pamięci odtwarzacza?

Rozwiązanie Za pomocą odpowiednio przygotowanego skryptu powłoki można analizować ilość wolnego miejsca w czasie kopiowania plików do odtwarzacza MP3. Gdy pamięć zostanie zapełniona, skrypt powinien zakończyć swoje działanie. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

#!/usr/bin/env bash # plik receptury: load_mp3 # Skrypt zapełnia odtwarzacz MP3 maksymalną liczbą utworów. # Założenie: odtwarzacz MP3 jest zamontowany w katalogu /media/mp3 # # # wyznaczenie rozmiaru pliku # function FILESIZE () { FN=${1:-/dev/null} if [[ -e $FN ]] then # FZ=$(ls -s $FN | cut -d ' ' -f 1) set -- $(ls -s "$FN") FZ=$1 fi } # # obliczenie ilości wolnego miejsca w pamięci odtwarzacza MP3 # function FREESPACE { # FREE=$(df /media/mp3 | awk '/^\/dev/ {print $4}') set -- $(df /media/mp3 | grep '^/dev/') FREE=$4 } # odjęcie (przekazanego) rozmiaru pliku od (globalnej) wartości wolnego miejsca function REDUCE () (( FREE-=${1:-0})) # # główna część skryptu # let SUM=0 let COUNT=0 export FZ export FREE FREESPACE find . -name '*.mp3' -print | \

12.3. Zapis danych w odtwarzaczu MP3

|

273

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

(while read PATHNM do FILESIZE "$PATHNM" if ((FZ contentwnl.xml cd "$PRIV2" unzip -q "$FULL2" sed -e 's/>/>\ /g' -e 's//>\ /g' | grep ' w poniższym zestawieniu odpowiada znakom tabulatora. Domyślnym znakiem rozdziału jest znak spacji, ale ustawienie to można zmienić za pomocą zmiennej $OFS. # Nazwy kont, katalogi domowe i nazwy powłok # Zamiana dwóch ostatnich pól i rozdzielenie pól znakiem tabulatora $ awk 'BEGIN {FS=":"; OFS="\t"; } { print $1,$7,$6; }' /etc/passwd root --> /bin/bash --> /root bin --> /sbin/nologin --> /bin daemon --> /sbin/nologin --> /sbin operator --> /sbin/nologin --> /root games --> /sbin/nologin --> /usr/games gopher --> /sbin/nologin --> /var/gopher ftp --> /sbin/nologin --> /var/ftp nobody --> /sbin/nologin --> / named --> /sbin/nologin --> /var/named apache --> /sbin/nologin --> /dev/null marek --> /bin/false --> /home/marek ossec --> /sbin/nologin --> /var/ossec ossecm --> /sbin/nologin --> /var/ossec ossece --> /sbin/nologin --> /var/ossec ossecr --> /sbin/nologin --> /var/ossec danka --> /bin/bash --> /home/danka # Podział w miejscach znaków odstępu, zamiana pól, usunięcie pierwszego pola $ grep '^# [1-9]' /etc/hosts | awk '{print $3,$2}' 10.255.255.255 10.0.0.0 172.31.255.255 172.16.0.0 192.168.255.255 192.168.0.0

300 |

Rozdział 13. Interpretacja danych i podobne zadania

Dodanie opcji –o do polecenia grep powoduje wyświetlenie jedynie tej części wiersza, która dokładnie odpowiada wzorcowi. Funkcja ta jest szczególnie użyteczna, gdy nie można opisać ograniczników w sposób, który umożliwiłby zastosowanie przedstawionego wcześniej rozwiązania. Przykładem może tu być konieczność wyodrębnienia wszystkich adresów IP niezależnie od tego, w którym fragmencie pliku są zapisane. Ponieważ do wykonania zadania trzeba użyć wyrażenia regularnego (regex), w kolejnym przykładzie zostanie zastosowane polecenia egrep. Nie zmienia to faktu, że opcja –o działa zgodnie z oczekiwaniami we wszystkich odmianach instrukcji grep pakietu projektu GNU. Prawdopodobnie jednak nie jest obsługiwana w poleceniach spoza pakietu GNU. Dokładne informacje na ten temat są zawarte w dokumentacji oprogramowania. $ cat plik_z_IP To jest 1. wiersz z 1 adresem IP: 10.10.10.10 W wierszu 2. są 2 adresy: 10.10.10.11 oraz 10.10.10.12 Wiersz trzeci zawiera adres serwera FTP: ftp_server=10.10.10.13:21 $ egrep -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' plik_z_IP 10.10.10.10 10.10.10.11 10.10.10.12 10.10.10.13

Analiza Liczba możliwych zastosowań dla opisanych rozwiązań jest praktycznie nieskończona. Przedstawione przykłady są jedynie pewną namiastką potencjalnych konstrukcji, które stanowią o wyjątkowości uniksowych sekwencji poleceń. Połączenie wielu nieskomplikowanych narzędzi, które doskonale realizują jedno zadanie, umożliwia rozwiązywanie wielu problemów. Wykorzystane w przykładzie wyrażenie regularne również nie należy do najbardziej skomplikowanych. Mechanizm ten pozwala bowiem na wyszukiwanie znacznie bardziej złożonych ciągów, na przykład adresów poczty elektronicznej. Jeżeli zainstalowany w danym systemie program grep obsługuje opcję -P, można przygotować znacznie efektywniejszy wzorzec, bazujący na wyrażeniach regularnych zgodnych z wyrażeniami języka Perl (PCRE — Perl Compatible Regular Expressions) i opisanych w książce Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001). Inne rozwiązanie polega na użyciu języka Perl. $ grep -oP '([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d \d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])' plik_z_IP 10.10.10.10 10.10.10.11 10.10.10.12 10.10.10.13 $ perl -ne 'while ( m/([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\. ([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])/g ) { print qq($1.$2.$3.$4\n); }' plik_z_IP 10.10.10.10 10.10.10.11 10.10.10.12 10.10.10.13

Zobacz również • Polecenie man cut. • Polecenie man awk.

13.12. Wyodrębnianie określonych pól listingu danych

|

301

• Polecenie man grep. • Książka Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001). • Receptura 8.4, „Wycinanie fragmentów listingu wynikowego”. • Receptura 13.14, „Usuwanie krańcowych znaków odstępu”. • Receptura 15.10, „Ustalanie własnych adresów IP”. • Receptura 17.16, „Wyszukiwanie wierszy tylko jednego pliku”.

13.13. Modyfikacja określonych pól listingu danych Problem Chcemy wyodrębnić określone fragmenty (pola) wiersza (rekordu) danych, a następnie zmodyfikować je.

Rozwiązanie Jeżeli problem dotyczy jedynie pobrania wartości pojedynczego pola wiersza danych i wykonania na niej pewnych operacji, wystarczy zastosować polecenie cut lub awk. Zagadnienie to zostało szczegółowo omówione w recepturze 13.12, „Wyodrębnianie określonych pól listingu danych”. Trudniejszym zadaniem jest zmodyfikowanie wartości pola danych bez wyodrębniania jej z listingu. Jeśli sprowadza się ono do wyszukania i zastąpienia informacji, można zastosować mechanizm sed. Przykładem może tu być konieczność zastąpienia powłoki csh interpreterem sh w kontach wszystkich użytkowników systemu NetBSD. $ grep csh /etc/passwd root:*:0:0:Charlie &:/root:/bin/csh $ sed 's/csh$/sh/' /etc/passwd | grep '^root' root:*:0:0:Charlie &:/root:/bin/sh

Do wykonywania działań arytmetycznych na wartościach pól lub modyfikowania ciągu tekstowego pojedynczego wiersza doskonale nadaje się program awk: $ cat plik_danych Wiersz nr 1 Wiersz nr 2 Wiersz nr 3 Wiersz nr 4 Wiersz nr 5 $ awk '{print $1, $2, $3+5}' plik_danych Wiersz nr 6 Wiersz nr 7 Wiersz nr 8 Wiersz nr 9 Wiersz nr 10

302

|

Rozdział 13. Interpretacja danych i podobne zadania

# Jeśli w trzecim polu jest liczba 4, zostanie oznaczona i zastąpiona liczbą 8. $ awk '{ if ($3=="4") print $1, $2, $3+5, "Zmieniony"; else print $0; }' plik_danych Wiersz nr 1 Wiersz nr 2 Wiersz nr 3 Wiersz nr 9 Zmieniony Wiersz nr 5

Analiza Liczba możliwych zastosowań przedstawionych rozwiązań jest nieskończona. Wnikliwa analiza powyższego kodu powinna ułatwić zmagania z problemem modyfikacji danych dowolnych listingów.

Zobacz również • Polecenie man awk. • Polecenie man sed. • http://sed.sourceforge.net/sedfaq.html. • http://sed.sourceforge.net/sed1line.txt. • Receptura 11.7, „Obliczanie daty i czasu”. • Receptura 13.12, „Wyodrębnianie określonych pól listingu danych”.

13.14. Usuwanie krańcowych znaków odstępu Problem Chcemy usunąć początkowe i (lub) końcowe znaki odstępu występujące w poszczególnych polach danych.

Rozwiązanie Proponowane rozwiązanie bazuje na szczególnym sposobie obsługiwania przez powłokę bash instrukcji read i zmiennej $REPLY. Sposób alternatywny został opisany w końcowej części punku Rozwiązanie. Najpierw wyświetlmy na ekranie plik zawierający początkowe i końcowe znaki odstępu. Aby ułatwić analizę wyników, w poniższych listingach zostały dodane znaki ~~. Dzięki nim bez trudu można zauważyć początkowe i końcowe znaki spacji. Znaki tabulatora zostały natomiast oznaczone symbolem -->. # Wyświetlenie znaków odstępu w przykładowym pliku $ while read; do echo ~~"$REPLY"~~; done < odstępy ~~ W tym wierszu występuje początkowy znak spacji.~~ ~~W tym wierszu występuje końcowy znak spacji. ~~ ~~ W tym wierszu występuje zarówno początkowy, jak i końcowy znak spacji. ~~ ~~ --> Początkowy tabulator.~~ ~~Końcowy tabulator. --> ~~ ~~ --> Początkowy i końcowy tabulator. --> ~~

13.14. Usuwanie krańcowych znaków odstępu

| 303

~~ --> Początkowe znaki spacji i tabulatora.~~ ~~Końcowe znaki spacji i tabulatora. --> ~~ ~~ --> Początkowe i końcowe znaki spacji i tabulatora.

--> ~~

Aby usunąć zarówno początkowe, jak i końcowe znaki odstępu, można wykorzystać parametr $IFS oraz wbudowaną zmienną REPLY (zasada działania instrukcji została opisana w punkcie Analiza). $ while read REPLY; do echo ~~"$REPLY"~~; done < odstępy ~~W tym wierszu występuje początkowy znak spacji.~~ ~~W tym wierszu występuje końcowy znak spacji.~~ ~~W tym wierszu występuje zarówno początkowy, jak i końcowy znak spacji.~~ ~~Początkowy tabulator.~~ ~~Końcowy tabulator.~~ ~~Początkowy i końcowy tabulator.~~ ~~Początkowe znaki spacji i tabulatora.~~ ~~Końcowe znaki spacji i tabulatora.~~ ~~Początkowe i końcowe znaki spacji i tabulatora.~~

Aby usunąć jedynie początkowe znaki spacji lub tyko końcowe znaki spacji, trzeba użyć nieskomplikowanego wzorca dopasowania. # Usunięcie tylko początkowych znaków spacji $ while read; do echo "~~${REPLY##}~~"; done < odstępy ~~W tym wierszu występuje początkowy znak spacji.~~ ~~W tym wierszu występuje końcowy znak spacji. ~~ ~~W tym wierszu występuje zarówno początkowy, jak i końcowy znak spacji. ~~ ~~ --> Początkowy tabulator.~~ ~~Końcowy tabulator. --> ~~ ~~ --> Początkowy i końcowy tabulator. --> ~~ ~~ --> Początkowe znaki spacji i tabulatora.~~ ~~Końcowe znaki spacji i tabulatora. --> ~~ ~~ --> Początkowe i końcowe znaki spacji i tabulatora. --> ~~ # Usunięcie tylko końcowych znaków spacji $ while read; do echo "~~${REPLY%%}~~"; done < odstępy ~~ W tym wierszu występuje początkowy znak spacji.~~ ~~W tym wierszu występuje końcowy znak spacji.~~ ~~ W tym wierszu występuje zarówno początkowy, jak i końcowy znak spacji.~~ ~~ --> Początkowy tabulator.~~ ~~Końcowy tabulator. --> ~~ ~~ --> Początkowy i końcowy tabulator. --> ~~ ~~ --> Początkowe znaki spacji i tabulatora.~~ ~~Końcowe znaki spacji i tabulatora. --> ~~ ~~ --> Początkowe i końcowe znaki spacji i tabulatora. --> ~~

Usunięcie końcowych lub początkowych znaków odstępu (w tym znaków tabulatora) jest nieco bardziej skomplikowane: # Ta instrukcja jest potrzebna w obydwu przypadkach $ shopt -s extglob # Usunięcie jedynie początkowych znaków odstępu $ while read; do echo "~~${REPLY##+([[:space:]])}~~"; done < odstępy ~~W tym wierszu występuje początkowy znak spacji.~~ ~~W tym wierszu występuje końcowy znak spacji. ~~ ~~W tym wierszu występuje zarówno początkowy, jak i końcowy znak spacji. ~~ ~~Początkowy tabulator.~~ ~~Końcowy tabulator. --> ~~ ~~Początkowy i końcowy tabulator. --> ~~ ~~Początkowe znaki spacji i tabulatora.~~ ~~Końcowe znaki spacji i tabulatora. --> ~~ ~~Początkowe i końcowe znaki spacji i tabulatora. --> ~~

304 |

Rozdział 13. Interpretacja danych i podobne zadania

# Usunięcie jedynie końcowych znaków odstępu $ while read; do echo "~~${REPLY%%+([[:space:]])}~~"; done < odstępy ~~ W tym wierszu występuje początkowy znak spacji.~~ ~~W tym wierszu występuje końcowy znak spacji.~~ ~~ W tym wierszu występuje zarówno początkowy, jak i końcowy znak spacji.~~ ~~ --> Początkowy tabulator.~~ ~~Końcowy tabulator.~~ ~~ --> Początkowy i końcowy tabulator.~~ ~~ --> Początkowe znaki spacji i tabulatora.~~ ~~Końcowe znaki spacji i tabulatora.~~ ~~ --> Początkowe i końcowe znaki spacji i tabulatora.~~

Analiza Zapoznając się z przedstawionymi listingami, wiele osób z pewnością zastanawiało się, czy można ten mechanizm opisać w sposób dostatecznie przejrzysty, aby stał się zrozumiały. Okazuje się, że nie stanowi to szczególnego problemu, jeśli omówienie zasadniczego algorytmu zostanie poprzedzone stosownym wprowadzeniem. W pierwszym przykładzie działanie kodu bazuje na wykorzystaniu zmiennej $REPLY, która jest powoływana przez polecenie read wówczas, gdy użytkownik nie określi własnej zmiennej. Chet Ramey (menedżer projektu bash) przyjął założenie, że „jeśli nie zostaną wskazane żadne zmienne, tekst odczytany za pomocą instrukcji read zostanie zapisany w zmiennej $REPLY (tekst pozostaje niezmieniony lub podzielony z wykorzystaniem zmiennej $IFS)”. $ while read; do echo ~~"$REPLY"~~; done < odstępy

Jeżeli jednak instrukcja read zostanie uzupełniona o nazwę zmiennej lub nazwy zmiennych, dane wejściowe zostaną poddane interpretacji z wykorzystaniem znaków separatora zapisanych w zmiennej $IFS (standardowo są to znaki spacji, tabulatora i nowego wiersza). Jedna z czynności procesu interpretacji polega na usunięciu początkowych i końcowych znaków odstępu, co jest celem opisywanego polecenia. $ while read REPLY; do echo ~~"$REPLY"~~; done < odstępy

Usunięcie początkowych lub końcowych znaków spacji (ale nie jednoczesne) nie stanowi problemu, gdyż można zastosować operatory ${##} i ${%%} (zagadnienie to zostało szczegółowo opisane w recepturze 6.7, „Sprawdzanie zgodności wartości ze wzorcem”). $ while read; do echo "~~${REPLY##}~~"; done < odstępy $ while read; do echo "~~${REPLY%%}~~"; done < odstępy

Jednak objęcie działaniem tego mechanizmu dodatkowo znaków tabulatora okazuje się nieco trudniejsze. Gdyby rzecz dotyczyła jedynie znaków tabulatora, można by wykorzystać operatory ${##} i ${%%} i wstawić faktyczny znak tabulacji, naciskając kolejno klawisze Ctrl+V oraz Ctrl+I. Zastosowanie tej metody może być jednak trochę ryzykowne, ponieważ najprawdopodobniej znaki tabulacji będą przeplecione ze znakami spacji. Dlatego konieczne jest włączenie rozszerzonego mechanizmu dopasowywania do wzorca i wykorzystanie klas znaków, które precyzyjnie opisują intencje programisty. Klasa [:space:] jest poprawnie interpretowana również z wyłączoną opcją extglob, ale w instrukcji musi zostać zawarta informacja „jedno wystąpienie lub większa ich liczba”, do czego służy konstrukcja +() (wymagająca włączenia opcji extglob). Pominięcie jej spowodowałoby usunięcie pojedynczego znaku spacji lub tabulatora, a nie wszystkich wystąpień tych znaków.

13.14. Usuwanie krańcowych znaków odstępu

| 305

# $ $ $

To rozwiązanie jest właściwe. Wymaga włączenia opcji extglob ze względu na zapis +(). shopt -s extglob while read; do echo "~~${REPLY##+([[:space:]])}~~"; done < odstępy while read; do echo "~~${REPLY%%+([[:space:]])}~~"; done < odstępy

# To rozwiązanie jest błędne. $ while read; do echo "~~${REPLY##[[:space:]]}~~"; done < odstępy ~~W tym wierszu występuje początkowy znak spacji.~~ ~~W tym wierszu występuje końcowy znak spacji. ~~ ~~W tym wierszu występuje zarówno początkowy, jak i końcowy znak spacji. ~~ ~~Początkowy tabulator.~~ ~~Końcowy tabulator.~~ ~~Początkowy i końcowy tabulator. --> ~~ ~~ --> Początkowe znaki spacji i tabulatora.~~ ~~Końcowe znaki spacji i tabulatora. --> ~~ ~~ --> Początkowe i końcowe znaki spacji i tabulatora. --> ~~

Kolejny listing jest przykładem innego podejścia do problemu i wykorzystania zmiennej $IFS do interpretacji danych, ale tym razem zapisanych w polach (słowach), a nie w rekordach (wierszach): $ for i in $(cat odstępy);do echo ~~$i~~; done ~~W~~ ~~tym~~ ~~wierszu~~ ~~występuje~~ ~~początkowy~~ ~~znak~~ ~~spacji.~~ ~~W~~ ~~tym~~ ~~wierszu~~ ~~występuje~~ ~~końcowy~~ ~~znak~~ ~~spacji.~~ ~~W~~ ~~tym~~ ~~wierszu~~ ~~występuje~~ ~~zarówno~~ ~~początkowy,~~ ~~jak~~ ~~i~~ ~~końcowy~~ ~~znak~~ ~~spacji.~~

Dotychczas opisywane techniki usuwania znaków odstępu bazują na założeniu Cheta Rameya o sposobie działania instrukcji read i zmiennej REPLY. Rozwiązanie przedstawione poniżej nie wykorzystuje omówionej zależności. shopt -s extglob while IFS= read -r line; do echo "Brak: ~~$line~~" echo "Pocz: ~~${line##+([[:space:]])}~~" echo "Końc: ~~${line%%+([[:space:]])}~~" line="${line##+([[:space:]])}" line="${line%%+([[:space:]])}" echo "Wszystkie: ~~$line~~" done < odstępy

306

|

Rozdział 13. Interpretacja danych i podobne zadania

Zobacz również • Receptura 6.7, „Sprawdzanie zgodności wartości ze wzorcem”. • Receptura 13.6, „Interpretacja tekstu z wykorzystaniem instrukcji read”.

13.15. Kompresowanie znaków odstępu Problem W pliku występuje seria znaków odstępu (treść pliku jest dopełniana określoną liczbą znaków spacji). Chcemy zastąpić wszystkie znaki odstępu pojedynczym znakiem pełniącym funkcję ogranicznika.

Rozwiązanie Należy w odpowiedni sposób wykorzystać polecenie tr lub mechanizm awk.

Analiza Chcąc skompresować serię znaków odstępu do jednego znaku, można zastosować polecenie tr, ale trzeba mieć świadomość ryzyka zniszczenia pliku, jeśli nie jest on właściwie sformatowany. Na przykład jeżeli poszczególne pola są wyznaczane za pomocą kilku znaków odstępu, ale w treści tych pól występują znaki spacji, zastąpienie wielu wystąpień znaku spacji jednym uniemożliwi rozróżnienie pól. Nietrudno się domyślić, jaki byłby rezultat prezentowanych poniżej instrukcji, gdyby zamiast znaków _ w treści pliku występowały znaki spacji. Znaki tabulatora zostały na listingu oznaczone za pomocą symbolu -->. $ cat plik_danych Nagłówek1 Rekord1_Pole1 Rekord2_Pole1 Rekord3_Pole1

Nagłówek2 Rekord1_Pole2 Rekord2_Pole2 Rekord3_Pole2

Nagłówek3 Rekord1_Pole3 Rekord2_Pole3 Rekord3_Pole3

$ cat plik_danych | tr –s ' ' '\t' Nagłówek1 --> Nagłówek2 --> Nagłówek3 Rekord1_Pole1 --> Rekord1_Pole2 --> Rekord1_Pole3 Rekord2_Pole1 --> Rekord2_Pole2 --> Rekord2_Pole3 Rekord3_Pole1 --> Rekord3_Pole2 --> Rekord3_Pole3

Jeśli ogranicznik pola składa się z większej liczby różnych znaków, instrukcja tr nie spełni swojego zadania, ponieważ odpowiada ona za translację pojedynczych znaków z pierwszego zbioru na odpowiadające im znaki w drugim zbiorze. Grupowanie i konwersja separatorów pól jest specjalnością polecenia awk. Wewnętrzny separator instrukcji awk — zmienna FS — umożliwia definiowanie wrażeń regularnych, co pozwala na separację tekstu o niemal dowolnej treści. Cała procedura staje się jeszcze łatwiejsza, jeżeli zastosuje się pewną opisaną dalej sztuczkę. Zamieszczenie odwołania do któregokolwiek pola wymusza na mechanizmie awk złożenie (reasemblację) rekordu z wykorzystaniem separatora pól wynikowych OFS (ang. Output Field Separator). Zatem przypisanie do zmiennej pola tego samego pola podczas wyświetlania rekordu powoduje zastąpienie znaków FS znakami OFS niezależnie od liczby rekordów w zbiorze danych.

13.15. Kompresowanie znaków odstępu

|

307

W kolejnym przykładzie pola danych są rozdzielone wieloma znakami spacji. Spacje występują jednak również w treści samych pól. W takim przypadku zwykła instrukcja awk 'BEGIN { OFS = "\t" } { $1 = $1; print }' plik_danych nie zadziała zgodnie z oczekiwaniami. Oto treść pliku danych: $ cat plik_danych2 Nagłówek1 Rekord1 Pole1 Rekord2 Pole1 Rekord3 Pole1

Nagłówek2 Rekord1 Pole2 Rekord2 Pole2 Rekord3 Pole2

Nagłówek3 Rekord1 Pole3 Rekord2 Pole3 Rekord3 Pole3

Aby rozwiązać ten problem, przypiszemy dwa znaki spacji do zmiennej FS oraz znak tabulatora do zmiennej OFS. Dzięki przypisaniu ($1 = $1) mechanizm awk zmieni budowę rekordu, ale serie podwójnych znaków spacji zostaną zastąpione seriami znaków tabulatora. Dlatego do usunięcia nadmiaru znaków tabulatora zostanie wykorzystana instrukcja gsub. Znaki tabulatora zostały na listingu oznaczone za pomocą symbolu -->. Ponieważ wynik jest dość trudny do przeanalizowania, klasyczny listing został uzupełniony o zrzut wartości szesnastkowych. Znak tabulatora ma w kodzie ASCII wartość 09, a znak spacji — 20. $ awk 'BEGIN { FS = " "; OFS = "\t" } { plik_danych2 Nagłówek1 --> Nagłówek2 --> Nagłówek3 Rekord1 --> Pole1 --> Rekord1 --> Pole2 Rekord2 --> Pole1 --> Rekord2 --> Pole2 Rekord3 --> Pole1 --> Rekord3 --> Pole2 $ awk 'BEGIN plik_danych2 00000000 4e 00000010 65 00000020 6b 00000030 72 00000040 31 00000050 50 00000060 6c 00000070 33 00000080 52 00000090 6b

{ FS = " "; OFS = | hexdump -C 61 67 b3 f3 77 65 6b 32 09 4e 61 67 6f 72 64 31 09 50 64 31 09 50 6f 6c 09 50 6f 6c 65 33 6f 6c 65 31 09 52 65 32 09 52 65 6b 0a 52 65 6b 6f 72 65 6b 6f 72 64 33 6f 72 64 33 09 50

$1 = $1; gsub(/\t+/, "\t"); print }' --> Rekord1 --> Pole3 --> Rekord2 --> Pole3 --> Rekord3 --> Pole3

"\t" } { $1 = $1; gsub(/\t+/, "\t"); print }' 6b b3 6f 65 0a 65 6f 64 09 6f

31 f3 6c 32 52 6b 72 33 50 6c

09 77 65 09 65 6f 64 09 6f 65

4e 65 31 52 6b 72 32 50 6c 33

61 6b 09 65 6f 64 09 6f 65 0a

67 33 52 6b 72 32 50 6c 32 0a

b3 0a 65 6f 64 09 6f 65 09

f3 52 6b 72 32 50 6c 31 52

77 65 6f 64 09 6f 65 09 65

|Nag..wek1.Nag..w| |ek2.Nag..wek3.Re| |kord1.Pole1.Reko| |rd1.Pole2.Rekord| |1.Pole3.Rekord2.| |Pole1.Rekord2.Po| |le2.Rekord2.Pole| |3.Rekord3.Pole1.| |Rekord3.Pole2.Re| |kord3.Pole3..|

W ten sam sposób można wykorzystać polecenie awk do usuwania początkowych i końcowych znaków odstępu. Jednak zgodnie z zamieszczoną wcześniej informacją, zastąpione zostaną również separatory pól, chyba że pierwotnie były one znakami spacji. # Usunięcie początkowych i końcowych znaków odstępu. # Powoduje również zastąpienie spacjami tabulatorów, pełniących funkcję separatorów pól. $ awk '{ $1 = $1; print }' odstępy

Zobacz również • Książka Arnolda Robbinsa Effective awk Programming (O’Reilly 2001). • Książka Dale’a Dougherty’ego i Arnolda Robbinsa sed i awk (Helion 2002). • Receptura 13.16, „Przetwarzanie pól o stałej długości”. • Punkt „Symbole specjalne polecenia tr” w dodatku A. • Punkt „Tabela wartości ASCII” w dodatku A.

308 |

Rozdział 13. Interpretacja danych i podobne zadania

13.16. Przetwarzanie pól o stałej długości Problem Chcemy odczytać i poddać analizie dane, które są zapisywane w polach o określonej długości.

Rozwiązanie Należy wykorzystać interpreter Perl lub program gawk w wersji 2.13 bądź późniejszej. Załóżmy, że plik danych zawiera następujące informacje: $ cat stała_długość Kolumna1------------Kolumna2----------------------Kolumna3 Rekord1 Pole1 Rekord1 Pole2 Rekord1 Pole3 Rekord2 Pole1 Rekord2 Pole2 Rekord2 Pole3 Rekord3 Pole1 Rekord3 Pole2 Rekord3 Pole3

Przetwarzanie danych można zlecić programowi GNU gawk, ustawiając odpowiednią długość pól w zmiennej FIELDWIDTHS, wyznaczając odpowiednią wartość zmiennej OFS oraz definiując operację przypisania w taki sposób, aby mechanizm gawk mógł przepisać dany rekord (zagadnienie to zostało opisane w recepturze 13.14, „Usuwanie krańcowych znaków odstępu”). Polecenie gawk nie zapewni jednak usunięcia znaków spacji wykorzystanych do wypełnienia pierwotnego rekordu. Zadanie to zostanie więc powierzone instrukcjom gsub — jednej przetwarzającej wszystkie pola wewnętrzne i drugiej obejmującej swoim działaniem ostatnie pole każdego rekordu. Ostatnia czynność polega na wyświetleniu przygotowanego ciągu. Znaki tabulatora zostały na listingu oznaczone za pomocą symbolu -->. Ponieważ wynik jest dość trudny do przeanalizowania, klasyczny listing został uzupełniony o zrzut wartości szesnastkowych. Znak tabulatora ma w kodzie ASCII wartość 09, a znak spacji — 20. $ gawk ' BEGIN { FIELDWIDTHS = "20 30 14"; OFS = "\t" } { $1 = $1; gsub(/ +\t/, "\t"); gsub(/ +$/, ""); print }' stała_długość Kolumna1------------ --> Kolumna2---------------------- --> Kolumna3 Rekord1 Pole1 --> Rekord1 Pole2 --> Rekord1 Pole3 Rekord2 Pole1 --> Rekord2 Pole2 --> Rekord2 Pole3 Rekord3 Pole1 --> Rekord3 Pole2 --> Rekord3 Pole3 $ gawk ' BEGIN { FIELDWIDTHS = "20 30 14"; OFS = "\t" } gsub(/ +$/, ""); print }' stała_długość | hexdump -C 00000000 4b 6f 6c 75 6d 6e 61 31 2d 2d 2d 2d 2d 2d 2d 00000010 2d 2d 2d 2d 09 4b 6f 6c 75 6d 6e 61 32 2d 2d 00000020 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 00000030 2d 2d 2d 09 4b 6f 6c 75 6d 6e 61 33 0a 52 65 00000040 6f 72 64 31 20 50 6f 6c 65 31 09 52 65 6b 6f 00000050 64 31 20 50 6f 6c 65 32 09 52 65 6b 6f 72 64 00000060 20 50 6f 6c 65 33 0a 52 65 6b 6f 72 64 32 20 00000070 6f 6c 65 31 09 52 65 6b 6f 72 64 32 20 50 6f 00000080 65 32 09 52 65 6b 6f 72 64 32 20 50 6f 6c 65 00000090 0a 52 65 6b 6f 72 64 33 20 50 6f 6c 65 31 09 000000a0 65 6b 6f 72 64 33 20 50 6f 6c 65 32 09 52 65 000000b0 6f 72 64 33 20 50 6f 6c 65 33 0a 0a 000000bc

{ $1 = $1; gsub(/ +\t/, "\t"); 2d 2d 2d 6b 72 31 50 6c 33 52 6b

|Kolumna1--------| |----.Kolumna2---| |----------------| |---.Kolumna3.Rek| |ord1 Pole1.Rekor| |d1 Pole2.Rekord1| | Pole3.Rekord2 P| |ole1.Rekord2 Pol| |e2.Rekord2 Pole3| |.Rekord3 Pole1.R| |ekord3 Pole2.Rek| |ord3 Pole3..|

Jeżeli w danym systemie program gawk nie jest dostępny, można wykorzystać interpreter Perl. Rozwiązanie bazujące na skrypcie Perl jest nawet łatwiejsze do zdefiniowania. Została w nim wykorzystana wejściowa pętla while, niewyświetlająca danych (-n). Podczas odczytywania

13.16. Przetwarzanie pól o stałej długości

| 309

informacji każdy rekord został wyodrębniony ($_), a wynikowa lista, po złączeniu pól za pomocą znaku tabulatora, została przekazana jako wartość skalarna. Na końcu każdy rekord został uzupełniony o znak nowego wiersza i wyświetlony. $ perl -ne 'print join("\t", unpack("A20 A30 A14", $_) ) . "\n";' stała_długość Kolumna1------------ --> Kolumna2---------------------- --> Kolumna3 Rekord1 Pole1 --> Rekord1 Pole2 --> Rekord1 Pole3 Rekord2 Pole1 --> Rekord2 Pole2 --> Rekord2 Pole3 Rekord3 Pole1 --> Rekord3 Pole2 --> Rekord3 Pole3 $ perl -ne 'print join("\t", unpack("A20 hexdump -C 00000000 4b 6f 6c 75 6d 6e 61 31 2d 2d 00000010 2d 2d 2d 2d 09 4b 6f 6c 75 6d 00000020 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 00000030 2d 2d 2d 09 4b 6f 6c 75 6d 6e 00000040 6f 72 64 31 20 50 6f 6c 65 31 00000050 64 31 20 50 6f 6c 65 32 09 52 00000060 20 50 6f 6c 65 33 0a 52 65 6b 00000070 6f 6c 65 31 09 52 65 6b 6f 72 00000080 65 32 09 52 65 6b 6f 72 64 32 00000090 0a 52 65 6b 6f 72 64 33 20 50 000000a0 65 6b 6f 72 64 33 20 50 6f 6c 000000b0 6f 72 64 33 20 50 6f 6c 65 33 000000bb

A30 A14", $_) ) . "\n";' stała_długość | 2d 6e 2d 61 09 65 6f 64 20 6f 65 0a

2d 61 2d 33 52 6b 72 32 50 6c 32

2d 32 2d 0a 65 6f 64 20 6f 65 09

2d 2d 2d 52 6b 72 32 50 6c 31 52

2d 2d 2d 65 6f 64 20 6f 65 09 65

2d 2d 2d 6b 72 31 50 6c 33 52 6b

|Kolumna1--------| |----.Kolumna2---| |----------------| |---.Kolumna3.Rek| |ord1 Pole1.Rekor| |d1 Pole2.Rekord1| | Pole3.Rekord2 P| |ole1.Rekord2 Pol| |e2.Rekord2 Pole3| |.Rekord3 Pole1.R| |ekord3 Pole2.Rek| |ord3 Pole3.|

Szablony funkcji pack i unpack zostały opisane w dokumentacji interpretera Perl.

Analiza Każdy użytkownik systemu Unix automatycznie umieści w danych wyjściowych pewien rodzaj separatora. Pamięta bowiem o możliwości definiowania sekwencji poleceń przetwarzania tekstu i wie, że rozwiązania bazujące na polach o stałej długości nie są często stosowane w świecie uniksowym. Są one jednak bardzo popularne środowiskach typu mainframe, więc czasami konieczne jest dostosowanie ich do współpracy z większymi aplikacji, takimi jak aplikacje SAP. Jak można się było przekonać, realizacja tego typu zadania nie jest skomplikowana. Jedyną wadą opisanego rozwiązania jest to, że wymaga ono zakończenia każdego rekordu znakiem nowego wiersza. Ponieważ wiele aplikacji typu mainframe nie wykorzystuje tego formatu, konieczne jest dodanie znaków nowego wiersza przed rozpoczęciem przetwarzania poszczególnych rekordów. Mechanizm ten został opisany w recepturze 13.17, „Przetwarzanie plików niezawierających znaków nowego wiersza”.

Zobacz również • Polecenie man gawk. • http://www.faqs.org/faqs/computer-lang/awk/faq. • http://perldoc.perl.org/functions/unpack.html. • http://perldoc.perl.org/functions/pack.html. • Receptura 13.14, „Usuwanie krańcowych znaków odstępu”. • Receptura 13.17, „Przetwarzanie plików niezawierających znaków nowego wiersza”.

310

|

Rozdział 13. Interpretacja danych i podobne zadania

13.17. Przetwarzanie plików niezawierających znaków nowego wiersza Problem Chcemy poddać przetwarzaniu plik, w którym nie występują znaki nowego wiersza.

Rozwiązanie Należy dokonać wstępnej analizy pliku i dodać znaki nowego wiersza w odpowiednich miejscach. Przykładem mogą tu być pliki zapisane w formacie otwartego dokumentu OpenOffice.org (ODF — Open Document Format), które można rozpakować za pomocą polecenia unzip i przeszukać (kod XML) z wykorzystaniem polecenia grep — operacje tego typu były wielokrotnie opisywane we wcześniejszej części książki. Więcej informacji na temat przetwarzania plików ODF zostało zamieszczonych w recepturze 12.5, „Porównywanie dwóch dokumentów”. W przykładzie analizowanym w tym rozdziale znak nowego wiersza zostanie wstawiony za każdym zamykającym nawiasem trójkątnym (>). Dzięki temu dane będą łatwiejsze do wykorzystania za pomocą polecenia grep lub innego narzędzia tekstowego. Aby zamieścić w skrypcie sed znak nowego wiersza, trzeba nacisnąć klawisz Enter bezpośrednio za znakiem lewego ukośnika: $ wc –l treść.xml 1 treść.xml $ sed –e 's/>/>\ /g' treść.xml | wc –l 1687

Jeżeli w pliku nie ma znaków nowego wiersza, ale występują rekordy o stałej długości, można zastosować rozwiązanie przedstawione poniżej (wartość 48 odpowiada długości rekordu). $ cat stała_długość2 Wiersz_1__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_2__ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_3__ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_4__ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_5__ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_6__ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_7__ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_8__ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_9__ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_10_ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_11_ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZWiersz_12_ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ $ wc –l stała_długość2 1 stała_długość2 $ sed 's/.\{48\}/&\ /g;' stała_długość2 Wiersz_1__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_2__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_3__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_4__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_5__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_6__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ

13.17. Przetwarzanie plików niezawierających znaków nowego wiersza

|

311

Wiersz_7__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_8__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_9__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_10_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_11_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_12_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ $ perl -pe 's/(.{48})/$1\n/g;' stała_długość2 Wiersz_1__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_2__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_3__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_4__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_5__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_6__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_7__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_8__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_9__aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_10_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_11_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ Wiersz_12_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ

Analiza Ten sposób zapisu jest często wykorzystywany przez osoby, które programowo generują listing wynikowy, a szczególnie gdy używają do tego celu osadzanych modułów przygotowujących treść HTML lub XML. Stosując mechanizm podmiany wartości, właściwy dla programu sed, trzeba zwrócić szczególną uwagę na dość nietypowy zapis znaku nowego wiersza. Znak ampersand (&) znajdujący się po prawej stronie operatora podmiany wartości jest zastępowany przez cały ciąg dopasowany za pomocą wyrażenia znajdującego się po lewej stronie. Końcowy znak lewego ukośnika poprzedza znak nowego wiersza, dzięki czemu powłoka poprawnie interpretuje instrukcję. Znak ten pozostaje jednak w obrębie ciągu podmiany. Wynika to z faktu, że mechanizm sed nie rozpoznaje znaku \n jako znaku specjalnego, jeśli występuje on po prawej stronie konstrukcji s///.

Zobacz również • http://sed.sourceforge.net/sedfaq.html. • Książka Arnolda Robbinsa Effective awk Programming (O’Reilly 2001). • Książka Dale’a Dougherty’ego i Arnolda Robbinsa sed i awk (Helion 2002). • Receptura 12.5, „Porównywanie dwóch dokumentów”. • Receptura 13.16, „Przetwarzanie pól o stałej długości”.

13.18. Zapis pliku danych w formacie CSV Problem Chcemy przekształcić plik z danymi w plik formatu CSV (w którym poszczególne wartości są rozdzielane za pomocą znaku przecinka).

312

|

Rozdział 13. Interpretacja danych i podobne zadania

Rozwiązanie Aby przekształcić dane do formatu CSV, wystarczy wykorzystać polecenie awk: $ awk 'BEGIN { FS="\t"; OFS="\",\"" } { gsub(/"/, "\"\""); $1 = $1; printf "\"%s\"\n", $0}' rozdzielone_tabulatorami "Wiersz 1","Pole 1","Pole 2","Pole 3","Pole 4","Pole 5 ze ""znakami"" cudzysłowu" "Wiersz 2","Pole 1","Pole 2","Pole 3","Pole 4","Pole 5 ze ""znakami"" cudzysłowu" "Wiersz 3","Pole 1","Pole 2","Pole 3","Pole 4","Pole 5 ze ""znakami"" cudzysłowu" "Wiersz 4","Pole 1","Pole 2","Pole 3","Pole 4","Pole 5 ze ""znakami"" cudzysłowu"

To samo zadanie można wykonać za pomocą skryptu Perl: $ perl -naF'\t' -e 'chomp rozdzielone_tabulatorami "Wiersz 1","Pole 1","Pole "Wiersz 2","Pole 1","Pole "Wiersz 3","Pole 1","Pole "Wiersz 4","Pole 1","Pole

@F; s/"/""/g for @F; print q(").join(q(","), @F).qq("\n);' 2","Pole 2","Pole 2","Pole 2","Pole

3","Pole 3","Pole 3","Pole 3","Pole

4","Pole 4","Pole 4","Pole 4","Pole

5 5 5 5

ze ze ze ze

""znakami"" ""znakami"" ""znakami"" ""znakami""

cudzysłowu" cudzysłowu" cudzysłowu" cudzysłowu"

Analiza Analizę tego zagadnienia trzeba rozpocząć od stwierdzenia, że definicja formatu CSV jest bardzo niejednoznaczna. Nie istnieje formalna specyfikacja standardu, a różni producenci oprogramowania w różny sposób implementują mechanizmy formatowania CSV. Przedstawione rozwiązanie nie należy do bardzo skomplikowanych i generuje wyniki, które powinny być powszechnie akceptowane. Każde z pól zostało otoczone znakami cudzysłowu (choć w niektórych implementacjach znakami cudzysłowu otaczane są jedynie ciągi tekstowe), a cudzysłowy występujące wewnątrz pól zostały podwojone. Działanie programu awk rozpoczyna się od wyznaczenia znaku tabulatora jako separatora pól danych wejściowych oraz ustawienia znaku przecinka jako separatora pól wyjściowych (OFS). Następnie wszystkie znaki cudzysłowu zostały zastąpione dwoma znakami cudzysłowu, a operacja przypisania umożliwiła mechanizmowi awk utworzenie nowych rekordów (zagadnienie to zostało opisane w recepturze 13.14, „Usuwanie krańcowych znaków odstępu”). Na końcu rekordy są wyświetlane z wykorzystaniem początkowych i końcowych znaków cudzysłowu. Konieczność zabezpieczenia w kilku miejscach znaków cudzysłowu (poprzedzenia ich znakami lewego ukośnika) zmniejszyła czytelność instrukcji, która w innym przypadku byłaby bardzo łatwa do przeanalizowania.

Zobacz również • Pytania i odpowiedzi związane z programem awk. • Receptura 13.14, „Usuwanie krańcowych znaków odstępu”. • Receptura 13.19, „Przetwarzanie plików z danymi CSV”.

13.18. Zapis pliku danych w formacie CSV

|

313

13.19. Przetwarzanie plików z danymi CSV Problem Chcemy przeanalizować plik danych CSV (plik danych rozdzielanych za pomocą znaków przecinka).

Rozwiązanie W przeciwieństwie do rozwiązania przedstawionego w recepturze dotyczącej przekształcania plików w dane CSV, trudno opracować uniwersalny mechanizm interpretacji informacji CSV. Przyczyną jest brak jednolitego standardu CSV. Oto kilka rozwiązań, które warto przeanalizować: • sed — http://sed.sourceforge.net/sedfaq4.html#s4.12. • awk — http://lorance.freeshell.org/csv. • Perl — książka Jeffreya E.F. Friedla Wyrażenia regularne (Helion 2001) — wykorzystanie

wyrażeń regularnych do formowania zestawień CSV. • Perl — CPAN (http://www.cpan.org), witryna udostępniająca różne moduły Perl. • Aby uzyskać zestawienie o wydzielonych polach danych, można również otworzyć plik CSV

w arkuszu kalkulacyjnym (np. OpenOffice.org Calc lub Microsoft Excel), a następnie skopiować dane, wklejając je w edytorze tekstu.

Analiza Zgodnie z informacją zamieszczoną w recepturze 13.18, „Zapis pliku danych w formacie CSV”, nie istnieje formalna specyfikacja standardu CSV. Fakt ten w połączeniu z mnogością różnych implementacji algorytmów generowania danych CSV sprawia, że opracowanie uniwersalnego rozwiązania tego problemu jest znacznie trudniejsze, niżby się mogło początkowo wydawać.

Zobacz również • Receptura 13.18, „Zapis pliku danych w formacie CSV”.

314

|

Rozdział 13. Interpretacja danych i podobne zadania

ROZDZIAŁ 14.

Bezpieczne skrypty powłoki

Tworzenie bezpiecznych skryptów powłoki?! Czy skrypt może być bezpieczny, skoro każdy może zapoznać się z jego kodem źródłowym? System, którego bezpieczeństwo jest oparte na ukrywaniu szczegółów implementacyjnych, wcale nie jest bezpieczny. Wystarczy spytać o to dowolnego doświadczonego programistę, którego kod jest ściśle strzeżony, a mimo to bezustannie pozostaje narażony na działanie aplikacji wykorzystujących luki w oprogramowaniu, tworzonych przez osoby, które nigdy nie widziały tego „bezpiecznego kodu”. Z drugiej strony każdy może przejrzeć ogólnie dostępny kod OpenSSH czy OpenBSD, który mimo braku ograniczeń w dostępie pozostaje bardzo bezpieczny. Technika zabezpieczania aplikacji ograniczająca się do ukrywania kodu nigdy nie gwarantuje bezpieczeństwa w dłuższym okresie. Niemniej niektóre jej formy mogą być postrzegane jako użyteczna dodatkowa warstwa zabezpieczająca. Na przykład przypisywanie usługom sieciowym niestandardowych numerów portów z pewnością zmniejszy prawdopodobieństwo ataku przygotowywanego przez tzw. „dzieciaki skryptowe” (ang. script-kiddies). Niedopuszczalne jest jednak ograniczenie mechanizmu zabezpieczeń tylko do jednej warstwy tego typu. Prędzej czy później ktoś odkryje, że dana usługa została przeniesiona na inny port. Bruce Schneider twierdzi, że zabezpieczanie jest ciągłym procesem. Nie jest to produkt, obiekt lub technika. Prace nad nim nigdy się nie kończą. Nieustanny rozwój technologii sieciowych, metod ataków i obrony wymuszają również właściwie dostosowywanie mechanizmów zabezpieczeń. Co więc oznacza termin bezpieczne skrypty powłoki? Bezpieczne skrypty powłoki charakteryzują się tym, że wykonują powierzone im zadania i tylko powierzone im zadania. Nie pozostawiają możliwości wykorzystania ich do przejęcia uprawnień użytkownika root, nie wykonują niezamierzonych operacji rm –rf /, nie udostępniają utajnionych informacji (na przykład haseł). Muszą być wszechstronne, ale jednocześnie mają wbudowany mechanizm „eleganckiego” przerwania działania w przypadku wystąpienia błędu. Muszą właściwie obsługiwać niezamierzone błędy użytkownika i weryfikować wprowadzane przez niego dane. Ich kod jest możliwie najprostszy, składa się z przejrzystych instrukcji, którym towarzyszy zrozumiała dokumentacja, dzięki czemu przeznaczenie każdego wiersza nie pozostawia jakichkolwiek wątpliwości. Wydaje się, że w ten sposób powinien funkcjonować każdy poprawnie napisany program, czyż nie? Bezpieczne działanie jest elementem, który musi być uwzględniany od początku procesu projektowania aplikacji. Związanych z nim czynności nie wolno odkładać na koniec. W dalszej

315

części rozdziału zostaną omówione najczęściej występujące wady skryptów powłoki oraz sposoby usuwania ich. Zagadnienie to zostało opisane w wielu publikacjach. Osoby szczególnie nim zainteresowane powinny rozpocząć zgłębianie tematu od zapoznania się z książką Gene’a Spafforda Practical UNIX & Internet Security (O’Reilly 1996). Dobrym źródłem informacji jest również 15. rozdział książki Nelsona H.F. Beebe’a i Arnolda Robbinsa Programowanie skryptów powłoki (Helion 2005). Wiele użytecznych podręczników jest również publikowanych w internecie, na przykład dokument A Lab engineers check list for writing secure Unix code dostępny na stronie http://www.aus ´cert.org.au/render.html?it=1975. Kilka uniwersalnych instrukcji wykorzystywanych podczas pisania bezpiecznych skryptów powłoki zostało zamieszczonych poniżej. Zestawienie ich w jednym miejscu umożliwia szybkie odszukanie właściwego rozwiązania i skopiowanie go jako szablonu dla przyszłego skryptu. Każda przedstawiona tu technika programistyczna została szczegółowo opisana w jednej z receptur rozdziału. #!/usr/bin/env bash # plik receptury: security_template # Wyznaczenie bezpiecznej wartości zmiennej $PATH PATH='/usr/local/bin:/bin:/usr/bin' # Niemal na pewno zmienna jest już oznaczona jako eksportowana, ale dla pewności... \export PATH # Usunięcie wszystkich aliasów. Uwaga: początkowy znak \ zapobiega wykorzystaniu aliasu \unalias -a # Usunięcie wszystkich dowzorowań ścieżek dostępu do poleceń hash -r # Wyznaczenie "twardego" ograniczenia na 0 zapobiega zrzutom pamięci ulimit -H -c 0 -# Ustawienie bezpiecznej wartości IFS (składnia dla powłok bash i ksh93 - nieprzenośna!) IFS=$' \t\n' # Wyznaczenie bezpiecznej wartości zmiennej umask i wprowadzenie nowego ustawienia. # Działanie nie obejmuje plików powstałych w wyniku wcześniejszego przekierowania danych # Wartość 002 odpowiada prawom 0774, wartość 077 odpowiada prawom 0700 itd. UMASK=002 umask $UMASK until [ -n "$temp_dir" -a ! -d "$temp_dir" ]; do temp_dir="/tmp/opisowy_prefiks.${RANDOM}${RANDOM}${RANDOM}" done mkdir -p -m 0700 $temp_dir \ || (echo "BŁĄD: Nie można utworzyć katalogu tymczasowego '$temp_dir': $?"; exit 100) # W miarę możliwości wszystkie pliki tymczasowe powinny zostać usunięte. # Zmienna $temp_dir musi być zdefiniowana, a jej wartość nie może być modyfikowana! cleanup="rm -rf $temp_dir" trap "$cleanup" ABRT EXIT HUP INT QUIT

316

|

Rozdział 14. Bezpieczne skrypty powłoki

14.1. Unikanie częstych problemów związanych z bezpieczeństwem Problem Chcemy uniknąć typowych problemów związanych z bezpieczeństwem aplikacji skryptowych.

Rozwiązanie Należy pamiętać o weryfikowaniu wszystkich danych wejściowych, włącznie z informacjami wprowadzanymi w czasie interaktywnej pracy użytkownika z powłoką oraz plikami konfiguracyjnymi wykorzystywanymi w czasie pracy. Nigdy nie wolno wykonywać polecenia eval w odniesieniu do danych wejściowych, które nie zostały szczegółowo sprawdzone. Nie bez znaczenia jest również zagwarantowanie bezpieczeństwa plików tymczasowych, które powinny być przechowywane w bezpiecznych katalogach tymczasowych. Wykorzystując zewnętrzne pliki wykonywalne, trzeba się upewnić, że ich działanie nie stanowi zagrożenia dla systemu.

Analiza Zagadnienia opisane w tej recepturze są jedynie wstępem do rozważań nad problemem bezpieczeństwa systemu i aplikacji skryptowych. Odnoszą się tylko do najczęściej występujących błędów w systemie zabezpieczeń. Walidacja danych, a raczej jej brak, jest obecnie jednym z największych problemów związanych z bezpieczeństwem systemów informatycznych. Może prowadzić do przepełnienia bufora pamięci, co jest najczęściej stosowaną metodą wykorzystywania luk w oprogramowaniu. Skrypty bash nie są narażone na tego typu ataki w taki samym stopniu, jak aplikacje języka C, ale idea pozostaje taka sama. W przypadku powłoki bash znacznie bardziej prawdopodobne jest przekazanie za pomocą niewłaściwie zweryfikowanych danych instrukcji typu ; rm –rf / niż doprowadzenie do rzeczywistego przepełnienia bufora. Nie zmienia to faktu, że żadna z wymienionych sytuacji nie jest mile widziana. Dane muszą być sprawdzane! Kolejnym istotnym problemem są sytuacje wyścigu (ang. race condition), blisko związane z ryzykiem uzyskania przez osobę atakującą możliwości niekontrolowanego zapisywania plików. Sytuacja wyścigu występuje wówczas, gdy dwa zdarzenia (lub większa ich liczba) muszą zostać wygenerowane w odpowiedniej kolejności i w odpowiednim czasie, ale bez zewnętrznej interwencji. W takim przypadku często użytkownik uzyskuje dostęp z prawem odczytu i (lub) zapisu do plików, do których nie powinien mieć dostępu. W rezultacie mamy do czynienia z tzw. eskalacją uprawnień, czyli uzyskanie przez zwykłego użytkownika praw użytkownika root. Istotnym czynnikiem ułatwiającym przeprowadzenie tego typu ataku jest pozostawienie niezabezpieczonych plików tymczasowych. W celu wyeliminowania ryzyka ataku wystarczy stosować bezpieczne pliki tymczasowe, zapisywane w bezpiecznych katalogach tymczasowych.

14.1. Unikanie częstych problemów związanych z bezpieczeństwem

|

317

Inny często stosowany atak polega na wykorzystaniu narzędzi z kodem koni trojańskich. Narzędzia tego typu, podobnie jak koń trojański, wydają się czymś zupełnie innym, niż są w rzeczywistości. Klasycznym przykładem jest zmodyfikowane polecenie ls, które działa dokładnie tak samo jak standardowe polecenie ls, ale pod warunkiem, że nie korzysta z niego użytkownik root. Gdy zostaje uruchomione przez administratora, tworzy konto nowego użytkownika o nazwie r00t z domyślnym hasłem znanym jedynie osobie atakującej. Następnie program usuwa sam siebie. Najlepszym zabezpieczeniem przed tego typu atakami na etapie pisania skryptu jest wykorzystanie bezpiecznej wartości zmiennej $PATH. Z systemowego punktu widzenia do obrony przed opisanym atakiem można użyć wielu specjalizowanych narzędzi do weryfikowania spójności systemu, na przykład Tripwire lub AIDE.

Zobacz również • http://www.tripwiresecurity.com. • http://www.cs.tut.fi/~rammer/aide.html. • http://osiris.shmoo.com.

14.2. Unikanie spoofingu w pracy interpretera Problem Chcemy uniknąć ataków z wykorzystaniem praw setuid i konta użytkownika root.

Rozwiązanie W wywołaniu powłoki należy uwzględnić końcowy znak myślnika: #!/bin/bash –

Analiza Pierwszy wiersz skryptu ma szczególne znaczenie. Informuje on jądro systemu, którego interpretera należy użyć, aby właściwie przetworzyć pozostałą część pliku. Jądro uwzględnia również pojedynczą opcję dostarczaną do interpretera. Fakt ten jest wykorzystywany w niektórych rodzajach ataków. Jeżeli jednak wraz z plikiem wykonywalnym powłoki zostanie dostarczona odpowiednia opcja, ryzyko ataku nie występuje. Więcej informacji na ten temat znajduje się na stronie http://www.faqs.org/faqs/unix-faq/faq/part4/section-7.html. Zapisanie stałej wartość ścieżki dostępu do powłoki bash może skutkować pewnymi problemami związanymi z przenośnością skryptu. Zagadnienie to zostało szczegółowo omówione w recepturze 15.1, „Przenośność skryptu — problem wiersza #!”.

Zobacz również • Receptura 14.15, „Tworzenie skryptów z prawami setuid i setgid”. • Receptura 15.1, „Przenośność skryptu — problem wiersza #!”.

318

|

Rozdział 14. Bezpieczne skrypty powłoki

14.3. Wyznaczanie bezpiecznej wartości $PATH Problem Chcemy mieć pewność, że wykorzystywana wartość zmiennej $PATH nie stanowi zagrożenia dla systemu.

Rozwiązanie Na początku każdego skryptu należy zmiennej $PATH przypisać znaną poprawną wartość: # Wyznaczenie bezpiecznej wartości zmiennej $PATH PATH='/usr/local/bin:/bin:/usr/bin' # Niemal na pewno zmienna jest już oznaczona jako eksportowana, ale dla pewności... \export PATH

Inna metoda polega na wykorzystaniu narzędzia getconf, które wyznacza wartość zmiennej $PATH gwarantującą odnalezienie wszystkich standardowych narzędzi specyfikacji POSIX. export PATH=$(getconf PATH)

Analiza Przedstawione rozwiązanie ma dwie wady związane z przenośnością. Po pierwsze, składnia `` jest znacznie częściej stosowana (choć mniej czytelna) niż zapis $(). Po drugie, umieszczenie instrukcji export w tym samym wierszu, w którym jest zdefiniowana operacja przypisania wartości, nie działa we wszystkich systemach. Zamiast polecenia export zmienna="wartość" należałoby zastosować dwie instrukcje zmienna="wartość"; export zmienna. Będą one właściwie interpretowane w znacznie większej liczbie systemów operacyjnych. Poza tym, aby zmienna została oznaczona jako eksportowana do procesów potomnych, wystarczy wykonać polecenie export jeden raz. Jeśli z pewnych przyczyn użycie polecenia getconf jest wykluczone, warto wykorzystać przedstawioną wartość zmiennej $PATH jako wartość początkową, która zostanie później dostosowana do uwarunkowań określonego systemu. Zasadne może się również okazać użycie nieco mniej przenośnego zapisu: export PATH='/usr/local/bin:/bin:/usr/bin'

W zależności od dopuszczalnego poziomu ryzyka oraz wymagań aplikacji warto rozważyć użycie ścieżek bezwzględnych, choć trzeba wziąć pod uwagę nietypowość tego rozwiązania i potencjalne problemy z przenośnością, uwidaczniające się w systemach, które przechowują wykorzystywane w skrypcie programy w nietypowych katalogach. Jeden ze sposobów ograniczenia opisanych problemów polega na zastosowaniu zmiennych. W przypadku ich wykorzystania warto posortować zawierające je wiersze, aby nie doszło do sytuacji, w której dane polecenie występuje trzy razy tylko dlatego, że nie zostało zauważone podczas analizowania nieposortowanej listy. Najważniejszą zaletą tego rozwiązania jest to, że ustalenie, od jakich narzędzi zależy działanie skryptu, nie nastręcza większych trudności. Poza tym można dodać funkcję, która sprawdzi, czy poszczególne programy są dostępne, zanim zasadniczy kod skryptu rozpocznie pracę.

14.3. Wyznaczanie bezpiecznej wartości $PATH

|

319

#!/usr/bin/env bash # plik receptury: finding_tools # instrukcja export może być potrzebna lub nie, w zależności od zadania skryptu # Dość bezpieczne założenia _cp='/bin/cp' _mv='/bin/mv' _rm='/bin/rm' # Trochę mniej pewne case $(/bin/uname) in 'Linux') _cut='/bin/cut' _nice='/bin/nice' # [...] ;; 'SunOS') _cut='/usr/bin/cut' _nice='/usr/bin/nice' # [...] ;; # [...] esac

Dobierając nazwy zmiennych, trzeba zachować szczególną ostrożność. Niektóre programy (np. InfoZip) wykorzystują zmienne środowiskowe (np. $ZIP i $UNZIP) do przekazywania informacji potrzebnych w działaniu programu. Dlatego zdefiniowanie podstawienia typu ZIP='/usr/bin/zip' może się dla twórcy skryptu zakończyć długimi godzinami analizowania, dlaczego skrypt nie działa poprawnie, mimo że instrukcje wpisywane bezpośrednio w wierszu poleceń są właściwie realizowane. Autorzy książki boleśnie się o tym przekonali.

Zobacz również • Receptura 6.14, „Wielokrotne rozgałęzianie kodu”. • Receptura 6.15, „Przetwarzanie parametrów wiersza polecenia”. • Receptura 14.9, „Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfiko-

wanie zawartości”. • Receptura 15.2, „Ustawianie zmiennej $PATH zgodnie z zalecanymi POSIX”. • Receptura 16.3, „Trwała zmiana wartości $PATH”. • Receptura 16.4, „Chwilowa zmiana wartości $PATH”. • Receptura 19.3, „Zapominanie o braku bieżącego katalogu w zmiennej $PATH”. • Punkt „Polecenia wbudowane i słowa kluczowe” w dodatku A.

320

|

Rozdział 14. Bezpieczne skrypty powłoki

14.4. Usuwanie wszystkich aliasów Problem Ze względów bezpieczeństwa musimy się upewnić, że nie zostały zdefiniowane żadne niebezpieczne aliasy.

Rozwiązanie Aby usunąć wszystkie aliasy, trzeba wykorzystać polecenie \unalias –a.

Analiza Jeśli włamywaczowi uda się wymusić na użytkowniku root lub innym użytkowniku systemu uruchomienie podmienionego polecenia, może uzyskać dostęp do zasobów, do których nie powinien mieć dostępu. Jeden ze sposobów na oszukanie użytkowników i doprowadzenie do uruchomienia niebezpiecznego programu polega na utworzeniu aliasu do powszechnie wykorzystywanej instrukcji (np. ls). Początkowy znak \ zapobiega wykorzystaniu aliasu. Użycie go jest bardzo ważne, ponieważ w przeciwnym przypadku można wyrządzić wiele szkód w systemie, co demonstruje poniższy przykład: $ alias unalias=echo $ alias builtin=ls $ builtin unalias vi ls: unalias: Nie ma takiego pliku ani katalogu ls: vi: Nie ma takiego pliku ani katalogu $ unalias –a -a

Zobacz również • Receptura 10.7, „Zmiana definicji poleceń za pomocą aliasów”. • Receptura 10.8, „Pomijanie aliasów i funkcji”. • Receptura 16.6, „Skracanie i zmienianie nazw poleceń”.

14.5. Czyszczenie tablicy odwzorowań plików wykonywalnych Problem Chcemy mieć pewność, że tablica odwzorowań plików wykonywalnych nie została zmodyfikowana. 14.5. Czyszczenie tablicy odwzorowań plików wykonywalnych

|

321

Rozwiązanie Aby usunąć wszystkie wpisy w tablicy odwzorowań plików wykonywalnych, wystarczy wprowadzić instrukcję hash –r.

Analiza Aby przyspieszyć kolejne wywołania poleceń, powłoka bash zapamiętuje położenia plików wykonywalnych najczęściej realizowanych instrukcji. Jeśli włamywaczowi uda się wymusić na użytkowniku root lub innym użytkowniku systemu uruchomienie podmienionego polecenia, może uzyskać dostęp do zasobów, do których nie powinien mieć dostępu. Jeden ze sposobów na oszukanie użytkowników i doprowadzenie do uruchomienia niebezpiecznego programu polega na zatruciu tablicy odwzorowań plików wykonywalnych. W wyniku tej operacji może zostać uruchomiony niewłaściwy program.

Zobacz również • Receptura 14.9, „Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfi-

kowanie zawartości”. • Receptura 14.10, „Dodawanie bieżącego katalogu do listy $PATH”. • Receptura 15.2, „Ustawianie zmiennej $PATH zgodnie z zalecanymi POSIX”. • Receptura 16.3, „Trwała zmiana wartości $PATH”. • Receptura 16.4, „Chwilowa zmiana wartości $PATH”. • Receptura 19.3, „Zapominanie o braku bieżącego katalogu w zmiennej $PATH”.

14.6. Zapobieganie zrzutom pamięci Problem Chcemy uniemożliwić skryptowi dokonanie zrzutu pamięci w przypadku wystąpienia błędu krytycznego. W danych tych mogą bowiem być zawarte poufne informacje, na przykład hasła.

Rozwiązanie Należy użyć wbudowanego polecenia powłoki ulimit i ustawić rozmiar pliku zrzutu na 0. Zazwyczaj taką definicję umieszcza się w pliku .bashrc: ulimit –H –c 0 --

Analiza Zrzuty pamięci są wykorzystywane przez programistów w czasie uruchamiania programów (podczas debugowania) i zawierają obraz pamięci procesu z chwili wystąpienia błędu i przerwania pracy. Oznacza to, że plik zrzutu zawiera wszystkie dane, jakie w tym czasie proces przechowywał w pamięci (np. wprowadzone przez użytkownika hasła). 322

|

Rozdział 14. Bezpieczne skrypty powłoki

Aby uniemożliwić zwykłemu użytkownikowi zmianę opisywanego ustawienia, można zapisać instrukcję ulimit w plikach /etc/profile lub /etc/bashrc, w których zwykły użytkownik nie może wprowadzać zmian.

Zobacz również • Polecenie help ulimit.

14.7. Wyznaczenie bezpiecznej wartości $IFS Problem Chcemy mieć pewność, że wartość zmiennej środowiskowej $IFS, czyli zmienna wewnętrznego separatora pól, jest właściwie zdefiniowana.

Rozwiązanie Na początku każdego skryptu zmienna powinna otrzymać poprawną, znaną wartość. Służy do tego poniższe polecenie (niezgodne ze specyfikacją POSIX): # Ustawienie bezpiecznej wartości IFS (składnia dla powłok bash i ksh93 - nieprzenośna!) IFS=$' \t\n'

Analiza Zgodnie z dołączonym do kodu komentarzem przedstawiona składnia nie jest przenośna. Niestety standardowa (przenośna) składnia instrukcji nie gwarantuje ustawienia właściwych wartości — treść wiersza może zostać zniekształcona przez edytory, które usuwają krańcowe znaki odstępu. Wartościami separatora są zazwyczaj znaki spacji, tabulatora i nowego wiersza. Kolejność ich wymieniania jest istotna, gdyż pierwszy z wymienionych znaków jest wykorzystywany w odwołaniach do zmiennej $* (zwracającej wszystkie parametry pozycyjne) oraz w wyrażeniach ${!prefiks@} i ${!prefiks*} odpowiadających za podmianę wartości parametrów. W standardowej metodzie zapisu znak spacji i tabulatora jest umieszczany w pierwszym wierszu: 1 IFS=' 2 '

--> ¶¶

Usunięcie znaku nowego wiersza, spacji i tabulatora jest mało prawdopodobne, ale możliwa jest zmiana kolejności tych znaków, co często prowadzi do działania instrukcji niezgodnego z założeniami. 1 IFS='¶¶ 2 ● --> '

Zobacz również • Receptura 13.14, „Usuwanie krańcowych znaków odstępu”.

14.7. Wyznaczenie bezpiecznej wartości $IFS

|

323

14.8. Wyznaczanie bezpiecznej wartości umask Problem Chcemy mieć pewność, że wykorzystywana wartość umask nie stanowi zagrożenia dla systemu.

Rozwiązanie Dzięki wbudowanemu poleceniu powłoki umask można na początku każdego skryptu ustawić poprawną, znaną wartość maski: # Wyznaczenie bezpiecznej wartości zmiennej umask i wprowadzenie nowego ustawienia. # Działanie nie obejmuje plików powstałych w wyniku wcześniejszego przekierowania # danych # Wartość 002 odpowiada prawom 0774, wartość 077 odpowiada prawom 0700 itd. UMASK=002 umask $UMASK

Analiza Powołanie zmiennej $UMASK pozwala na wyznaczenie różnych wartości masek w różnych miejscach programu. Oczywiście, można by bez problemu wykonać to samo zadanie bez tworzenia zmiennej: umask 002

Należy pamiętać, że instrukcja umask definiuje bity, które odpowiadają prawom usuniętym z domyślnego ustawienia praw dostępu. Domyślne ustawienie wyznaczają wartości 777 w przypadku katalogów oraz 666 w przypadku plików. W razie jakichkolwiek wątpliwości można przetestować działanie instrukcji za pomocą poniższego kodu: # Uruchomienie nowej powłoki, aby zmiany nie wpłynęły na bieżące środowisko /tmp$ bash # Sprawdzenie bieżących ustawień /tmp$ touch um_bieżąca_maska # Test kilku nowych ustawień /tmp$ umask 000; touch um_000 /tmp$ umask 022; touch um_022 /tmp$ umask 077; touch um_077 $ ls -l um_* -rw-rw-rw- 1 -rw-r--r-- 1 -rw------- 1 -rw-rw-r-- 1

marek marek marek marek

marek marek marek marek

0 0 0 0

paź paź paź paź

19 19 19 19

10:17 10:18 10:18 10:17

um_000 um_022 um_077 um_bieżąca_maska

# Usunięcie plików i zakończenie pracy podpowłoki /tmp$ rm um_* /tmp$ exit

324 |

Rozdział 14. Bezpieczne skrypty powłoki

Zobacz również • Polecenie help umask. • http://linuxzoo.net/page/sec_umask.html.

14.9. Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfikowanie zawartości Problem Chcemy mieć pewność, że w zmiennej $PATH użytkownika root nie występują katalogi, w których mogą wprowadzać zmiany wszyscy użytkownicy systemu. Informacje o tym, dlaczego takie ustawienie jest niepożądane, zostały zamieszczone w recepturze 14.10, „Dodawanie bieżącego katalogu do listy $PATH”.

Rozwiązanie Przedstawiony poniżej skrypt zapewnia weryfikację katalogów zapisanych w zmiennej $PATH. W połączeniu z poleceniem su lub sudo pozwala na sprawdzenie listy katalogów innych użytkowników. #!/usr/bin/env bash # plik receptury: chkpath.1 # Wyszukanie katalogów zmiennej $PATH, które umożliwiają zapis wszystkim # użytkownikom lub które nie występują w systemie exit_code=0 for dir in ${PATH//:/ }; do [ -L "$dir" ] && printf "%b" "dowiązanie symb., " if [ ! -d "$dir" ]; then printf "%b" "nie występuje w systemie\t" (( exit_code++ )) elif [ "$(ls -lLd $dir | grep '^d.......w. ')" ]; then printf "%b" "ogólnodostępny\t\t\t" (( exit_code++ )) else printf "%b" "poprawny\t\t\t" fi printf "%b" "$dir\n" done exit $exit_code

Oto przykład uruchomienia skryptu: #./chkpath.1 poprawny poprawny poprawny poprawny poprawny poprawny poprawny poprawny poprawny

/opt/mono-1.1.13.2/bin /root/bin /usr/local/sbin /usr/local/bin /sbin /bin /usr/sbin /usr/bin /usr/X11R6/bin

14.9. Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfikowanie zawartości

|

325

nie występuje w systemie ogólnodostępny dowiązanie symb., poprawny

/brakujący /tmp

/tmp/bin

Analiza Wartość zmiennej $PATH została przekształcona w listę katalogów rozdzielanych znakami spacji, zgodnie z zaleceniami zawartymi w recepturze 9.11, „Wyszukiwanie plików z wykorzystaniem listy potencjalnych lokalizacji”. Następne działania polegają na sprawdzeniu, czy dany katalog jest dowiązaniem symbolicznym (-L) oraz czy w ogóle istnieje (-d). Kolejną czynnością jest pobranie rozbudowanego listingu parametrów danego katalogu (-l), wyświetlenie elementu docelowego dowiązania (-L) oraz ograniczenie wyniku do listy nazw katalogów, a nie ich zawartości (-d). Na końcu polecenie grep przechwytuje wszystkie katalogi, które umożliwiają zapisywanie danych wszystkim użytkownikom systemu. Skrypt nie działa zgodnie z typową uniksową regułą stanowiącą, że dopóki nie wystąpi błąd, na ekranie nie są wyświetlane żadne komunikaty. Przyjęty sposób działania pozwala użytkownikowi na dokładne ustalenie, jakie rzeczywiście katalogi zostały uwzględnione w zmiennej $PATH oraz umożliwia szczegółowe zweryfikowanie poszczególnych z nich. Zwracany kod wynikowy ma wartość zero, jeśli nie wystąpiły żadne błędy, oraz wartość liczby błędów w przypadku wystąpienia jakichkolwiek problemów. Po wprowadzaniu niewielkich korekt w kodzie skryptu można uwzględnić w listingu wynikowym również informacje o prawach dostępu, właścicielu i grupie katalogów. Dane te mogą się okazać jeszcze bardziej wartościowe niż informacje uwzględnione w poprzednim zestawieniu. #!/usr/bin/env bash # plik receptury: chkpath.2 # Wyszukanie katalogów zmiennej $PATH, które umożliwiają zapis wszystkim # użytkownikom lub które nie występują w systemie. # Skrypt uzupełniony o informacje o prawach dostępu, właścicielu i grupie. exit_code=0 for dir in ${PATH//:/ }; do [ -L "$dir" ] && printf "%b" "dowiązanie symb., " if [ ! -d "$dir" ]; then printf "%b" "nie występuje w systemie\t" (( exit_code++ )) else stat=$(ls -lHd $dir | awk '{print $1, $3, $4}') if [ "$(echo $stat | grep '^d. .....w. ')" ]; then printf "%b" "ogólnie dostępny\t\t\t$stat " (( exit_code++ )) else printf "%b" "poprawny\t\t\t$stat " fi fi printf "%b" "$dir\n" done exit $exit_code

Oto przykład uruchomienia skryptu: # ./chkpath.2 ; echo $? poprawny poprawny

326

|

drwxr-xr-x root root /opt/mono-1.1.13.2/bin /root/bin

Rozdział 14. Bezpieczne skrypty powłoki

poprawny poprawny poprawny poprawny poprawny poprawny poprawny nie występuje w systemie ogólnie dostępny dowiązanie symb., poprawny 2

drwxr-xr-x root root /usr/local/sbin drwxr-xr-x root root /usr/local/bin drwxr-xr-x root root /sbin drwxr-xr-x root root /bin drwxr-xr-x root root /usr/sbin drwxr-xr-x root root /usr/bin drwxr-xr-x root root /usr/X11R6/bin /brakujący drwxrwxrwt root root /tmp drwxr-xr-x root root /tmp/bin

Zobacz również • Receptura 9.11, „Wyszukiwanie plików z wykorzystaniem listy potencjalnych lokalizacji”. • Receptura 14.10, „Dodawanie bieżącego katalogu do listy $PATH”. • Receptura 15.2, „Ustawianie zmiennej $PATH zgodnie z zalecanymi POSIX”. • Receptura 16.3, „Trwała zmiana wartości $PATH”. • Receptura 16.4, „Chwilowa zmiana wartości $PATH”. • Receptura 19.3, „Zapominanie o braku bieżącego katalogu w zmiennej $PATH”.

14.10. Dodawanie bieżącego katalogu do listy $PATH Problem Konieczność uwzględniania znaku kropki i ukośnika podczas wpisywania instrukcji ./skrypt jest dość uciążliwa. Chcemy dodać znak kropki (lub pusty katalog, czyli znak : na początku lub końcu listy bądź dwuznak :: w ciągu listy) do zmiennej $PATH.

Rozwiązanie Stosowanie tego rozwiązania przez zwykłego użytkownika systemu nie jest zalecane, a w ogóle kategorycznie odradzane jest w przypadku konta użytkownika root. Jeżeli jednak opisywana zmiana musi zostać wprowadzona, znak kropki powinien zostać umieszczony na końcu listy. Niemniej nigdy nie należy wykonywać tej operacji z prawami użytkownika root.

Analiza Jak wiadomo, po wprowadzeniu nazwy polecenia bez poprzedzającej je ścieżki powłoka przeszukuje wszystkie katalogi zdefiniowane w zmiennej $PATH, aby ustalić położenie pliku polecenia. Powód, dla którego nie należy dodawać znaku kropki do listy katalogów, jest taki sam, jak w przypadku wyeliminowania z listy $PATH katalogów umożliwiających zapis plików wszystkim użytkownikom. Załóżmy, że pracujemy w katalogu /tmp, a znak kropki jest pierwszą wartością w ciągu zmiennej $PATH. Jeśli wpiszemy instrukcję ls, a w katalogu znajduje się plik o nazwie /tmp/ls, zamiast uruchomienia programu /bin/ls (odpowiadającego standardowemu poleceniu ls) wykonany

14.10. Dodawanie bieżącego katalogu do listy $PATH

|

327

zostanie plik /tmp/ls. Istnieje ryzyko (szczególnie, jeśli uwzględni się nazwę tego pliku), że plik /tmp/ls jest specjalnie przygotowanym skryptem, który uruchomiony z prawami użytkownika root może wykonać dowolne niebezpieczne zadania, a na końcu usunąć siebie w celu zatarcia śladów swojej działalności. Co się stanie, jeśli znak kropki będzie występował jako ostatni na liście? Zapewne wielu osobom nie raz zdarzyło się wpisać instrukcję mc zamiast mv. Jeżeli aplikacja Midnight Commander nie jest zainstalowana w systemie, można w ten sposób przypadkowo uruchomić program ./mc zamiast /bin/mv. Rezultat może być taki sam, jak opisany wcześniej. Ta technika nie powinna być wykorzystywana!

Rozwiązanie • Punkt 2.13 dokumentu http://www.faqs.org/faqs/unix-faq/faq/part2. • Receptura 9.11, „Wyszukiwanie plików z wykorzystaniem listy potencjalnych lokalizacji”. • Receptura 14.13, „Definiowanie praw dostępu”. • Receptura 14.9, „Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfi-

kowanie zawartości”. • Receptura 15.2, „Ustawianie zmiennej $PATH zgodnie z zalecanymi POSIX”. • Receptura 16.3, „Trwała zmiana wartości $PATH”. • Receptura 16.4, „Chwilowa zmiana wartości $PATH”. • Receptura 19.3, „Zapominanie o braku bieżącego katalogu w zmiennej $PATH”.

14.11. Bezpieczne pliki tymczasowe Problem Chcemy utworzyć plik tymczasowy, wiedząc, jakie konsekwencje ma dla systemu zabezpieczeń zastosowanie łatwej do przewidzenia nazwy.

Rozwiązanie Zazwyczaj łatwym i dostatecznie bezpiecznym rozwiązaniem jest wykorzystanie zmiennej $RAN ´DOM. Oto przykład: # Zmienna $TMP musi być zdefiniowana [ -n "$TMP" ] || TMP='/tmp' # Wykorzystanie wartości losowych do wyznaczenia nazwy katalogu tymczasowego until [ -n "$temp_dir" -a ! -d "$temp_dir" ]; do temp_dir="/tmp/opisowy_prefiks.${RANDOM}${RANDOM}${RANDOM}" done mkdir -p -m 0700 $temp_dir \ || { echo "BŁĄD: Nie można utworzyć katalogu tymczasowego '$temp_dir': $?"; exit 100; } # Wykorzystanie wartości losowych do wyznaczenia nazwy pliku tymczasowego until [ -n "$temp_file" -a ! -d "$temp_file" ]; do

328

|

Rozdział 14. Bezpieczne skrypty powłoki

temp_file="/tmp/opisowy_prefiks.${RANDOM}${RANDOM}${RANDOM}" done touch $temp_file && chmod 0600 $temp_file \ || { echo "BŁĄD: Nie można utworzyć pliku tymczasowego '$temp_file': $?"; exit 101; }

Jeszcze lepszym rozwiązaniem jest utworzenie tymczasowego pliku o losowej nazwie w tymczasowym katalogu o losowej nazwie: # plik receptury: make_temp # Wykorzystanie wartości losowych do wyznaczenia nazwy katalogu tymczasowego until [ -n "$temp_dir" -a ! -d "$temp_dir" ]; do temp_dir="/tmp/opisowy_prefiks.${RANDOM}${RANDOM}${RANDOM}" done mkdir -p -m 0700 $temp_dir \ || { echo "BŁĄD: Nie można utworzyć katalogu tymczasowego '$temp_dir': $?"; exit 100; } # Wykorzystanie wartości losowych do wyznaczenia nazwy pliku tymczasowego temp_file="$temp_dir/opisowy_prefiks.${RANDOM}${RANDOM}${RANDOM}" touch $temp_file && chmod 0600 $temp_file \ || { echo "BŁĄD: Nie można utworzyć pliku tymczasowego '$temp_file': $?"; exit 101; }

Korzystając z katalogów i plików tymczasowych, nie wolno zapominać o ustanowieniu pułapek odpowiedzialnych za „posprzątanie” po pracy skryptu. Zgodnie z zamieszczoną informacją zmienna $temp_dir musi być zdefiniowana przed zadeklarowaniem pułapki i nie może ulec zmianie. Jeżeli te warunki nie mogą być spełnione, kod poniższego skryptu musi zostać odpowiednio zmieniony. # plik receptury: clean_temp # W miarę możliwości wszystkie pliki tymczasowe powinny zostać usunięte. # Zmienna $temp_dir musi być zdefiniowana, a jej wartość nie może być modyfikowana! cleanup="rm -rf $temp_dir" trap "$cleanup" ABRT EXIT HUP INT QUIT

Analiza Zmienna $RANDOM jest dostępna w powłoce bash od wersji 2.0 oprogramowania, a jej zastosowanie wydaje się dostatecznie skutecznym rozwiązaniem. Nieskomplikowany kod jest zazwyczaj bardziej bezpieczny i efektywniejszy w działaniu niż kod bardzo rozbudowany. Dlatego zamiast opracowywać mechanizmy walidacji i kontroli błędów właściwej dla instrukcji mktemp i urządzenia /dev/urandom, korzystniejsze okazuje się użycie bezpiecznej zmiennej $RANDOM. Poza tym jest to rozwiązanie łatwiejsze do zastosowania. Zmienna $RANDOM dostarcza jednak tylko wartości liczbowe. Natomiast instrukcja mktemp uwzględnia zarówno wartości liczbowe, jak i małe oraz duże litery. Liczby i małe litery są także generowane przez urządzenie /dev/ ´random. Niezależnie od rodzaju wykorzystanego mechanizmu utworzenie tymczasowego katalogu ma wiele zalet: • Zastosowanie instrukcji mkdir –p –m 0700 $temp_dir zapobiega wystąpieniu sytuacji

wyścigu właściwej dla polecenia touch $temp_file && chmod 0600 $temp_file. • Pliki zapisane w katalogu tymczasowym nie są nawet widoczne dla osoby atakującej (pra-

cującej z prawami innymi niż prawa użytkownika root), pracującej poza tym katalogiem. Wynika to z definicji praw dostępu — 0700.

14.11. Bezpieczne pliki tymczasowe

|

329

• Użycie tymczasowego katalogu upraszcza procedurę usuwania plików tymczasowych po

zakończeniu pracy skryptu. Jeśli program operuje dużą liczbą plików tymczasowych zapisanych w różnych miejscach, zawsze istnieje ryzyko pominięcia któregoś z nich. • Pliki tymczasowe zapisywane w takim katalogu mogą mieć opisowe nazwy, co znacznie

ułatwia proces projektowania i debugowania aplikacji, a tym samym zwiększa jej bezpieczeństwo i wydajność. • Wykorzystanie opisowych prefiksów nazw nie pozostawia wątpliwości, które skrypty

w danej chwili pracują (fakt ten może być zarówno zaletą, jak i wadą rozwiązania, ale trzeba mieć świadomość, że te same informacje można uzyskać za pomocą instrukcji ps lub analizując zawartość katalogu /proc). Ponadto pozwala na zaobserwowanie przypadków nieusunięcia plików tymczasowych, co niekiedy prowadzi do wycieku danych.

Zaprezentowany kod promuje stosowanie opisowych prefiksów w nazwach katalogów i plików tymczasowych. Część osób z pewnością uzna to za ograniczenie bezpieczeństwa systemu z uwagi na przewidywalność nazw. Trudno nie zgodzić się z tezą, że część ścieżki jest łatwa do przewidzenia, ale inne zalety tego rozwiązania przemawiają za wykorzystaniem go mimo formułowanych wątpliwości. Osoby, które mimo wszystko mają pewne obiekcje, mogą po prostu pominąć opisowy prefiks. W zależności od założonego poziomu bezpieczeństwa oraz wymagań aplikacji zasadne może się okazać zapisanie tymczasowych plików o losowych nazwach w tymczasowym katalogu o losowej nazwie — tak, jak to zostało przedstawione w przykładzie. Rozwiązanie to nie zapewnia istotnego zwiększenia bezpieczeństwa systemu, ale nic nie stoi na przeszkodzie, aby również je uwzględnić. Zgodnie z zamieszczonymi wcześniej informacjami wykorzystanie instrukcji touch $temp_file && chmod 600 $temp_file niesie ryzyko wystąpienia sytuacji wyścigu. Jeden ze sposobów wyeliminowania tego ryzyka polega na zastosowaniu poniższego rozwiązania: saved_umask=$(umask) umask 077 touch $temp_file umask $saved_umask unset seved_umask

Wykorzystanie jednocześnie tymczasowego katalogu o losowej (lub pseudolosowej) nazwie i tymczasowego pliku o losowej nazwie wydaje się najkorzystniejszą techniką operowania plikami tymczasowymi. Jeśli numeryczny charakter zmiennej $RANDOM rzeczywiście stanowi przeszkodę w jej wykorzystywaniu, warto rozważyć połączenie różnych instrukcji generujących wspólnie pseudolosowe i trudne do przewidzenia wartości, poddawane dodatkowo działaniu funkcji skrótu. dlugi_losowy_ciag=$( (last; who; netstat -a; free; date; \ echo $RANDOM) | md5sum | cut -d' ' -f1)

Wykorzystanie ostatniej prezentowanej metody nie jest najlepszym rozwiązaniem, ponieważ jego dodatkowa złożoność może sprawić, że w praktyce będzie ono jeszcze mniej bezpieczne niż technika bazująca na użyciu zmiennej $RANDOM. Niemniej czasami warto zastanowić się nad tym, w jaki sposób można sobie skomplikować pracę.

Teoretycznie najbezpieczniejszym rozwiązaniem jest zastosowanie narzędzia mktemp (dostępnego w wielu nowoczesnych systemach operacyjnych) z odwołaniem do urządzenia /dev/urandom 330

|

Rozdział 14. Bezpieczne skrypty powłoki

(również powszechnie stosowanego w nowoczesnych systemach) lub zmiennej $RANDOM. Wada tej metody polega na tym, że polecenie mktemp i urządzenie /dev/radnom nie są dostępne na wszystkich platformach. W praktyce oznacza to ograniczenie przenośności kodu, co znacznie bardziej komplikuje aplikację niż przedstawione rozwiązanie. #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Próba otworzenia pliku lub katalogu o bezpiecznej nazwie # Wywołanie: $plik_tymczasowy=$(MakeTemp [/ścieżka/do/pliku-prefiks] # Przykłady: # $ kat_tymczasowy=$(MakeTemp katalog /tmp/$PROGRAM.xxx) # $ plik_tymczasowy=$(MakeTemp plik /tmp/$PROGRAM.xxx) # function MakeTemp { # Zmienna $TMP musi być zdefiniowana [ -n "$TMP" ] || TMP='/tmp' local local local local

type_name=$1 prefix=${2:-$TMP/temp} temp_type='' sanity_check=''

# Brak prefiksu - wykorzystnie ciągu $TMP + temp

case $type_name in plik ) temp_type='' ur_cmd='touch' # Zwykły plik z prawami odczytu i zapisu, należący do użytkownika skryptu sanity_check='test -f $TEMP_NAME -a -r $TEMP_NAME -a -w $TEMP_NAME -a -O $TEMP_NAME' ;; katalog ) temp_type='-d' ur_cmd='mkdir -p -m0700' # Katalog z prawami odczytu, zapisu i przeglądania, należący do użytkownika skryptu sanity_check='test -d $TEMP_NAME -a -r $TEMP_NAME -a -w $TEMP_NAME -a -x $TEMP_NAME -a -O $TEMP_NAME' ;; * ) printf "\nBłędna definicja typu w $PROGRAM:MakeTemp! Poprawna wartość: plik|katalog." 1 ;; esac # Próba wykorzystania instrukcji mktemp TEMP_NAME=$(mktemp $temp_type ${prefix}.XXXXXXXXX) # Próba wykorzystania urządzenia urandom (jeśli wcześniejsza zakończyła się błędem) # Niepowodzenie w tym przypadku oznacza niewykonanie zadania if [ -z "$TEMP_NAME" ]; then TEMP_NAME="${prefix}.$(cat /dev/urandom | od -x | tr -d ' ' | head -1)" $ur_cmd $TEMP_NAME fi # Sprawdzenie, czy plik bądź katalog został rzeczywiście utworzony if ! eval $sanity_check; then printf "\aBŁĄD KRYTYCZNY: Nie można utworzyć tymczasowego pliku/katalogu. Funkcja: '$0:MakeTemp $*'\n" 2 else echo "$TEMP_NAME" fi } # koniec funkcji MakeTemp

14.11. Bezpieczne pliki tymczasowe

|

331

Zobacz również • Polecenie man mktemp. • Receptura 14.13, „Definiowanie praw dostępu”. • Skrypt ./scripts.noah/mktemp.bash w dodatku B.

14.12. Walidacja wprowadzanych danych Problem Opracowywany skrypt będzie wymagał dostarczania danych (na przykład wprowadzania ich przez użytkownika lub przekazywania z innego programu). Chcąc zapewnić bezpieczeństwo aplikacji i spójność danych, musimy zaimplementować mechanizmy, które sprawdzą, czy dostarczone informacje są zgodne z wymaganiami skryptu.

Rozwiązanie W zależności od rodzaju dostarczanych danych oraz rygorystyczności mechanizmu weryfikacji wyróżnia się kilka metod walidacji danych. Do zwykłego porównania wartości z ustalonym wzorcem wystarczą rozwiązania opisane w recepturze 6.6, „Sprawdzenie równości dwóch wartości”, w recepturze 6.7, „Sprawdzanie zgodności wartości ze wzorcem”, i w recepturze 6.8, „Sprawdzanie wartości z użyciem wyrażeń regularnych”. [[ "$dane_wejsciowe" == *.jpg ]] && echo "Mam plik JPEG"

Jeśli wprowadzona informacja może odpowiadać kilku wartościom, wystarczy zastosować instrukcję case w sposób opisany w recepturze 6.14, „Wielokrotne rozgałęzianie kodu”, lub recepturze 6.15, „Przetwarzanie parametrów wiersza polecenia”. #!/usr/bin/env bash # plik receptury: validate_using_case case $1 in *.firma.pl ;; *.jpg ;; *.[jJ][pP][gG] ;; coś | innego ;; [0-9][0-9][0-9] ;; [a-z][a-z][a-z][a-z] ;; * ;; esac

) printf "Prawdopodobnie nazwa jednostki \n" ) printf "Prawdopodobnie plik JPEG \n" ) printf "Plik JPEG z rozszerzeniem o różnej wielkości liter \n" ) printf "wprowadzony ciąg 'coś' lub 'innego' \n" ) printf "Liczba trzycyfrowa \n" ) printf "Słowo złożone z czterech małych liter \n" ) printf "Żadna z powyższych wartości \n"

Jeżeli standardowy mechanizm porównywania ze wzorcem nie jest dostatecznie selektywny, a skrypt jest przeznaczony dla interpreterów bash w wersji 3.0 lub późniejszej, można zastosować 332

|

Rozdział 14. Bezpieczne skrypty powłoki

wyrażenia regularne, opisane w recepturze 6.8, „Sprawdzanie wartości z użyciem wyrażeń regularnych”. Instrukcja zaprezentowana w kolejnym przykładzie odpowiada za wyszukanie nazwy pliku, składającej się z trzech do sześciu znaków alfanumerycznych oraz rozszerzenia .jpg (wielkość liter jest istotna). [[ "$dane_wejsciowe" =~ [[:alpha:]]{3,6}\.jpg ]] && echo "Mam plik JPEG"

Analiza Bardziej rozbudowany przykład szczegółowej weryfikacji danych wejściowych został zapisany w pliku examples/scripts/shprompt dołączonym do pakietu bash. Jego autorem jest Chet Ramey, menedżer projektu bash: # # # # # # # # # # # # #

shprompt -- wyświetla znak zgłoszenia i zwraca odpowiedź spełniającą pewne kryteria shprompt [-dDfFsy] zgłoszenie s = monit o wprowadzenie f = monit o wprowadzenie F = monit o wprowadzenie d = monit o wprowadzenie D = monit o wprowadzenie y = monit o wprowadzenie

ciągu tekstowego nazwy pliku pełnej ścieżki dostępu do pliku lub katalogu nazwy katalogu pełnej ścieżki dostępu do katalogu odpowiedzi typu y lub n

Chet Ramey [email protected]

Podobny przykład — examples/scripts.noah/y_or_n_p.bash — został napisany w 1993 roku przez Noaha Friedmana i dostosowany do wersji 2. powłoki przez Cheta Rameya. Na uwagę zasługują również następujące pliki przykładów: ./functions/isnum.bash, ./functions/isnum2 oraz ./functions/ ´isvalidip.

Zobacz również • Receptura 3.5, „Pobieranie danych od użytkownika”. • Receptura 3.6, „Wprowadzanie odpowiedzi typu tak-nie”. • Receptura 3.7, „Wybór opcji z listy”. • Receptura 3.8, „Wprowadzanie haseł”. • Receptura 6.6, „Sprawdzenie równości dwóch wartości”. • Receptura 6.7, „Sprawdzanie zgodności wartości ze wzorcem”. • Receptura 6.8, „Sprawdzanie wartości z użyciem wyrażeń regularnych”. • Receptura 6.14, „Wielokrotne rozgałęzianie kodu”. • Receptura 6.15, „Przetwarzanie parametrów wiersza polecenia”. • Receptura 11.2, „Dostarczanie domyślnej wartości daty”. • Receptura 13.6, „Interpretacja tekstu z wykorzystaniem instrukcji read”. • Receptura 13.7, „Zapisywanie danych w tablicy za pomocą instrukcji read”. • Przykłady skryptów powłoki bash zostały zestawione w dodatku B.

14.12. Walidacja wprowadzanych danych

|

333

14.13. Definiowanie praw dostępu Problem Chcemy wyznaczyć takie prawa dostępu do plików, które zagwarantują bezpieczeństwo systemu.

Rozwiązanie Aby narzucić określone ustawienie praw dostępu (ze względów związanych z bezpieczeństwem lub z powodu nieznajomości dotychczasowych ustawień), należy zastosować polecenie chmod z czterema cyframi ósemkowymi odpowiadającymi odpowiednim wartościom tych praw. $ chmod 0755 wybrany_skrypt

Chcąc jedynie dodać lub odjąć określone prawa, ale bez modyfikowania pozostałych dotychczasowych ustawień, wystarczy wykorzystać operatory + i -. $ chmod +x wybrany skrypt

Ewentualna próba rekurencyjnego ustawienia praw dostępu do wszystkich plików określonej struktury katalogów z zastosowaniem polecenia chmod –R 0644 katalog kończy się zazwyczaj niezgodnie z oczekiwaniami, ponieważ skutkuje odjęciem prawa wykonywania ze wszystkich podkatalogów. To z kolei oznacza, że nie będzie można korzystać z plików tych podkatalogów, przejść do tych podkatalogów ani przeglądać ich zawartości. Do zmiany uprawnień poszczególnych plików i katalogów należy zastosować polecenia find, xargs i chmod. $ find katalog –type f | xargs chmod 0644 $ find katalog –type d | xargs chmod 0755

#Prawa dostępu do plików #Prawa dostępu do katalogów

Oczywiście, jeśli zadanie dotyczy jedynie zmian praw dostępu do wszystkich plików określonego katalogu (bez działania rekurencyjnego), wystarczy przejść do wybranego katalogu i wyznaczyć nowe prawa. Podczas tworzenia nowego katalogu warto stosować polecenie mkdir –m prawa nowy_katalog. Poza wykonaniem dwóch zadań w jednej instrukcji zapobiega ono sytuacji wyścigu między utworzeniem katalogu a nadaniem mu określonych praw dostępu.

Analiza Wiele osób zwyczajowo definiuje ósemkowe prawa dostępu za pomocą trzech cyfr. Trzeba jednak pamiętać, że zastosowanie zapisu czterocyfrowego jednoznacznie wyznacza wszystkie atrybuty. Zapis cyfrowy ma również tę przewagę, że zawsze wiadomo, jakie ostateczne prawa dostępu są za jego pomocą ustawiane. Oczywiście, można również posługiwać się operatorem (=), definiując prawa w sposób symboliczny. Niemniej większość tradycjonalistów korzysta z ósemkowych wartości liczbowych. Posługując się operatorami trybu symbolicznego (+ i -), trudniej jest zagwarantować określone prawa wynikowe, gdyż zmiana uprawnień ma charakter względny. Niestety, w wielu przypadkach nie można po prostu zastąpić dotychczasowych praw dostępu wartościami ósemkowymi. Użytkownik nie ma wówczas innego wyjścia, jak tylko wykorzystać operatory trybu symbolicz-

334 |

Rozdział 14. Bezpieczne skrypty powłoki

nego (zazwyczaj operator +), które nie wpływają na wartość innych praw dostępu. Szczegółowe informacje na temat sposobu działania określonego mechanizmu chmod są zawarte w podręczniku systemowym. Korzystając z trybu symbolicznego, zawsze należy upewnić się, czy wyznaczone prawa dostępu są zgodne z założeniami. $ ls -l razem 0 -rw-r--r--

1 root root 0 paź 19 16:21 skrypt.sh

# Ósemkowa wersja praw, gwarantująca właścicielowi prawo odczytu, zapisu i uruchomienia $ chmod 700 skrypt.sh $ ls -l razem 0 -rwx------

1 root root 0 paź 19 16:21 skrypt.sh

# Ustawienie praw odczytu i wykonania dla wszystkich użytkowników systemu $ chmod ugo+rx *.sh $ ls -l razem 0 -rwxr-xr-x

1 root root 0 paź 19 16:21 skrypt.sh

W ostatnim przykładzie prawa rx zostały dodane (+) wszystkim użytkownikom systemu (ugo), co nie zmienia faktu, że właściciel pliku zachował prawo w. Takie było założenie zadania i w ten sposób często wyznaczane są prawa dostępu w praktyce. Jednocześnie jednak przykład ten uwidacznia, jak łatwo można przez pomyłkę pozostawić nieodpowiednie prawo dostępu pliku. Dlatego właśnie wiele osób posługuje się zapisem ósemkowym. Bardzo ważne jest tu oczywiście również sprawdzenie wyniku wykonanej instrukcji. Zawsze przed wyznaczeniem praw dostępu dla większej liczby plików trzeba wnikliwie przeanalizować efekty wykonania polecenia. Niekiedy zasadne bywa również wcześniejsze sporządzenie kopii zapasowej praw dostępu oraz informacji na temat przynależności plików. Zagadnienie to jest tematem receptury 17.8, „Rejestracja metadanych plików w celu ich późniejszego odtworzenia”.

Zobacz również • Polecenie man chmod. • Polecenie man find. • Polecenie man xargs. • Receptura 17.8, „Rejestracja metadanych plików w celu ich późniejszego odtworzenia”.

14.14. Ujawnienie haseł na liście procesów Problem Hasła wprowadzane w wierszu polecenia bywają ujawniane po wpisaniu instrukcji ps. Oto przykład: $ ./kiepska_apl -u użytkownik -p hasło & [1] 9308

14.14. Ujawnienie haseł na liście procesów

|

335

$ ps a PID TTY 8550 pts/0 9376 pts/1 9438 pts/1

STAT Ss S R+

TIME 0:00 0:00 0:00

COMMAND -bash /bin/bash ./kiepska_apl -u użytkownik -p hasło ps a

Rozwiązanie Należy dołożyć wszelkich starań, aby hasła nie były przekazywane za pomocą wiersza poleceń.

Analiza Nigdy nie wolno przekazywać haseł w wierszu polecenia. Wiele aplikacji, które umożliwiają dołączanie opcji –p lub innych o podobnym znaczeniu ma jednocześnie wbudowany mechanizm monitowania o podanie hasła, jeśli nie zostanie ono zdefiniowane w wierszu polecenia. Choć rozwiązanie to sprawdza się w przypadku interaktywnej pracy z aplikacją, nie jest możliwe do wykorzystania w skryptach. Dlatego wiele osób przygotowuje krótkie skrypty „opakowujące” lub aliasy, które umieszczają ciąg hasła w wywołaniu programu. Niestety, metoda ta nie zapewnia bezpieczeństwa hasła, ponieważ program ostatecznie zostaje uruchomiony, a tym samym zarejestrowany na liście procesów. Jeżeli dana aplikacja umożliwia przekazanie hasła za pośrednictwem standardowego strumienia wyjściowego, można wykorzystać ten sposób. To oczywiście rodzi kolejne problemy, ale przynajmniej zapobiega wyświetleniu ciągu hasła na liście procesów. $ kiepska_apl ~.ukryty/hasło_kiepskiej_aplikacji

Jeśli to rozwiązanie okaże się nieskuteczne, trzeba poszukać nowej aplikacji, zainstalować stosowną nakładkę na bieżącą aplikację lub zgodzić się na ryzyko przechwycenia hasła.

Zobacz również • Receptura 3.8, „Wprowadzanie haseł”. • Receptura 14.20, „Wykorzystanie haseł w skryptach”.

14.15. Tworzenie skryptów z prawami setuid i setgid Problem Mamy problem, który można rozwiązać, dodając do skryptu powłoki bit setgid lub setuid.

Rozwiązanie Użytkownicy skryptu powinni otrzymać najmniejszy zestaw uprawnień, który gwarantuje im poprawne wykonanie zadania. Należy do tego celu wykorzystać mechanizm definiowania grup, prawa dostępu do pliku oraz polecenie sudo.

336

|

Rozdział 14. Bezpieczne skrypty powłoki

Stosowanie bitów setuid i setgid w odniesieniu do skryptów powłoki czyni więcej szkody niż pożytku. Niektóre systemy (np. Linux) ignorują bity setuid w przypadku skryptów. Zatem ustawianie bitów setuid w plikach skryptów powoduje poza obniżeniem poziomu bezpieczeństwa niepotrzebne problemy z przenośnością.

Analiza Skrypty użytkownika root z ustawionym bitem setuid są szczególnie niebezpieczne. Lepiej więc w ogóle zapomnieć o możliwości stosowania tego rozwiązania i użyć mechanizmu sudo. Znaczenie bitów setuid i setgid w odniesieniu do katalogów jest nieco inne niż w przypadku plików wykonywalnych. Jeżeli jeden z wymienionych bitów jest przypisany katalogowi, wszystkie nowo tworzone pliki lub podkatalogi tego katalogu są automatycznie przypisywane do tego samego właściciela lub grupy, do których należy katalog nadrzędny. Aby sprawdzić, czy dany plik ma ustawiony bit setuid, wystarczy wykonać polecenie test –u. Sprawdzenie ustawienia bitu setgid sprowadza się do wykonania instrukcji test –g. $ mkdir kat_suid kat_sgid $ touch plik_suid plik_sgid $ ls –l drwxr-xr-x drwxr-xr-x -rw-r--r--rw-r--r--

2 2 1 1

root root root root

root root root root

4096 4096 0 0

paź paź paź paź

19 19 19 19

18:19 18:19 18:20 18:20

kat_sgid kat_suid plik_sgid plik_suid

$ chmod 4755 kat_suid plik_suid $ chmod 2755 kat_sgid plik_sgid $ ls -l drwxr-sr-x drwsr-xr-x -rwxr-sr-x -rwsr-xr-x

2 2 1 1

root root root root

root 4096 paź 19 18:19 root 4096 paź 19 18:19 root 0 paź 19 18:20 root 0 paź 19 18:20

kat_sgid kat_suid plik_sgid plik_suid

$ [ -u kat_suid ] && echo 'Jest bit setuid' || echo 'Nie ma bitu setuid' Jest bit setuid $ [ -u kat_sgid ] && echo 'Jest bit setuid' || echo 'Nie ma bitu setuid' Nie ma bitu setuid $ [ -g plik_sgid ] && echo 'Jest bit setgid' || echo 'Nie ma bitu setgid' Jest bit setgid $ [ -g plik_suid ] && echo 'Jest bit setgid' || echo 'Nie ma bitu setgid' Nie ma bitu setgid

Zobacz również • Polecenie man chmod. • Receptura 14.18, „Wykonywanie skryptu z prawami zwykłego użytkownika”. • Receptura 14.19, „Bezpieczne wykorzystanie mechanizmu sudo”.

14.15. Tworzenie skryptów z prawami setuid i setgid

|

337

• Receptura 14.20, „Wykorzystanie haseł w skryptach”. • Receptura 17.15, „Wykorzystanie mechanizmu sudo w odniesieniu do grupy poleceń”.

14.16. Ograniczenie praw konta gościa Zawarte w niniejszej recepturze informacje na temat ograniczenia praw konta gościa są również publikowane w książce Camerona Newhama bash. Wprowadzenie (Helion 2006).

Problem System musi zostać udostępniony gościom (niezarejestrowanym użytkownikom), którym trzeba wyznaczyć określone prawa posługiwania się zasobami systemu.

Rozwiązanie O ile jest to możliwe, należy unikać tworzenia współdzielonych kont. W przeciwnym przypadku administrator traci kontrolę nad poczynaniami poszczególnych użytkowników i godzi się na logistyczny chaos, gdy jeden z użytkowników przestaje korzystać z systemu i trzeba zmienić hasło (oraz powiadomić o zmianie wszystkich pozostałych użytkowników). Właściwe rozwiązanie polega na utworzeniu odpowiedniej liczby niezależnych kont z minimalnym zakresem uprawnień, gwarantującym wykonanie powierzonego zadania. Warto tu zastanowić się nad wykorzystaniem następujących technik: • ograniczenie przestrzeni systemowej za pomocą mechanizmu chroot, opisane w recepturze

14.17, „Wykorzystanie środowiska chroot”; • użycie usługi SSH do nieinteraktywnego dostępu do poleceń i zasobów systemowych, zgod-

nie z zasadami opisanymi w recepturze 14.21, „Wykorzystanie usługi SSH bez hasła”; • wykorzystanie powłoki bash o ograniczonej liczbie poleceń.

Analiza Powłoka o ograniczonej liczbie poleceń jest środowiskiem, w którym prawo użytkownika do przemieszczania się w strukturze katalogów i zapisywania plików zostało znacznie ograniczone. Jest ono zazwyczaj przypisywane kontom gości. Aby przypisać użytkownikowi tego typu powłokę, wystarczy w pliku /etc/passwd, w rekordzie danego użytkownika, wpisać nazwę rbash zamiast bash (jeśli opcja obsługi powłoki rbash została włączona podczas kompilacji kodu źródłowego). Ograniczenia związane z tą powłoką uniemożliwiają użytkownikowi wykonywanie następujących zadań: • Zmiana katalogu bieżącego — polecenie cd nie działa. Próba jego użycia kończy się wyświe-

tleniem komunikatu rbash: cd: restricted. • Przekierowanie danych wyjściowych do pliku. Wykorzystanie operatorów >, >|, oraz >>

jest niedozwolone.

338 |

Rozdział 14. Bezpieczne skrypty powłoki

• Przypisanie nowych wartości zmiennym środowiskowym $ENV, $BASH_ENV, $SHELL oraz

$PATH. • Wprowadzanie poleceń z uwzględnieniem znaku ukośnika (/). Powłoka uznaje pliki spoza

bieżącego katalogu za „nieznalezione” (not found). • Wykorzystanie wbudowanego polecenia exec. • Definiowanie jako parametru polecenia . (source) nazwy, która zawiera znak ukośnika (/). • Importowanie funkcji podczas uruchamiania środowiska powłoki. • Dodawanie lub usuwanie poleceń wbudowanych z wykorzystaniem opcji –f i –d wbudo-

wanej instrukcji enable. • Wykorzystanie opcji –p we wbudowanym poleceniu command. • Wyłączenie trybu ograniczonego działania przez użycie instrukcji set +r.

Wymienione ograniczenia są nakładane na środowisko użytkownika po uwzględnieniu ustawień zawartych w pliku .bash_profile i innych plikach środowiskowych. Z tego względu rozsądna wydaje się zmiana właściciela plików .bash_profile i .bashrc na użytkownika root oraz pozostawienie jedynie praw odczytu. Podobnie katalog domowy użytkownika-gościa powinien być przeznaczony jedynie do odczytu. Całe środowisko pracy dla użytkownika-gościa jest więc definiowane w plikach /etc/profile i .bash_profile. Ponieważ użytkownik nie ma dostępu do pliku /etc/profile i nie może nadpisać pliku .bash_profile, to na administratorze spoczywa obowiązek skonfigurowania powłoki w taki sposób, aby była ona w pełni funkcjonalna. Dwie najczęściej stosowane metody przygotowania tego typu środowisk polegają na utworzeniu katalogu bezpiecznych poleceń i umieszczeniu ścieżki do katalogu jako jedynej wartości zmiennej $PATH bądź na opracowaniu menu poleceń, z którego użytkownik nie może wyjść bez zakończenia pracy powłoki. Powłoka o ograniczonej liczbie poleceń nie jest zabezpieczeniem przed zdeterminowanym włamywaczem. Jej „uszczelnienie” może się okazać zadaniem trudniejszym, niżby się mogło początkowo wydawać. Wynika to z faktu, że wiele aplikacji, takich jak Vi i Emacs, udostępnia funkcje powłoki, które mogą całkowicie pomijać nałożone ograniczenia. Rozsądne wykorzystanie opisanego mechanizmu jest wartościowym dodatkiem do istniejącego systemu zabezpieczeń, ale nie może stanowić jedynego zabezpieczenia systemu.

Również w oryginalnej powłoce Bourne’a dostępna jest wersja o ograniczonym zestawie poleceń. Interpreter ten nazywa się rsh i bywa mylony z tak zwanymi r-narzędziami (rsh, rcp, rlogin itd.), czyli programami zdalnej powłoki. Stanowiąca niezwykle duże zagrożenie dla systemu bezpieczeństwa zdalna powłoka rsh została zastąpiona przez aplikację SSH (bezpieczną powłokę).

Zobacz również • Receptura 14.17, „Wykorzystanie środowiska chroot”. • Receptura 14.21, „Wykorzystanie usługi SSH bez hasła”.

14.16. Ograniczenie praw konta gościa

|

339

14.17. Wykorzystanie środowiska chroot Problem Chcemy uruchomić skrypt lub aplikację, do których nie mamy zaufania.

Rozwiązanie Warto rozważyć uruchomienie ich w tzw. klatce chroot. Polecenie chroot zmienia główny katalog bieżącego procesu, na katalog wskazany przez użytkownika. Uzyskuje się w ten sposób efekt umieszczenia procesu w klatce, z której teoretycznie nie ma powrotu do katalogu nadrzędnego. W przypadku złamania zabezpieczeń aplikacji lub wymuszenia na niej niebezpiecznych operacji, zagrożona pozostaje jedynie niewielka część systemu plików. Jeśli dodatkowo proces działa z prawami użytkownika o bardzo ograniczonych uprawnieniach, rozwiązanie to może istotnie zwiększyć bezpieczeństwo systemu. Niestety szczegółowe omówienie mechanizmu chroot wykracza poza ramy tematyczne książki — samo doskonale nadaje się na odrębną publikację. Wzmianka o nim została tutaj zamieszczona tylko po to, aby każdy, kto poszukuje sposobu na podniesienie bezpieczeństwa systemu, wiedział o istnieniu tego typu rozwiązania.

Analiza Dlaczego zatem nie mielibyśmy uruchamiać wszystkich programów w środowisku chroot? Dlatego, że wiele aplikacji musi współdziałać z innymi programami, plikami, katalogami i gniazdami (ang. socket) rozproszonymi w całym systemie. Największą wadą mechanizmu chroot jest brak możliwości współpracy aplikacji z komponentami zainstalowanymi poza wydzielonym obszarem dyskowym. Wszystkie elementy potrzebne do jej prawidłowego funkcjonowania muszą się znajdować w obrębie klatki chroot. Im bardziej złożony jest program, tym trudniej uruchomić go w środowisku chroot. W klatce chroot uruchamia się aplikacje, które muszą być nieustannie dostępne w internecie, takie jak serwery DNS (np. BIND), serwery WWW, czy serwer poczty (np. POSTFIX). Złożoność konfiguracji jest w takich przypadkach bardzo różna. Szczegółowe informacje na ten temat są zamieszczane w dokumentacji każdego z pakietów oprogramowania. Środowisko chroot doskonale sprawdza się również w procesie odbudowy systemu. Po uruchomieniu systemu z płyty typu Live CD i zamontowaniu głównego systemu plików często zachodzi konieczność wykorzystania narzędzi takich jak Grub lub Lilo. W zależności od danej konfiguracji niekiedy niezbędne jest upewnienie się, że są one rzeczywiście uruchamiane w zniszczonym systemie. Jeśli dystrybucja Live CD i zainstalowany system nie różnią się zbyt istotnie, zazwyczaj można utworzyć środowisko chroot w punkcie montowania zniszczonego systemu i naprawić ten system. Rozwiązanie to działa poprawnie, ponieważ wszystkie narzędzia, biblioteki, pliki konfiguracyjne i pliki urządzeń są zamknięte w klatce chroot i stanowią kompletny (całkiem nieźle) funkcjonujący system. Pracując w środowisku chroot, niekiedy trzeba poeksperymentować trochę z ustawieniami zmiennej $PATH (zależnie od tego, „jak bardzo system Live CD jest zbliżony do zainstalowanego”).

340 |

Rozdział 14. Bezpieczne skrypty powłoki

Warto przy tej okazji wspomnieć również o systemie NSA Security Enhanced Linux (SELinux) i implementowanym wraz z nim mechanizmie obowiązkowej kontroli dostępu (MAC — Mandatory Access Control). Funkcje MAC umożliwiają definiowanie wielopoziomowych uprawnień do operowania poszczególnymi komponentami systemu. Definicja odpowiedniego zakresu uprawnień jest nazywana polityką bezpieczeństwa i pozwala na uzyskiwanie rezultatów podobnych do uruchomienia klatki chroot. Każda aplikacja i proces mogą wykonywać jedynie te zadania, które nie wykraczają poza ograniczenia narzucane przez politykę bezpieczeństwa. Mechanizm SELinux został włączony przez firmę Red Hat Linux do oferowanych przez nią produktów klasy Enterprise. Na platformie Novel SUSE funkcjonuje podobne rozwiązanie MAC o nazwie AppArmor. Analogiczne implementacje są dostępne w systemach Solaris, BSD i OS X.

Zobacz również • Polecenie man chroot. • http://www.nsa.gov/selinux. • http://en.wikipedia.org/wiki/Mandatory_access_control. • http://olivier.sessink.nl/jailkit. • http://www.jmcresearch.com/projects/jail.

14.18. Wykonywanie skryptu z prawami zwykłego użytkownika Problem Chcemy uruchomić skrypt z prawami użytkownika innego niż root, ale istnieje obawa, że nie będzie on mógł zrealizować wszystkich zadań.

Rozwiązanie Skrypty zawsze powinny być uruchamiane z identyfikatorami użytkowników innych niż root (na przykład z identyfikatorem użytkownika lub specjalnie przygotowanego konta). Podobnie, korzystając z powłoki, nie należy logować się jako root. W realizacji wszystkich zadań, które wymagają zwiększonego zakresu uprawnień, pomocny jest mechanizm sudo.

Analiza Polecenie sudo można wykorzystywać zarówno w kodzie skryptu, jak i podczas interaktywnej pracy z powłoką. Specjalnie w tym celu została udostępniona opcja NOPASSWD. Więcej informacji na ten temat znajduje się w recepturze 14.19, „Bezpieczne wykorzystanie mechanizmu sudo”.

14.18. Wykonywanie skryptu z prawami zwykłego użytkownika

|

341

Zobacz również • Polecenie man sudo. • Polecenie man sudoers. • Receptura 14.15, „Tworzenie skryptów z prawami setuid i setgid”. • Receptura 14.19, „Bezpieczne wykorzystanie mechanizmu sudo”. • Receptura 14.20, „Wykorzystanie haseł w skryptach”. • Receptura 17.15, „Wykorzystanie mechanizmu sudo w odniesieniu do grupy poleceń”.

14.19. Bezpieczne wykorzystanie mechanizmu sudo Problem Chcemy użyć mechanizmu sudo, ale w sposób, który nie zapewni zbyt wielu przywilejów poszczególnym użytkownikom systemu.

Rozwiązanie Bardzo dobrze! Troska o bezpieczeństwo systemu jest jednym z zadań administratora. Choć uruchomienie mechanizmu sudo gwarantuje znacznie większe bezpieczeństwo pracy niż brak tego rozwiązania, to domyślne ustawienia środowiska sudo można znacznie udoskonalić. Czas poświęcony na zapoznanie się z dokumentacją aplikacji sudo oraz składnią pliku /etc/sudoers nigdy nie jest czasem straconym. Można się na przykład dowiedzieć, że nigdy nie należy wykorzystywać definicji ALL=(ALL)ALL! Tak, użycie jej rozwiązuje wiele problemów, ale nie ma żadnego związku z jakimkolwiek zabezpieczeniem systemu. Użytkownicy mogą wykonywać wszystkie czynności zarezerwowane dla administratora. Poszczególne polecenia są co prawda rejestrowane w dziennikach, ale ominięcie rejestracji jest trywialnie proste — wystarczy wykonać instrukcję sudo bash. Zakres stosowania mechanizmu sudo powinien być dokładnie przemyślany. Tak jak nie powinno się używać dyrektyw ALL=(ALL)ALL, niewłaściwe wydaje się również przypisywanie uprawnień kolejnym pojedynczym użytkownikom. Narzędzie sudoers umożliwia wielopoziomowe zarządzanie prawami użytkowników. Warto więc skorzystać z jego funkcji. W podręczniku systemowym (man sudoers) zostało zamieszonych wiele ciekawych informacji oraz przykładów związanych z tym zagadnieniem. Na szczególną uwagę zasługuje tutaj punkt poświęcony sposobom zabezpieczenia mechanizmu przed ucieczką z powłoki. W pliku sudoers obsługiwane są cztery rodzaje aliasów: user, runas, host oraz command. Rozważne wykorzystanie ich w charakterze ról lub grup pozwala na znaczne ograniczenie chaosu konfiguracyjnego. Na przykład za pomocą dyrektywy User_Alias można zdefiniować alias dla programistów, za pomocą dyrektywy Host_Alias można wyznaczyć komputery, przy których ci użytkownicy będą mogli pracować, a za pomocą dyrektywy Cmnd_Alias określić dozwolone polecenia. Jeśli zgodnie z przyjętą polityką plik /etc/sudoers będzie modyfikowany tylko w jednym komputerze i okresowo kopiowany za pomocą polecenia scp (z wykorzystaniem uwierzy-

342 |

Rozdział 14. Bezpieczne skrypty powłoki

telniania bazującego na kluczu publicznym) do pozostałych jednostek, można przygotować bardzo bezpieczne, a jednocześnie użyteczne środowisko pracy z minimalnym zakresem uprawnień. Gdy program sudo wyświetla monit o wprowadzenie hasła, oczekuje wprowadzenia hasła danego użytkownika, a nie hasła użytkownika root. Z jakichś przyczyn wiele osób często myli te wartości.

Analiza Niestety, mechanizm sudo nie jest domyślnie instalowany w każdym systemie. Zazwyczaj jest dostępny w systemach Linux i BSD, ale nie na innych platformach. Jeżeli nie jest automatycznie dołączony, warto zapoznać się z dokumentacją systemową, a następnie zainstalować stosowny pakiet oprogramowania. Do edycji pliku /etc/sudoers zawsze należy używać narzędzia visudo. Podobnie jak edytor vipw, program visudo blokuje dostęp do pliku, dzięki czemu tylko jedna osoba może w danej chwili wprowadzać zmiany. Poza tym przed przekazaniem zapisanego pliku do użycia dokonuje sprawdzenia składni, uniemożliwiając przypadkowe zablokowanie sobie dostępu do systemu.

Zobacz również • Polecenie man sudo. • Polecenie man sudoers. • Polecenie man visudo. • Książka Daniela J. Barretta SSH. The Secure Shell. The Definitive Guide (O’Reilly 2005). • Receptura 14.15, „Tworzenie skryptów z prawami setuid i setgid”. • Receptura 14.18, „Wykonywanie skryptu z prawami zwykłego użytkownika”. • Receptura 14.20, „Wykorzystanie haseł w skryptach”. • Receptura 17.15, „Wykorzystanie mechanizmu sudo w odniesieniu do grupy poleceń”.

14.20. Wykorzystanie haseł w skryptach Problem W skrypcie musi być na stałe zapisane hasło.

Rozwiązanie Oczywiście, nie jest to właściwe rozwiązanie i należy go unikać, jeśli to tylko możliwe. Niestety czasami nie ma możliwości zrealizowania zadania w inny sposób.

14.20. Wykorzystanie haseł w skryptach

| 343

Pierwsza czynność polega na sprawdzeniu, czy problemu nie udałoby się rozwiązać, stosując mechanizm sudo z opcją NOPASSWD. W ten sposób można wyeliminować konieczność stałego definiowania hasła w jakimkolwiek pliku. Naturalnie metoda ta również wnosi pewne ryzyko, ale warto ją wziąć po uwagę. Więcej informacji na ten temat znajduje się w recepturze 14.19, „Bezpieczne wykorzystanie mechanizmu sudo”. Kolejną akceptowalną alternatywą bywa zastosowanie usługi SSH z kluczami publicznymi i precyzyjnie dobranym zestawem dozwolonych poleceń. Zagadnienie to jest tematem receptury 14.21, „Wykorzystanie usługi SSH bez hasła”. Jeżeli nie ma innej możliwości wykonania danego zadania, najlepszym rozwiązaniem jest umieszczenie identyfikatora użytkownika i jego hasła w oddzielnym pliku, który będzie mógł zostać odczytany jedynie przez użytkownika realizującego zadanie. Plik taki może być włączany do skryptu zasadniczego za pomocą polecenia source (.) — szczegółowo mechanizm włączania plików został opisany w recepturze 10.3, „Wykorzystanie plików konfiguracyjnych w skrypcie”.

Analiza Dostęp do danych zdalnego komputera jest relatywnie bezpieczny dzięki dostępności usługi SSH (więcej informacji na ten temat znajduje się w recepturach 14.21, „Wykorzystanie usługi SSH bez hasła”, oraz 15.11, „Pobieranie danych z innego komputera”). Teoretycznie możliwe jest również wykorzystanie usługi SSH do przetwarzania danych własnej jednostki, ale w praktyce posłużenie się poleceniem sudo okazuje się znacznie efektywniejsze. Żadna z tych metod nie rozwiązuje jednak takich problemów, jak konieczność pobrania informacji ze zdalnej bazy danych z użyciem instrukcji SQL. Oczywiście, wiele osób stwierdzi teraz, że dostępne jest przecież polecenie crypt i inne funkcje skrótu dla haseł. Rzecz w tym, że bezpieczne przechowywanie haseł bazuje na założeniu, że przechowujemy jedynie tak zwany jednokierunkowy skrót. Można go utworzyć, znając hasło, ale na jego podstawie nie można odtworzyć hasła, a dostępność niezaszyfrowanego hasła jest kluczem do rozwiązania problemu. Jest ono potrzebne do ustanowienia połączenia z bazą danych. Bezpieczne przechowanie hasła nie jest więc możliwe. Pozostają jedynie rozwiązania nie w pełni bezpieczne, które niekiedy okazują się jeszcze gorsze niż zapisanie hasła otwartym tekstem — dają bowiem złudne poczucie bezpieczeństwa. Jednak mając tę świadomość, można wykorzystać pewne mechanizmy ukrywania haseł, takie jak ROT13 lub jemu podobne. Ponieważ w metodzie ROT13 przetwarzane są jedynie litery zestawu znaków ASCII, warto przetestować również działanie algorytmu ROT47 obejmującego niektóre znaki interpunkcyjne. $ ROT13=$(echo haslo | tr 'A-Za-z' 'N-ZA-Mn-za-m') $ ROT47=$(echo haslo | tr '!-~' 'P-~!-O')

Należy pamiętać, że stosowanie algorytmów ROT13 i ROT47 nie jest niczym innym, jak zwykłym „zaciemnianiem” hasła i nie gwarantuje jego bezpiecznego przechowywania. Rozwiązanie to można stosować wtedy i tylko wtedy, gdy zarówno programista, jak i jego przełożeni mają świadomość, że zabezpieczenie jest iluzoryczne i pamiętają o ryzyku z nim związanym.

344 |

Rozdział 14. Bezpieczne skrypty powłoki

Zobacz również • http://pl.wikipedia.org/wiki/ROT13. • Receptura 10.3, „Wykorzystanie plików konfiguracyjnych w skrypcie”. • Receptura 14.15, „Tworzenie skryptów z prawami setuid i setgid”. • Receptura 14.18, „Wykonywanie skryptu z prawami zwykłego użytkownika”. • Receptura 14.19, „Bezpieczne wykorzystanie mechanizmu sudo”. • Receptura 14.21, „Wykorzystanie usługi SSH bez hasła”. • Receptura 15.11, „Pobieranie danych z innego komputera”. • Receptura 17.15, „Wykorzystanie mechanizmu sudo w odniesieniu do grupy poleceń”.

14.21. Wykorzystanie usługi SSH bez hasła Problem Zawarte w skrypcie odwołanie do usługi SSH lub programu scp musi być zrealizowane bez użycia hasła. Analogiczny problem występuje w przypadku umieszczenia takiego odwołania w definicji zadania cron1. Protokół SSH1 oraz związane z nim pliki wykonywalne są uznawane za przedawnione i gwarantują niższy poziom bezpieczeństwa niż nowy protokół SSH2, implementowany przez zespoły OpenSSH i SSH Communication Security. Z uwagi na istotną przewagę algorytmu SSH2 i oprogramowania OpenSSH rozwiązania bazujące na protokole SSH1 nie będą w książce opisywane.

Rozwiązanie Istnieją dwa sposoby wykorzystania usługi SSH bez konieczności wprowadzania haseł — dobry i zły. Błędna metoda polega na użyciu klucza publicznego, który nie jest zaszyfrowany frazą zabezpieczającą (tzw. passphrase). Właściwe rozwiązanie bazuje natomiast na wykorzystaniu zabezpieczonego klucza publicznego oraz mechanizmów ssh-agent i keychain. Wszystkie omówienia zawarte w dalszej części receptury odnoszą się do poleceń i plików pakietu OpenSSH. Jeśli w danym systemie jest wykorzystywane inne oprogramowanie SSH, przed wykonaniem opisywanych zadań konieczne będzie zapoznanie się z jego dokumentacją. Pierwsza czynność polega na utworzeniu pary kluczy (jeśli nie zostały one utworzone wcześniej). Jedna para kluczy wystarcza do uwierzytelnienia użytkownika w dowolnej liczbie systemów. Niemniej ze względów osobistych lub zawodowych wiele osób wykorzystuje więcej niż jeden zestaw kluczy. Wspomniana para składa się z klucza prywatnego, który powinien być chroniony za wszelką cenę, oraz klucza publicznego (*.pub), którego wartość można publikować nawet na 1

Pragniemy podziękować Richardowi Silvermanowi i Danielowi Barrettowi za inspirację i wyśmienitą lekturę książek SSH. The Secure Shell. The Definitive Guide (a szczególnie rozdziałów 2., 6. i 11.) oraz Linux. Bezpieczeństwo. Receptury, bez których ten podrozdział byłby jedynie namiastką prawdziwej receptury.

14.21. Wykorzystanie usługi SSH bez hasła

| 345

stronie internetowej. Złożony algorytm matematyczny zapewnia taką zależność między kluczem publicznym i prywatnym, że obydwa wzajemnie się identyfikują, ale na podstawie jednego nie można wyznaczyć wartości drugiego. Aby wygenerować parę kluczy, należy użyć programu ssh-keygen (lub ssh-keygen2 w systemach niewykorzystujących oprogramowania OpenSSH). Opcja –t jest obowiązkowa i wymaga zdefiniowania parametru o wartości rsa lub dsa. Opcja –b nie jest obowiązkowa, ale umożliwia określenie liczby bitów nowego klucza (w czasie pisania książki domyślnie generowane klucze miały długość 1024 bitów). Opcja –C pozwala na zdefiniowanie komentarza. W przypadku jej pominięcia wykorzystywany jest ciąg nazwa_użytkownika@nazwa_komputera. Zalecane jest dołączenie przynajmniej opcji –t dsa i –b 2048. Nie należy natomiast pomijać definiowania hasła zabezpieczającego (passphrase). Polecenie ssh-keygen umożliwia również późniejszą zmianę frazy zabezpieczającej oraz komentarza. $ ssh-keygen You must specify a key type (-t). Usage: ssh-keygen [options] Options: -b bits Number of bits in the key to create. -c Change comment in private and public key files. -e Convert OpenSSH to IETF SECSH key file. -f filename Filename of the key file. -g Use generic DNS resource record format. -i Convert IETF SECSH to OpenSSH key file. -l Show fingerprint of key file. -p Change passphrase of private key file. -q Quiet. -y Read private key file and print public key. -t type Specify type of key to create. -B Show bubblebabble digest of key file. -C comment Provide new comment. -N phrase Provide new passphrase. -P phrase Provide old passphrase. -r hostname Print DNS resource record. -G file Generate candidates for DH-GEX moduli -T file Screen candidates for DH-GEX moduli $ ssh-keygen -t dsa -b 2048 -C 'To jest nowy klucz' Generating public/private dsa key pair. Enter file in which to save the key (/home/jp/.ssh/id_dsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/jp/.ssh/id_dsa. Your public key has been saved in /home/jp/.ssh/id_dsa.pub. The key fingerprint is: 75:6c:be:d6:7a:62:9d:1e:40:7e:b4:1c:1c:73:40:d8 To jest nowy klucz $ ls -l id_dsa* -rw------- 1 jp jp 1264 paź 21 10:31 id_dsa -rw-r--r-- 1 jp jp 1120 paź 21 10:31 id_dsa.pub $ cat id_dsa.pub ssh-dss AAAAB3NzaC1kc3MAAAEBAObr2PG+yjMvrGWKeLThQwebF6R5u5KqWj3/1EqUVeyXPih4Nw61utT9S0neg6Qdnh IF33an3uqPN4RYjQ8VDqWcjvnfJb0aMO+IOvlZzm+2aKfix1LI9m2/oxo2j4XzxZ9Ze0jjf4x1hm9CRHERbCUg fq8lDkUn91F58mWGDr6o3XkoYdWNWqmRE/QoX0e9nRyBUhJYUCmBCeVUcelwlLREN+bpbOt7e0RUl8SM8Rz3uK 9toMvbFMjPkjoLnqQqOTZWnT5A39YleOcfrI0ghz7dgDbhdCejXItrEH4GLZQXAvdJ5SPHusKCmL9VUnPacbaJ c8L5XG87/Mk8ptNHmf0AAAAVAOoZzIqLY8yq5a2Mjg0xXy6mt0CBAAABAAy12QQH8epojDh3+RdrXluyaGfhlP M8ZCrsqZzA/lJDP5xq0Q3LQUEUBeHyuiZghpl9gf+vsq/5zzH+OcmZh7WbHqf4VnNea9fgNrENKrBAB1TUm4Y+ azZ43h6q5rd9a1YzAu/FTKsd2BSI57+b6SqKqBdQif1IzkkvnO+zGpr4CMar2ieLCmH4Tv69Pfm4jidQ12J+jZ pp68A7Bd/0Pk0G6VtpvKCgW8MTPvW/0j160At/15wG0p8aQUvPDSCR28yyBEeBU2HDSZCeJNywYOYVmO/VBlRV

346 |

Rozdział 14. Bezpieczne skrypty powłoki

ApDawufOnt4kGAj0fF41x5eN6qBcY9xASJYEzgR5zuVWXNVP+pFSYf8AAAEACRaQPzdPCrE8GttQRz1az3TbVn Lcv/o6zbeD5K8vnW+Jqfk6mFMlaPqdZhHhrp6kZaGWAdISbxDqyIWgl8jKMf976g2KG3FL92i/z269Rj+7SyXJ GUiqoB5mhYPkwGXiE60Syo6erpfgZ/N9q+A7HpZjhgHRxug+vv0FR6Qv3zwhmXe9UgKlzJyOvHdlyNTP89oxFg 8map3cPWewwLKMLNoeDPgJKEzDHPxdTca+8cH3ReBCbWK2WSvYvtUj6EJo3YzM48MSLi6XbBGz42gozWN7moIP ouxWI9/aOKbV7XFMDogRqZMnwVNV0eno0a0VkSwNL6KI2OTmxWekD3JGzA== To jest nowy klucz

Po tej operacji klucz publiczny powinien zostać dodany do pliku ~/.ssh/authorized_keys każdego katalogu domowego w każdym zdalnym systemie, do którego będziemy się logować. Do skopiowania treści klucza można wykorzystać polecenie scp. Można również przenieść plik za pomocą dyskietki lub pendrive’a i polecenia cp bądź zastosować technikę kopiuj-wklej w oknie terminala. Choć można całą procedurę sprowadzić do wykonania pojedynczej instrukcji (np. scp id_dsa.pub zdalna_jednostka:.ssh/id_dsa_pub), nie należy tego robić, jeśli nie mamy pewności, że w zdalnym systemie nie został wcześniej zapisany plik authorized.keys. Jeśli plik już istnieje, konieczne będzie wykorzystanie nieco bardziej skomplikowanego, ale znacznie bezpieczniejszego polecenia (pierwszego wyróżnionego): $ ssh zdalna_jednostka "echo $(cat ~/.ssh/id_dsa.pub) >> ~/.ssh/authorized_keys" jp@zdalna_jednostka's password: $ ssh zdalna_jednostka Last login: Sun Oct 21 13:16:36 2007 from test.domain NetBSD 2.0.2 (GENERIC) #0: Wed Mar 23 08:53:42 UTC 2005 Welcome to NetBSD! -bash-3.00$ exit logout Connection to zdalna_jednostka closed.

Monit o wprowadzenie hasła został wyświetlony jedynie po uruchomieniu początkowego polecenia scp. Wykonanie instrukcji ssh nie wymagało powtórzenia tej czynności. Jest to efekt działania niewidocznego w tym przypadku agenta SSH (ssh-agent), który rejestruje frazy zabezpieczające, zwalniając użytkownika z obowiązku wprowadzania ich. Aby powyższe polecenie zostało wykonane poprawnie, w obydwu systemach musi istnieć katalog ~/.ssh. Jeśli katalog nie istnieje, trzeba go utworzyć za pomocą polecenia mkdir –m 700 –p ~/.ssh. Prawa dostępu do katalogu ~/.ssh muszą mieć wartość ósemkową 0700, gdyż w przeciwnym przypadku oprogramowanie OpenSSH nie zrealizuje poprawnie swojego zadania. Rozsądne wydaje się również ustawienie praw 0600 dla pliku ~/.ssh/authorized_keys. Zdefiniowana w ten sposób zależność ma charakter jednokierunkowy. Możemy ustanowić połączenie SSH ze zdalną jednostką bez podawania hasła. Jednak z uwagi na brak klucza prywatnego i agenta SSH w zdalnej jednostce nie ma ona możliwości ustanowienia połączenia z systemem lokalnym. Nic nie stoi na przeszkodzie, aby plik klucza prywatnego został skopiowany do wszystkich wykorzystywanych jednostek sieciowych (co doprowadziłoby do utworzenia „bezhasłowej” sieci SSH), ale wówczas znacznie skomplikowałaby się procedura zmiany frazy zabezpieczającej. Trudniejsze byłoby również zagwarantowanie bezpiecznego przechowywania klucza prywatnego. Jeśli jest to możliwe, należy korzystać z jednego dobrze zabezpieczonego systemu i w razie konieczności ustanawiać połączenia SSH z innymi komputerami. Agent SSH to bardzo „sprytne” i wyrafinowane narzędzie. Niekiedy okazuje się nawet zbyt sprytne. Zostało zaprojektowane do wykorzystania za pośrednictwem instrukcji eval i we współpracy z mechanizmem podmiany poleceń: eval `ssh-agent`. Jego działanie polega na utworzeniu dwóch zmiennych środowiskowych, które umożliwiają programom ssh i scp odszukanie agenta i pobranie informacji na temat tożsamości użytkownika. Mechanizm ten został precyzyjnie opisany w wielu dokumentach. Problemem jest jednak to, że nie może być wykorzystywany 14.21. Wykorzystanie usługi SSH bez hasła

|

347

tak, jak większość klasycznych programów (pewne jego rozwiązania są charakterystyczne także dla programu less, opisanego w recepturze 8.15, „Dodatkowe funkcje polecenia less”), przez co jest całkowicie niezrozumiały dla początkujących i niedoinformowanych użytkowników. Uruchomienie agenta powoduje wyświetlenie kilku informacji, które sprawiają wrażenie, że program działa poprawnie. W istocie jest uruchamiany, ale nie wykonuje żadnego zadania, ponieważ potrzebne zmienne środowiskowe nie zostały wcześniej zdefiniowane. Przy okazji warto wspomnieć, że opcja –k powoduje zakończenie pracy agenta. # Błędny sposób wykorzytania agenta SSH # Brak zmiennych środowiskowych $ set | grep SSH $ $ ssh-agent SSH_AUTH_SOCK=/tmp/ssh-yQOmD27841/agent.27841; export SSH_AUTH_SOCK; SSH_AGENT_PID=27842; export SSH_AGENT_PID; echo Agent pid 27842; # Nadal brak zmiennych środowiskowych $ set | grep SSH $ # Nie można nawet przerwać procesu, ponieważ potrzebna jest wartość $SSH_AGENT_PID $ ssh-agent -k SSH_AGENT_PID not set, cannot kill agent # Czy ten proces w ogóle działa? Tak. $ ps x PID TTY STAT TIME COMMAND 21418 tty0 Ss 0:00 -bash 27842 ? Ss 0:00 ssh-agent 27890 tty0 R+ 0:00 ps x $ kill 27842 $ ps x PID TTY 21418 tty0 27945 tty0

STAT Ss R+

TIME COMMAND 0:00 -bash 0:00 ps x

# Poprawny sposób uruchomienia agenta $ eval `ssh-agent` Agent pid 27973 # Udało się! $ set | grep SSH SSH_AGENT_PID=27973 SSH_AUTH_SOCK=/tmp/ssh-NzPwr27972/agent.27972 # Przerwanie pracy agenta – błędna metoda $ ssh-agent -k unset SSH_AUTH_SOCK; unset SSH_AGENT_PID; echo Agent pid 27973 killed; # Proces został przerwany, ale jego dane pozostały $ set | grep SSH SSH_AGENT_PID=27973 SSH_AUTH_SOCK=/tmp/ssh-NzPwr27972/agent.27972 # Porpawny sposób uruchomienia agenta $ eval `ssh-agent` Agent pid 28034

348 |

Rozdział 14. Bezpieczne skrypty powłoki

$ set | grep SSH SSH_AGENT_PID=28034 SSH_AUTH_SOCK=/tmp/ssh-hMuHU28033/agent.28033 $ eval `ssh-agent -k` Agent pid 28034 killed $ set | grep SSH $

Intuicyjnie proste? Nie. Sprytne? Efektywne? Wyrafinowane? Tak. Przyjazne dla użytkownika? Nie do końca. Po poprawnym uruchomieniu agenta trzeba załadować informacje na temat tożsamości za pomocą polecenia ssh-add. Operacja nie jest skomplikowana. Wystarczy uruchomić program z opcjonalną listą plików kluczy. Konieczne jest w tym przypadku wprowadzenie każdej zdefiniowanej frazy zabezpieczającej. W prezentowanym przykładzie nie został określony żaden plik kluczy, więc został wykorzystany plik domyślny, ustawiony w głównym pliku konfiguracyjnych usługi SSH: $ ssh-add Enter passphrase for key '/home/jp/.ssh/id_dsa': Identity added: /home/jp/.ssh/id_dsa (/home/jp/.ssh/id_dsa)

Od tego momentu można bez przeszkód korzystać z interaktywnych połączeń SSH (ze skonfigurowanymi wcześniej systemami) w ramach danej sesji powłoki bez konieczności podawania hasła lub frazy zabezpieczającej. Ale co z innymi sesjami, skryptami i zadaniami cron? Można wykorzystać przygotowany przez Arnolda Robbinsa skrypt keychain (http://www.gen ´too.org/proj/en/keychain/), który: [działa] jako nakładka na program ssh-agent zapewniająca uruchomienie jednego procesu agenta dla każdego systemu, a nie dla każdej sesji logowania. Gwarantuje znaczne zmniejszenie liczby przypadków, w których użytkownik musi wprowadzać frazę zabezpieczającą — zamiast podawania jej w każdej sesji logowania wystarczy wprowadzić frazę raz po uruchomieniu systemu. […] Mechanizm keychain zapewnia również bezpieczne wykorzystanie kluczy RSA/DSA w zadaniach cron bez konieczności użycia niezabezpieczonych kluczy prywatnych.

Program keychain jest efektywnym, właściwie napisanym i opisanym komentarzami skryptem powłoki, który odpowiada za automatyczne zarządzanie żmudnym procesem eksportowania omówionych zmiennych środowiskowych do innych sesji. Udostępnia te zmienne również innym skryptom oraz systemowi cron. Oczywiście, można dyskutować, czy pozostawienie wszystkich kluczy we władaniu takiego skryptu, aż do chwili ponownego uruchomienia komputera, jest zasadne. W praktyce okazuje się, że nie jest to takie złe rozwiązanie. Po pierwsze, działanie skryptu zawsze można przerwać, co uniemożliwia wykorzystanie go również w kodzie innych skryptów lub zadań cron. Po drugie, opcja --clean pozwala na usunięcie wszystkich kluczy z bufora podczas logowania. Czy zatem wracamy do punktu wyjścia? W rzeczywistości opcja ta bywa użyteczna. Oto przykład jej zastosowania wymyślony przez autora skryptu keychain (opublikowany w portalu developerWorks firmy IBM, pod adresem http://www.ibm.com/developerworks/linux/library/l-keyc2/):

14.21. Wykorzystanie usługi SSH bez hasła

| 349

Udowodniłem, że stosowanie niezabezpieczonych kluczy prywatnych jest bardzo ryzykowne, ponieważ umożliwia innym osobom przechwycenie klucza danego użytkownika, a następnie wykorzystanie go do zalogowania na jego zdalne konta z dowolnego systemu bez konieczności podawania hasła. Mimo że program keychain nie jest podatny na tego typu nadużycia (jeśli klucze prywatne pozostają zaszyfrowane), istnieje pewne zagrożenie związane z faktem, że skrypt key ´chain znacznie ułatwia „przyłączenie się” do działającego przez długi czas agenta SSH. Zastanawiałem się nad tym, co by się mogło stać, gdyby włamywacz w jakiś sposób ustalił moje hasło lub frazę zabezpieczającą i zalogował się do lokalnego systemu. Gdyby udało mu się wykorzystać moje konto, skrypt zapewniłby mu dostęp do zaszyfrowanych kluczy prywatnych, a tym samym wszystkich do moich kont. Zatem gdyby włamywacz w jakiś sobie znany sposób zalogował się w systemie jako ja, skrypt key ´chain zagwarantowałby mu możliwość wykorzystania moich kont w zdalnych systemach. Niemniej nawet w takich przypadkach trudno by mu było zdobyć faktyczną treść moich kluczy prywatnych, ponieważ pozostają one zapisane na dysku w formie zaszyfrowanej. Poza tym, uzyskanie dostępu do kluczy prywatnych jest możliwe tylko wtedy, gdy włamywacz rzeczywiście zaloguje się jako ja, a nie gdy zdoła odczytać pliki w moim katalogu. Złamanie zabezpieczeń programu keychain jest więc znacznie trudniejszym zadaniem niż uzyskanie dostępu do klucza prywatnego. Niemniej gdyby włamywaczowi udało się zalogować na moje konto systemowe, mógłby wyrządzić sporo szkód, posługując się moim rozszyfrowanym kluczem prywatnym. Jeśli więc skrypt keychain jest wykorzystywany na serwerze, do którego administrator nie loguje się zbyt często lub w jego systemie nie pracuje żaden mechanizm monitorujący próby włamań, warto rozważyć użycie opcji --clear, która zapewnia dodatkową warstwę zabezpieczeń. Opcja --clear informuje skrypt keychain o obowiązku traktowania każdego przypadku logowania na dane konto jako próby włamania, o ile użytkownik nie udowodni, że jest inaczej. Uruchomienie skryptu z opcją --clear powoduje, że podczas każdego logowania z pamięci podręcznej agenta SSH będą usuwane wszystkie klucze prywatne użytkownika, zanim jeszcze program przejdzie do trybu normalnej pracy. Gdy włamywacz zaloguje się do systemu, zobaczy monit o podanie frazy zabezpieczającej i nie uzyska dostępu do zbuforowanych wartości kluczy. Choć rozwiązanie to podnosi poziom zabezpieczeń, utrudnia nieco pracę użytkownika i niewiele różni się od uruchomienia programu ssh-agent z pominięciem skryptu keychain. Jest to przykład odwiecznego problemu — czy wybrać rozwiązanie bezpieczne, czy wygodne w użyciu. Mimo wszystko skrypt keychain z opcją --clear ma jedną przewagę nad agentem SSH. Nawet w przypadku dołączenia opcji --clear zadania mechanizmu cron oraz skrypty powłoki mogą ustanawiać połączenia bez konieczności oczekiwania na wprowadzenie hasła. Wynika to z faktu, że bufor kluczy prywatnych jest oczyszczany podczas logowania, a nie w chwili zakończenia pracy z systemem. Wylogowanie nie stanowi żadnego zagrożenia dla funkcjonowania systemu. Nie ma więc powodów, dla których skrypt keychain powinien usuwać z pamięci klucze prywatne. Opcja --clear doskonale nadaje się do wykorzystania w tych systemach, w których administrator rzadko się loguje, a które muszą okresowo realizować zadania objęte stosownym systemem zabezpieczeń. Przykładami tego typu jednostek mogą być serwery kopii zapasowych, firewalle i routery.

Aby z poziomu skryptu lub instrukcji cron wywołać program ssh-agent pracujący pod kontrolą skryptu keychain, trzeba włączyć (instrukcja source) plik utworzony przez mechanizm keychain. Skrypt keychain obsługuje również klucze PGP. [ -r ~/.ssh-agent ] && source ~/.ssh-agent \ || { echo "Skrypt keychain nie działa" >&2 ; exit 1; }

Analiza Użytkownik skryptu wykorzystującego połączenie SSH nie chce być pytany o hasło. Nie chce również oglądać ostrzegawczych komunikatów. Dodana do polecenia ssh opcja –q włącza tryb „cichej” pracy, dzięki czemu na ekranie nie są wyświetlane żadne informacje. Z kolei dołączenie 350

|

Rozdział 14. Bezpieczne skrypty powłoki

opcji –o 'BatchMode yes' zapobiega generowaniu zapytań kierowanych do użytkownika. Oczywiście, jeśli klient SSH nie będzie mógł dokonać uwierzytelnienia, przerwie pracę, ponieważ nie będzie mógł zapytać użytkownika o hasło. Jednak w przypadku zastosowania rozwiązania prezentowanego w niniejszej recepturze taki sposób działania nie powinien stanowić problemu. Usługa SSH jest niezwykle użytecznym narzędziem, za pomocą którego można realizować tak wiele różnych zadań, że ich omówienie oznaczałoby napisanie osobnej książki. Więcej szczegółowych informacji na temat mechanizmu SSH dostarcza publikacja Richarda E. Silvermana i Daniela J. Barretta SSH. The Secure Shell. The Definitive Guide (O’Reilly 2005). Wymiana kluczy publicznych między oprogramowaniem OpenSSH a serwerem SSH Server firmy SSH Communication Security bywa problematyczna, o czym informuje Daniel J. Barrett w 6. rozdziale książki Linux. Bezpieczeństwo. Receptury (Helion 2003). Doskonałym źródłem informacji są również artykuły publikowane w portalu IBM developerWorks przez autora skryptu keychain, Arnolda Robbinsa (głównego architekta dystrybucji Gentoo) — http://www.ibm.com/developerworks/library/l-keyc.html, http://www.ibm.com/developerworks/ ´library/l-keyc2, http://www.ibm.com/developerworks/linux/library/l-keyc3. Jeśli po uruchomieniu skrypt keychain nie działa zgodnie z oczekiwaniami lub po chwili przestaje działać, trzeba sprawdzić, czy w systemie nie został uruchomiony inny skrypt, który wznawia pracę programu ssh-agent. Porównanie informacji o identyfikatorach procesów (PID) i gniazdach (socket), wygenerowanych przez poniższe polecenia, pozwala na ustalenie, czy dany problem występuje, czy nie. Treść polecenia ps może wymagać dostosowania do konkretnego systemu — jeśli opcje –ef nie zostaną zaakceptowane, warto wypróbować opcje –eu. $ ps -ef | grep [s]sh-agent jp 30092 1 0 12:51 ?

00:00:00 ssh-agent

$ cat ~/.keychain/$HOSTNAME-sh SSH_AUTH_SOCK=/tmp/ssh-uUrAc30091/agent.30091; export SSH_AUTH_SOCK; SSH_AGENT_PID=30092; export SSH_AGENT_PID; $ set | grep SSH_A SSH_AGENT_PID=30092 SSH_AUTH_SOCK=/tmp/ssh-uUrAc30091/agent.30091

Zobacz również • http://www.gentoo.org/proj/en/keychain. • http://www.ibm.com/developerworks/library/l-keyc2. • Książka Richarda E. Silvermana i Daniela J. Barretta SSH. The Secure Shell. The Definitive

Guide (O’Reilly 2005). • Książka Daniela J. Barretta Linux. Bezpieczeństwo. Receptury (Helion 2003). • Książka Niella Fergusona i Bruce’a Schneiera Practical Cryptography (Wiley 2003). • Książka Bruce’a Schneiera Kryptografia dla praktyków (WNT 2002). • Receptura 8.15, „Dodatkowe funkcje polecenia less”.

14.21. Wykorzystanie usługi SSH bez hasła

|

351

Odcisk klucza Wszystkie wersje aplikacji SSH generują odciski klucza, czyli wartości ułatwiające porównywanie i weryfikowanie zarówno kluczy, jak i danych użytkownika. Sprawdzanie bit po bicie długich pseudolosowych danych jest bez wątpienia czynnością nużącą i podatną na błędy, a w niektórych przypadkach (np. dyktowania przez telefon) w praktyce niemożliwą do wykonania. Funkcje generowania odcisków klucza znacznie ułatwiają tę procedurę. Podobne rozwiązania są stosowane również w innych aplikacjach, szczególnie w odniesieniu do kluczy PGP/GPG. Podstawowym powodem weryfikowania kluczy jest możliwość wystąpienia tak zwanego ataku man-in-the-middle, czyli ataku prowadzonego przez osoby pośredniczące w komunikacji. Gdy Bob odbierze klucz od Alicji, będzie chciał mieć pewność, że wartość, którą otrzymał, rzeczywiście wygenerowała Alicja, a nie Ewa, która przechwyciła klucz Alicji i podstawiła własny. W tym celu konieczna jest pozapasmowa (ang. out-of-band) wymiana informacji, na przykład z wykorzystaniem telefonu. Odciski klucza są prezentowane w dwóch formatach — tradycyjnym formacie heksadecymalnym mechanizmu PGP oraz nowszym i łatwiejszym do przeczytania formacie bubblebabble. Gdy Bob odbierze klucz Alicji, może do niej zatelefonować i odczytać treść odcisku klucza. Jeżeli odciski się zgadzają, oznacza to, że klucze zostały wymienione poprawnie. $ ssh-keygen -l -f ~/.ssh/id_dsa 2048 75:6c:be:d6:7a:62:9d:1e:40:7e:b4:1c:1c:73:40:d8 /home/jp/.ssh/id_dsa.pub $ ssh-keygen -l -f ~/.ssh/id_dsa.pub 2048 75:6c:be:d6:7a:62:9d:1e:40:7e:b4:1c:1c:73:40:d8 /home/jp/.ssh/id_dsa.pub $ ssh-keygen -B -f ~/.ssh/id_dsa 2048 xolem-galun-fakym-tuvez-fytav-racug-gygoc-nupyt-feluk-cacog-tuxux /home/jp/.ssh/id_dsa.pub $ ssh-keygen -B -f ~/.ssh/id_dsa.pub 2048 xolem-galun-fakym-tuvez-fytav-racug-gygoc-nupyt-feluk-cacog-tuxux /home/jp/.ssh/id_dsa.pub

14.22. Ograniczenie liczby poleceń SSH Problem Chcemy ograniczyć zakres działalności użytkowników i skryptów ustanawiających połączenia SSH z danym systemem2.

2

Pragniemy podziękować Richardowi Silvermanowi i Danielowi Barrettowi za inspirację i wyśmienitą lekturę książek SSH. The Secure Shell. The Definitive Guide (a szczególnie rozdziałów 2., 6. i 11.) oraz Linux. Bezpieczeństwo. Receptury, bez których ten podrozdział byłby jedynie namiastką prawdziwej receptury.

352

|

Rozdział 14. Bezpieczne skrypty powłoki

Rozwiązanie Należy zmodyfikować plik ~/.ssh/authorized_keys, uwzględniając mechanizm wymuszonych poleceń (ang. forced commands), i opcjonalnie wyłączyć niepotrzebne funkcje usługi SSH. Załóżmy, że chcemy umożliwić pracę procesowi rsync i jednocześnie wyłączyć interaktywny dostęp do systemu. Pierwsza czynność polega na ustaleniu, jakie dokładnie polecenie zostaje wykonane w zdalnym systemie. Utwórzmy klucz (zgodnie z instrukcjami zawartymi w recepturze 14.21, „Wykorzystanie usługi SSH bez hasła”) i dodajmy wymuszone polecenie, które dostarczy stosownych informacji. W tym celu trzeba otworzyć plik ~/.ssh/authorized_keys w trybie edycji i wprowadzić poniższy wiersz przed treścią klucza: command="/bin/echo Polecenie to: $SSH_ORIGINAL_COMMAND"

Treść zmodyfikowanego wiersza powinna być zbliżona do następującej (wszystkie dane są zapisane w jednym wierszu): command="/bin/echo Polecenie to: $SSH_ORIGINAL_COMMAND" ssh-dss AAAAB3NzaC1kc3MAAAEBAObr2PG+yjMvrGWKeLThQwebF6R5u5KqWj3/1EqUVeyXPih4Nw61utT9S0neg6Qdnh IF33an3uqPN4RYjQ8VDqWcjvnfJb0aMO+IOvlZzm+2aKfix1LI9m2/oxo2j4XzxZ9Ze0jjf4x1hm9CRHERbCUg fq8lDkUn91F58mWGDr6o3XkoYdWNWqmRE/QoX0e9nRyBUhJYUCmBCeVUcelwlLREN+bpbOt7e0RUl8SM8Rz3uK 9toMvbFMjPkjoLnqQqOTZWnT5A39YleOcfrI0ghz7dgDbhdCejXItrEH4GLZQXAvdJ5SPHusKCmL9VUnPacbaJ c8L5XG87/Mk8ptNHmf0AAAAVAOoZzIqLY8yq5a2Mjg0xXy6mt0CBAAABAAy12QQH8epojDh3+RdrXluyaGfhlP M8ZCrsqZzA/lJDP5xq0Q3LQUEUBeHyuiZghpl9gf+vsq/5zzH+OcmZh7WbHqf4VnNea9fgNrENKrBAB1TUm4Y+ azZ43h6q5rd9a1YzAu/FTKsd2BSI57+b6SqKqBdQif1IzkkvnO+zGpr4CMar2ieLCmH4Tv69Pfm4jidQ12J+jZ pp68A7Bd/0Pk0G6VtpvKCgW8MTPvW/0j160At/15wG0p8aQUvPDSCR28yyBEeBU2HDSZCeJNywYOYVmO/VBlRV ApDawufOnt4kGAj0fF41x5eN6qBcY9xASJYEzgR5zuVWXNVP+pFSYf8AAAEACRaQPzdPCrE8GttQRz1az3TbVn Lcv/o6zbeD5K8vnW+Jqfk6mFMlaPqdZhHhrp6kZaGWAdISbxDqyIWgl8jKMf976g2KG3FL92i/z269Rj+7SyXJ GUiqoB5mhYPkwGXiE60Syo6erpfgZ/N9q+A7HpZjhgHRxug+vv0FR6Qv3zwhmXe9UgKlzJyOvHdlyNTP89oxFg 8map3cPWewwLKMLNoeDPgJKEzDHPxdTca+8cH3ReBCbWK2WSvYvtUj6EJo3YzM48MSLi6XbBGz42gozWN7moIP ouxWI9/aOKbV7XFMDogRqZMnwVNV0eno0a0VkSwNL6KI2OTmxWekD3JGzA== To jest nowy klucz

Teraz możemy wykonać polecenie i przeanalizować uzyskany wynik: $ ssh zdalna_jednostka 'ls –l /etc' Polecenie to: ls –l /etc

Wadą tego rozwiązania jest to, że programy, które muszą dysponować całym kanałem STDOUT´STDIN (takie jak rsync), nie będą mogły działać poprawnie. $ rsync –avzL –e ssh zdalna_jednostka:/etc . protocol version mismatch -- is your shell clean? (see the rsync man page for an explanation) rsync error: protocol incompatibility (code 2) at compat.c(64)

Problem ten można jednak rozwiązać, modyfikując nieco wymuszone polecenie: command="/bin/echo Polecenie to: $SSH_ORIGINAL_COMMAND >> ~/polecenie_ssh"

Spróbujmy ponowić instrukcję po stronie klienckiej: $ rsync –avzL –e ssh zdalna_jednostka:/etc . rsync: connection unexpectedly closed (0 bytes received so far) [receiver] rsync error: error in rsync protocol data stream (code 12) at io.c(420)

W zdalnym systemie został zapisany plik: $ cat ~/polecenie_ssh Polecenie to: rsync --server --sender –vlLogDtprz . /etc

Możemy więc odpowiednio zmodyfikować treść wymuszonego polecenia.

14.22. Ograniczenie liczby poleceń SSH

|

353

Dwa kolejne ustawienia, które warto zdefiniować, pozwalają na ograniczenie jednostek ustanawiających połączenia oraz wyłączenie niektórych funkcji SSH. Mechanizm ograniczający listę dozwolonych jednostek wymaga podania nazwy lub adresu IP komputera inicjującego połączenie. Polecenia wyłączające są proste do zapamiętania: no-port-forwarding,no-x11-forwarding,no-agent-forwarding,no-pty

Uwzględnienie wszystkich opisywanych dotychczas ustawień oznacza utworzenie jednego dużego wiersza o treści zbliżonej do następującej: no-port-forwarding,no-x11-forwarding,no-agent-forwarding,nopty,from="jednostka_kliencka",command=" rsync --server --sender –vlLogDtprz . /etc" ssh-dss AAAAB3NzaC1kc3MAAAEBAObr2PG+yjMvrGWKeLThQwebF6R5u5KqWj3/1EqUVeyXPih4Nw61utT9S0neg6Qdnh IF33an3uqPN4RYjQ8VDqWcjvnfJb0aMO+IOvlZzm+2aKfix1LI9m2/oxo2j4XzxZ9Ze0jjf4x1hm9CRHERbCUg fq8lDkUn91F58mWGDr6o3XkoYdWNWqmRE/QoX0e9nRyBUhJYUCmBCeVUcelwlLREN+bpbOt7e0RUl8SM8Rz3uK 9toMvbFMjPkjoLnqQqOTZWnT5A39YleOcfrI0ghz7dgDbhdCejXItrEH4GLZQXAvdJ5SPHusKCmL9VUnPacbaJ c8L5XG87/Mk8ptNHmf0AAAAVAOoZzIqLY8yq5a2Mjg0xXy6mt0CBAAABAAy12QQH8epojDh3+RdrXluyaGfhlP M8ZCrsqZzA/lJDP5xq0Q3LQUEUBeHyuiZghpl9gf+vsq/5zzH+OcmZh7WbHqf4VnNea9fgNrENKrBAB1TUm4Y+ azZ43h6q5rd9a1YzAu/FTKsd2BSI57+b6SqKqBdQif1IzkkvnO+zGpr4CMar2ieLCmH4Tv69Pfm4jidQ12J+jZ pp68A7Bd/0Pk0G6VtpvKCgW8MTPvW/0j160At/15wG0p8aQUvPDSCR28yyBEeBU2HDSZCeJNywYOYVmO/VBlRV ApDawufOnt4kGAj0fF41x5eN6qBcY9xASJYEzgR5zuVWXNVP+pFSYf8AAAEACRaQPzdPCrE8GttQRz1az3TbVn Lcv/o6zbeD5K8vnW+Jqfk6mFMlaPqdZhHhrp6kZaGWAdISbxDqyIWgl8jKMf976g2KG3FL92i/z269Rj+7SyXJ GUiqoB5mhYPkwGXiE60Syo6erpfgZ/N9q+A7HpZjhgHRxug+vv0FR6Qv3zwhmXe9UgKlzJyOvHdlyNTP89oxFg 8map3cPWewwLKMLNoeDPgJKEzDHPxdTca+8cH3ReBCbWK2WSvYvtUj6EJo3YzM48MSLi6XbBGz42gozWN7moIP ouxWI9/aOKbV7XFMDogRqZMnwVNV0eno0a0VkSwNL6KI2OTmxWekD3JGzA== To jest nowy klucz

Analiza W przypadku jakichkolwiek trudności w posługiwaniu się poleceniem ssh zawsze można dołączyć do niego opcje –v lub –v –v, które powodują wyświetlenie dodatkowych informacji, często zawierających wskazówkę do rozwiązania problemu. Warto również uwzględnić je w poleceniach, które są realizowane poprawnie, gdyż w ten sposób możemy się zapoznać z rodzajem generowanych informacji. Osoby, które chcą w większym zakresie decydować o tym, jakie czynności może wykonywać dana jednostka, a jakie mają być zabronione, powinny zainteresować się powłoką rssh (powłoka OpenSSH o ograniczonym zakresie funkcji). Obsługuje ona takie mechanizmy, jak scp, sftp, rdist, rsync oraz cvs. Można by przypuszczać, że nałożenie wymienionych ograniczeń jest bardzo łatwe, ale w praktyce wcale takie nie jest. Problem wynika ze sposobu działania usługi SSH (i używanych wcześniej r-poleceń), bazującego na genialnych założeniach, które doskonale się sprawdzają w praktyce, ale wykluczają możliwość ograniczenia zakresu działania usługi. W dużym uproszczeniu można przyjąć, że funkcjonowanie mechanizmu SSH polega na połączeniu lokalnego strumienia wyjściowego (STDOUT) ze zdalnym strumieniem wejściowym (STDIN) oraz zdalnego strumienia wyjściowego z lokalnym strumieniem wejściowym. Działanie narzędzi scp czy rsync sprowadza się więc do przesyłania strumienia bajtów z komputera lokalnego do jednostki zdalnej w sposób zbliżony do przekazywania ich za pomocą potoku. To bardzo wszechstronne rozwiązanie uniemożliwia jednocześnie serwerowi SSH odróżnienie pracy interaktywnej od dostępu związanego z wykonaniem polecenia scp. Dla oprogramowania SSH te dwa mechanizmy wymiany danych są identyczne. Z tego też względu nie można w plikach konfiguracyjnych powłoki bash umieszczać wielu instrukcji echo i poleceń związanych z debugowaniem kodu (zagadnienie to

354 |

Rozdział 14. Bezpieczne skrypty powłoki

jest tematem receptury 16.19, „Tworzenie samodzielnych, przenośnych plików RC”). Generowane w ten sposób dane zostałyby wprowadzone do strumienia bajtowego i spowodowałyby spore zamieszanie w wymianie informacji. W jaki więc sposób działa powłoka rssh? Instaluje nakładkę, która jest definiowana w pliku /etc/passwd zamiast domyślnej powłoki logowania (np. bash). Program nakładki decyduje o tym, które operacje są dozwolone, a które nie. To czyni go znacznie bardziej wszechstronnym od klasycznego mechanizmu ograniczania zakresu poleceń SSH.

Zobacz również • Książka Richarda E. Silvermana i Daniela J. Barretta SSH. The Secure Shell. The Definitive Guide

(O’Reilly 2005). • Książka Daniela J. Barretta Linux. Bezpieczeństwo. Receptury (Helion 2003). • Receptura 14.21, „Wykorzystanie usługi SSH bez hasła”. • Receptura 16.19, „Tworzenie samodzielnych, przenośnych plików RC”.

14.23. Rozłączanie nieaktywnych sesji Problem Chcemy mieć możliwość automatycznego rozłączania sesji nieaktywnych użytkowników — w szczególności dotyczy to użytkownika root.

Rozwiązanie Wystarczy zdefiniować w pliku /etc/bashrc lub ~/.bashrc zmienną środowiskową $TMOUT, przypisując jej wartość odpowiadającą liczbie sekund dopuszczalnej nieaktywności. Jeśli użytkownik pracujący w trybie interaktywnym nie wprowadzi polecenia w czasie $TMOUT sekund, powłoka bash zakończy działanie.

Analiza Zmienna $TMOUT jest również wykorzystywana we wbudowanych poleceniach powłoki read i select. Chcąc uniemożliwić zwykłym użytkownikom zmienianie wartości zmiennej, trzeba przenieść jej definicję do pliku systemowego, którego wspomniani użytkownicy nie mogą modyfikować — na przykład do plików /etc/profile lub /etc/bashrc. declare –r TMOUT=3600 # lub readonly TMOUT=3600

14.23. Rozłączanie nieaktywnych sesji

|

355

Ponieważ użytkownik dysponuje pełną kontrolą nad własnym środowiskiem, nie można w pełni polegać na funkcji realizowanej przez zmienną $TMOUT, nawet jeśli została ona oznaczona jako wartość tylko do odczytu. Użytkownik może bowiem uruchamiać kolejne powłoki. Należy ją traktować jako użyteczny mechanizm wspomagający pracę pozytywnie do niej nastawionych użytkowników, szczególnie wiecznie roztargnionych administratorów systemu.

Zobacz również • Receptura 16.19, „Tworzenie samodzielnych, przenośnych plików RC”.

356

|

Rozdział 14. Bezpieczne skrypty powłoki

ROZDZIAŁ 15.

Zaawansowane mechanizmy skryptowe

Pełna zgodność i uniwersalność systemów Unix i POSIX jest celem odwiecznych zmagań osób zaangażowanych w rozwój tych platform. Dlatego jednym z największych problemów, z którymi spotykają się twórcy zaawansowanych aplikacji skryptowych, jest zapewnienie również przenośności programu, tj. zagwarantowanie, że dany skrypt będzie poprawnie wykonywał swoje zadanie we wszystkich systemach, w których jest zainstalowany interpreter bash. Przygotowanie aplikacji działającej właściwie na różnych platformach systemowych nie jest łatwe. Trzeba wziąć pod uwagę wiele elementów, które są w różny sposób implementowane w różnych systemach. Na przykład sama powłoka bash nie zawsze jest instalowana w tym samym katalogu, a wiele poleceń uniksowych jest wyposażanych w opcje o nieznacznie odmiennym przeznaczeniu (generujące nieco inne wyniki). Tego rodzaju problemy oraz sposoby ich rozwiązywania są tematem niniejszego rozdziału. Zostaną tu także opisane mechanizmy, które bardzo często przydają się w codziennej pracy użytkownika, ale ich zaimplementowanie nie jest tak oczywiste, jakby się mogło wydawać. Rzecz dotyczy zaawansowanych zadań skryptowych, takich jak automatyzowanie procesów, wysyłanie wiadomości e-mail z poziomu skryptu, rejestracja zdarzeń w systemie syslog, wykorzystywanie zasobów sieciowych oraz niestandardowe mechanizmy pobierania danych wejściowych i przekazywania danych wyjściowych. Choć zapoznanie się z treścią niniejszego rozdziału sprawia, że budowanie złożonych aplikacji skryptowych jest znacznie łatwiejsze, nie można zapominać o obowiązku definiowania przejrzystych instrukcji i właściwego ich dokumentowania. Jeden z pierwszych programistów systemu Unix — Brian Kernighan — odniósł się do tego problemu w następujący sposób: Debugowanie jest dwa razy trudniejsze od pisania kodu. Zatem jeśli wykorzystasz wszystkie zasoby swojego umysłu na opracowanie możliwie najbardziej wyrafinowanego kodu, z definicji nie będziesz w stanie go przeanalizować.

Nie jest problemem napisanie wyrafinowanego skryptu powłoki, który będzie wyjątkowo trudny lub nawet niemożliwy do przeanalizowania. Im bardziej wyjątkowy wydaje się kod podczas jego opracowywania, tym więcej czasu będzie musiał poświęcić jego autor (lub, co gorsza, inny programista), aby zrozumieć działanie programu po 6, 12 lub 18 miesiącach. Jeżeli ktoś mimo wszystko nie może powstrzymać się od wykorzystywania niestandardowych rozwiązań, powinien przynajmniej dokładnie dokumentować swoją pracę (więcej informacji na ten temat zostało zawartych w recepturze 5.1, „Dokumentowanie skryptu”).

357

15.1. Przenośność skryptu — problem wiersza #! Problem Musimy uruchomić skrypt powłoki w kilku systemach, ale interpreter bash nie zawsze jest umieszczony w tym samym katalogu (zagadnienie to zostało również opisane w recepturze 1.11, „Pobranie interpretera bash dla systemu BSD”).

Rozwiązanie Należy wykorzystać polecenie /usr/bin/env w następujący sposób: #!/usr/bin/env bash. Jeżeli w danym systemie program env nie jest zapisany w katalogu /usr/bin, trzeba poprosić administratora, aby go tam zainstalował, przeniósł lub utworzył dowiązanie symboliczne do odpowiedniego pliku. Na przykład w systemie Red Hat plik ten domyślnie jest zapisywany w katalogu /bin (/bin/env), ale jednocześnie tworzone jest stosowne dowiązanie symboliczne. Oczywiście, można by również utworzyć dowiązanie symboliczne dla samej powłoki bash, ale odwołanie się do programu env jest rozwiązaniem poprawnym, zgodnym z przyjętą konwencją.

Analiza Zadanie programu env polega na „uruchomieniu programu w zmienionym środowisku”. Jednak dzięki mechanizmowi przeszukiwania katalogów zmiennej $PATH podczas uruchamiania dostarczonego polecenia, doskonale nadaje się on do wykorzystania w opisywanym przypadku. Nie można ulec pokusie zastąpienia treści pierwszego wiersza zapisem #!/bin/sh. Jeśli w skrypcie zostały zdefiniowane rozwiązania charakterystyczne tylko dla powłoki bash, nie zostaną one poprawnie zrealizowane w systemach, które nie kojarzą powłoki bash z odwołaniem /bin/sh (np. BSD, Solaris, Ubuntu 6.10+). Ponadto nawet jeśli w danej chwili skrypt nie obejmuje rozwiązań właściwych tylko powłoce bash, istnieje ryzyko, że zostaną one dodane w przyszłości. Jeżeli opracowywana aplikacja musi wykorzystywać mechanizmy właściwe dla standardu POSIX, zawsze należy używać zapisu #!/bin/sh (sam skrypt nie powinien być jednak tworzony w systemie Linux; zagadnienie to jest tematem receptury 15.3, „Tworzenie przenośnych skryptów powłoki”), w innych przypadkach konieczne jest precyzyjne definiowanie interpretera. Niekiedy między znakami #! a ciągiem /bin/interpreter można zauważyć znak spacji. W przeszłości niektóre systemy wymagały bowiem rozdzielania wymienionych ciągów. Zapis ten nie jest już wykorzystywany od dłuższego czasu. Istnieje więc bardzo małe prawdopodobieństwo, że system udostępniający powłokę bash będzie wymagał uwzględnienia znaku spacji. Obecnie jego brak jest powszechnie tolerowany. Powinien jednak występować w skryptach, które muszą zachować zgodność nawet z przestarzałymi systemami. We wszystkich dłuższych skryptach i funkcjach, które zostały udostępnione jako materiały towarzyszące książce, występuje zapis #!/usr/bin/env bash. Gwarantuje on bowiem poprawne działanie skryptu w największej grupie systemów operacyjnych. Ponadto, biorąc pod uwagę fakt, że polecenie env wykorzystuje zmienną $PATH do odszukania powłoki bash, jest to element podnoszący poziom zabezpieczenia aplikacji (więcej informacji na ten temat znajduje się w recepturze 14.2, „Unikanie spoofingu w pracy interpretera”), choć nie o największym znaczeniu. 358 |

Rozdział 15. Zaawansowane mechanizmy skryptowe

Paradoksalnie uwzględnienie programu env w celu zapewnienia przenośności skryptu powoduje, że pierwszy jego wiersz nie jest jednakowo przetwarzany we wszystkich systemach. Wiele platform, w tym Linux, umożliwia przekazanie do interpretera pojedynczego polecenia. Dlatego wykonanie instrukcji #!/usr/bin/env bash – kończy się wygenerowaniem błędu: /usr/bin/env: bash -: No such file or directory

Problem wynika z tego, że interpreterem jest /usr/bin/env, a jedyny dopuszczalny parametr to bash -. Inne systemy, takie jak BSD i Solaris, nie mają tego ograniczenia. Znak myślnika (-) służy jako element zwiększający bezpieczeństwo pracy aplikacji (zagadnienie to zostało opisane w recepturze 14.2, „Unikanie spoofingu w pracy interpretera”). Jednak z uwagi na jego niejednakową interpretację w różnych systemach jest przyczyną problemu z przenośnością lub bezpieczeństwem skryptu. Można więc dołączyć znak (-) w celu podniesienia poziomu zabezpieczeń skryptu za cenę zmniejszenia przenośności lub pominąć go, zwiększając przenośność i zmniejszając bezpieczeństwo. Uwzględniając jednak fakt, że program env przeszukuje katalogi zmiennej $PATH, w przypadku jakichkolwiek wątpliwości dotyczących poziomu bezpieczeństwa skryptu, należałoby i tak pominąć wywołanie tego programu. Dlatego też brak możliwości powszechnego stosowania znaku myślnika jest akceptowalny. Podsumowując, należy pomijać znak myślnika (-) zawsze, gdy istotna jest przenośność instrukcji env, oraz dołączać go do nazwy interpretera za każdym razem, gdy ważniejsze jest bezpieczeństwo aplikacji.

Zobacz również • Informacje na temat pierwszego wiersza skryptu (/usr/bin/env) są dostępne pod adresami: • http://srfi.schemers.org/srfi-22/mail-archive/msg00069.html. • http://www.in-ulm.de/~mascheck/various/shebang. • http://homepages.cwi.nl/~aeb/std/hashexclam-1.html. • http://www.faqs.org/faqs/unix-faq/faq/part3 — punkt 3.16, Why do some scripts start with #!...?. • Receptura 1.11, „Pobranie interpretera bash dla systemu BSD”. • Receptura 15.2, „Ustawianie zmiennej $PATH zgodnie z zalecanymi POSIX”. • Receptura 15.3, „Tworzenie przenośnych skryptów powłoki”. • Receptura 15.6, „Przenośność instrukcji echo”.

15.2. Ustawianie zmiennej $PATH zgodnie z zaleceniami POSIX Problem Pracujemy w systemie, który udostępnia starsze narzędzia lub narzędzia określonego dostawcy (np. Solaris) i musimy wyznaczyć wartość zmiennej $PATH w taki sposób, aby była ona zgodna ze specyfikacją POSIX.

15.2. Ustawianie zmiennej $PATH zgodnie z zaleceniami POSIX

|

359

Rozwiązanie Wystarczy wykorzystać narzędzie getconf. PATH=$(PATH=/bin:/usr/bin getconf PATH)

Poniżej zostało przedstawionych kilka domyślnych wartości zmiennej $PATH oraz wartości generowanych przez polecenie getconf (zgodnych z zaleceniami POSIX) w różnych systemach operacyjnych: # Red Hat Enterprise Linux (RHEL) 4.3 $ echo $PATH /usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/$USER/bin $ getconf PATH /bin:/usr/bin # Debian Sarge $ echo $PATH /usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games $ getconf PATH /bin:/usr/bin # Solaris 10 $ echo $PATH /usr/xpg4/bin:/usr/ccs/bin:/usr/bin:/opt/SUNWspro/bin $ getconf PATH /usr/bin: # OpenBSD 3.7 $ echo $PATH /home/$USER/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local /sbin:/usr/games $ getconf PATH /usr/bin:/bin:/usr/bin:/sbin:/usr/X11R6/bin:/usr/local/bin

Analiza Polecenie getconf zwraca wartości różnych systemowych parametrów konfiguracyjnych, które można wykorzystać do sformowania domyślnej ścieżki przeszukiwania plików. Ponieważ jednak program getconf sam nie jest poleceniem wbudowanym, niezbędna jest pewna początkowa wartość zmiennej $PATH (np. PATH=/bin:/usr/bin), która umożliwi odszukanie pliku programu. Teoretycznie powinno się wykorzystywać zmienną CS_PATH. Jednak w praktyce okazuje się, że zmienna PATH jest właściwie przetwarzana we wszystkich testowanych systemach, natomiast odwołania do zmiennej CS_PATH nie są poprawnie realizowane w systemach BSD.

Zobacz również • http://www.unixreview.com/documents/s=7781/uni1042138723500. • Receptura 9.11, „Wyszukiwanie plików z wykorzystaniem listy potencjalnych lokalizacji”.

360

|

Rozdział 15. Zaawansowane mechanizmy skryptowe

• Receptura 14.3, „Wyznaczanie bezpiecznej wartości $PATH”. • Receptura 14.9, „Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfi-

kowanie zawartości”.

• Receptura 14.10, „Dodawanie bieżącego katalogu do listy $PATH”. • Receptura 16.3, „Trwała zmiana wartości $PATH”. • Receptura 16.4, „Chwilowa zmiana wartości $PATH”. • Receptura 19.3, „Zapominanie o braku bieżącego katalogu w zmiennej $PATH”.

15.3. Tworzenie przenośnych skryptów powłoki Problem Tworzymy skrypt, który musi poprawnie pracować w kilku różnych wersjach systemów operacyjnych Unix i POSIX.

Rozwiązanie Przede wszystkim należy zastosować wbudowane polecenie powłoki command z opcją –p, która odpowiada za wyszukanie programu zgodnego z zaleceniami POSIX (np. w systemie Solaris oznacza to uwzględnienie katalogów /usr/xpg4 lub /usr/xpg6). command –p program parametry

Następnie trzeba odszukać najstarszy lub w najmniejszym stopniu kompatybilny system Unix i przygotować działający w nim kod. W przypadku problemów z ustaleniem, która platforma jest w najmniejszym stopniu zgodna z innymi systemami, można użyć dowolnej dystrybucji systemu BSD lub Solaris (im starsza wersja, tym lepsza).

Analiza Polecenie command –p wykorzystuje domyślną wartość ścieżek przeszukiwania, która gwarantuje odnalezienie wszystkich narzędzi zgodnych ze specyfikacją POSIX. Jeżeli przygotowywany skrypt będzie uruchamiany tylko w systemach Linux, nie ma powodu, by zaprzątać sobie głowę opisywanym problemem. Jeśli jednak zadanie polega na opracowaniu w pełni przenośnego kodu, w pracach programistycznych nie należy wykorzystywać systemów Linux, czy Windows (np. za pośrednictwem mechanizmu Cygwin). Z przygotowaniem kodu na platformie Linux wiążą się następujące problemy:

1. Interpreter /bin/sh nie jest tak naprawdę powłoką Bourne’a, ale powłoką bash pracującą w trybie interpretera Bourne’a — wyjątkiem jest powłoka dash (na przykład w systemie Ubuntu 6.10). Wszystkie wymienione powłoki są bardzo dobre, ale nie doskonałe. Żadna z nich nie działa dokładnie tak samo jak pozostałe, co może być niezwykle mylące (dotyczy to chociażby sposobu wykonywania polecenia echo).

2. Systemy Linux korzystają z narzędzi projektu GNU, a nie z oryginalnych programów platform Unix.

15.3. Tworzenie przenośnych skryptów powłoki

|

361

Chcielibyśmy zostać dobrze zrozumiani — uwielbiamy systemy Linux i korzystamy z nich codziennie. Ale Linux nie jest Uniksem. Pewne jego mechanizmy działają inaczej, a cały system bazuje na narzędziach GNU. Narzędzia GNU są niezwykle użyteczne i to właśnie jest problemem. Udostępniają użytkownikom wiele opcji i funkcji, których nie ma w innych systemach. W rezultacie skrypty opracowywane na platformie Linux nie działają w innych systemach, generując różnorodne komunikaty o błędach niezależnie od staranności, z jaką programista przygotował aplikację. Z drugiej strony systemy Linux cechuje taki poziom zgodności z innymi platformami, że skrypty opracowane w innych uniksowych systemach niemal zawsze poprawnie działają w Linuksie. Nie zawsze są idealne (np. domyślny sposób działania polecenia echo powoduje wyświetlenie ciągu \n zamiast przejścia do następnego wiersza), ale zazwyczaj pozostają dostatecznie funkcjonalne. Mamy tu do czynienia z sytuacją bez wyjścia — im więcej różnych funkcji powłoki zostanie wykorzystanych w skrypcie, w tym mniejszym stopniu zależy on od sposobu działania zewnętrznych programów. Jednocześnie jednak, mimo że interpreter bash jest znacznie bardziej rozbudowany niż powłoka sh, jest jednym z tych narzędzi, które mogą być zainstalowane w określonym systemie lub nie. Co prawda pewna odmiana powłoki sh jest dostępna w każdym systemie Unix lub systemie zgodnym z Uniksem, ale to nie zawsze rozwiązuje problem. Podobny problem wiąże się z zapisem opcji poleceń. Rozbudowana forma zapisu opcji w instrukcjach GNU jest znacznie łatwiejsza do analizowania w kodzie skryptu. Jednak bardzo często nie jest ona obsługiwana w innych systemach operacyjnych. Zatem zamiast polecenia sort --field´separator=, plik_nieposortowany > plik_posortowany konieczne jest zastosowanie instrukcji sort –t, plik_nieposortowany > plik_posortowany, która zapewnia przenośność kodu. Obecnie wykorzystanie do prac projektowych systemu operacyjnego innego niż Linux jest łatwiejsze niż kiedykolwiek. Jeśli dana firma nie dysponuje odpowiednimi platformami, można je łatwo i szybko zainstalować. Systemy Solaris czy BSD pracują w środowiskach wirtualnych, takich jak VMware Player lub VMware Server, które są uruchamiane na platformie Windows lub Linux (a niedługo będą również dostępne dla komputerów „Mac”). Jeżeli ktoś posługuje się komputerem „Mac” z systemem OS X, to tak naprawdę korzysta z systemu BSD. Mechanizm wirtualizacji (np. VMware) ułatwia również testowanie skryptów (zagadnienie to jest tematem receptury 15.4, „Testowanie skryptów w środowisku VMware”). Jedyną wadą opisywanego rozwiązania jest to, że systemy AIX i HP-UX nie mogą być instalowane w komputerach o architekturze x86, a tym samym nie współdziałają ze środowiskiem VMware. Jeżeli więc istnieje możliwość ich użycia, bezwzględnie należy z niej skorzystać. W przeciwnym przypadku warto zapoznać się z recepturą 1.15, „Korzystanie z powłoki bash bez jej pobierania”.

Zobacz również • Polecenie help command. • http://en.wikipedia.org/wiki/Debian_Almquist_shell. • http://pl.wikipedia.org/wiki/Bash. • http://www.opensolaris.org/os/article/2006-02-27_getting_started_with_opensolaris_using_vmware. • http://www.testdrive.hp.com/os.

362

|

Rozdział 15. Zaawansowane mechanizmy skryptowe

• http://www.testdrive.hp.com/faq. • http://www.polarhome.com. • http://www.faqs.org/faqs/hp/hpux-faq/preamble.html. • Historia Uniksa — http://www.levenez.com/unix. • Receptura 1.15, „Korzystanie z powłoki bash bez jej pobierania”. • Receptura 15.4, „Testowanie skryptów w środowisku VMware”. • Receptura 15.6, „Przenośność instrukcji echo”. • Punkt „Opcje i znaki specjalne instrukcji echo” w dodatku A.

15.4. Testowanie skryptów w środowisku VMware Problem Musimy opracować skrypt działający poprawnie na różnych platformach, ale nie mamy odpowiedniego systemu lub sprzętu.

Rozwiązanie Jeśli docelowymi platformami systemowymi są platformy o architekturze x86, wystarczy pobrać i zainstalować oprogramowanie VMware Server, a następnie uruchomić stosowny system. Można również poszukać w serwisie wstępnie przygotowanych maszyn wirtualnych — stosowne pakiety są udostępniane w portalu VMware, a także w serwisach dostawców oprogramowania oraz w różnych witrynach internetowych. Wadą tego rozwiązania jest to, że nie można go stosować w odniesieniu do systemów AIX i HP-UX, które nie działają w komputerach x86, a tym samym nie można ich zainstalować w środowisku VMware. Jeżeli więc wymienione systemy są zainstalowane na komputerach firmowych, należy z nich skorzystać. W przeciwnym przypadku warto zapoznać się z recepturą 1.15, „Korzystanie z powłoki bash bez jej pobierania”.

Analiza Procedura testowania skryptów nie wymaga zazwyczaj alokacji dużej ilości zasobów systemowych, dlatego można ją przeprowadzić z wykorzystaniem nawet średnio wydajnych komputerów, w których uda się zainstalować pakiet wirtualizacyjny VMware. Pakiet VMware celowo jest wymieniany w niniejszym rozwiązaniu jako jedyny, gdyż związane z nim produkty VMware Server i VMware Player są dostępne za darmo, pracują w systemach Linux i Windows i są bardzo łatwe w użyciu. Oczywiście, trzeba pamiętać, że istnieją również rozwiązania alternatywne. W przypadku zainstalowania pakietu VMware Server na serwerze Linux można nawet wyeliminować problemy związane z narzutem interfejsu graficznego — wystarczy użyć opartego na mechanizmie VNC oprogramowania VMware Console, które umożliwia zarządzanie pracą oprogramowania z innej jednostki z systemem Linux lub Windows. Do testowania skryptów wystarczy utworzenie jednostek wirtualnych o 128 MB pamięci RAM (a niekiedy jeszcze mniejszej). Do

15.4. Testowanie skryptów w środowisku VMware

|

363

przesyłania testowych skryptów i przetwarzanych przez nie danych można wykorzystać udziały NFS. Cała procedura weryfikacji kodu sprowadza się wówczas do ustanowienia połączenia telnet lub (lepiej) SSH i uruchomienia programu. Poniżej zostały wymienione kolejne etapy przykładowej procedury instalacji systemu w środowisku VMware player:

1. Ze strony o adresie http://vmware.com/vmtn/appliances/directory/browserapp.html pobierz darmowe oprogramowanie VMware Player dla systemu Windows lub Linux.

2. Pobierz wstępnie przygotowany obraz maszyny wirtualnej: a) Na stronie Browser Appliance portalu VMware dostępny jest pakiet oprogramowania Ubuntu Linux 5.10 (pochodna systemu Debian), Firefox 1.0.7 i Gnome 2.12.1 (258 MB, http://www.vmware.com/vmtn/appliances/directory/browserapp.html). b) W serwisie PC-BSD jest dostępna dystrybucja systemu BSD z pulpitem KDE (609 MB, http://www.pcbsd.org/?p=download#vmware).

3. Rozpakuj pobrany pakiet i otwórz w programie Playera. Utwórz nowy identyfikator VMware UUID.

Po uruchomieniu systemu (co zajmuje trochę czasu) można korzystać z pulpitu Gnome i powłoki bash 3.0 platformy Ubuntu 5.10 lub graficznego interfejsu KDE wraz z interpreterem bash 3.1 systemu BSD. Nic nie stoi przeszkodzie, żeby uruchomić dwa procesy Playera (lub oprogramowania w wersji Server) i pracować równolegle w dwóch środowiskach. Trzeba jednak pamiętać o tym, że są to środowiska graficzne, które wymagają znacznie więcej pamięci i zasobów procesora niż zwykłe dystrybucje tekstowe. Obydwa rozwiązania zostały tutaj opisane tylko jako jeden ze sposobów na szybkie wykonanie zadania. Mimo dużych wymagań systemowych mają one istotną zaletę — są „oficjalnymi” dystrybucjami, a nie obrazami systemów modyfikowanych nieustannie przez programistów skupionych w ramach określonego projektu. W oprogramowaniu pobieranym ze strony VMware Browser Appliance są zainstalowane narzędzia VMware, których nie ma w dystrybucji PC-BSD. Dlatego obsługa klawiatury i myszy w tego typu pakietach odbiega nieznacznie od innych dystrybucji. Szczególną uwagę trzeba zwrócić na informacje statusowe Playera, wyświetlane w lewej dolnej części okna.

Szczegółowe informacje na temat możliwości implementowania różnych systemów w środowisku VMware są dostępne na stronach VMware Forum oraz na stronach wyszukiwanych w serwisie Google.

Zobacz również • http://www.vmware.com. • http://www.vmware.com/player. • http://www.vmware.com/vmtn/appliances. • http://www.vmware.com/support/ws55/doc/new_guest_tools_ws.html. • http://www.ubuntu.org. • http://www.pcbsd.org.

364 |

Rozdział 15. Zaawansowane mechanizmy skryptowe

• Receptura 1.11, „Pobranie interpretera bash dla systemu BSD”. • Receptura 1.15, „Korzystanie z powłoki bash bez jej pobierania”.

15.5. Przenośność kodu pętli Problem Musimy zastosować pętlę for, która powinna działać również w starszych wersjach powłoki.

Rozwiązanie Technika zaprezentowana poniżej gwarantuje zgodność ze wszystkimi powłokami bash od wersji 2.04. $ for ((i=1; i dwa --> trzy cztery $ /bin/echo -e "jeden\tdwa\ttrzy\ncztery" jeden --> dwa --> trzy cztery $ shopt -s xpg_echo $ builtin echo "jeden\tdwa\ttrzy\ncztery" jeden --> dwa --> trzy cztery $ shopt -u xpg_echo $ builtin echo "jeden\tdwa\ttrzy\ncztery" jeden\tdwa\ttrzy\ncztery

Typowe działanie polecenia echo w systemie BSD (/bin/csh i /bin/sh): $ which echo echo: shell built-in command. $ echo "jeden\tdwa\ttrzy\ncztery" jeden\tdwa\ttrzy\ncztery $ /bin/echo "jeden\tdwa\ttrzy\ncztery" jeden\tdwa\ttrzy\ncztery

15.6. Przenośność instrukcji echo

|

367

$ echo –e "jeden\tdwa\ttrzy\ncztery" -e jeden\tdwa\ttrzy\ncztery $ /bin/echo –e "jeden\tdwa\ttrzy\ncztery" -e jeden\tdwa\ttrzy\ncztery $ printf "%b" "jeden\tdwa\ttrzy\ncztery" jeden --> dwa --> trzy cztery $ /bin/sh $ echo "jeden\tdwa\ttrzy\ncztery" jeden\tdwa\ttrzy\ncztery $ echo -e "jeden\tdwa\ttrzy\ncztery" jeden --> dwa --> trzy cztery $ printf "%b" "jeden\tdwa\ttrzy\ncztery" jeden --> dwa --> trzy cztery

Działanie polecenia echo w systemie Solaris 10 (/bin/sh): $ which echo /usr/bin/echo $ type echo echo is shell builtin $ echo "jeden\tdwa\ttrzy\ncztery" jeden --> dwa --> trzy cztery $ echo -e "jeden\tdwa\ttrzy\ncztery" jeden --> dwa --> trzy cztery $ printf "%b" "jeden\tdwa\ttrzy\ncztery" jeden --> dwa --> trzy cztery

Zobacz również • Polecenie help printf. • Polecenie man 1 printf. • http://www.opengroup.org/onlinepubs/009695399/functions/printf.html. • Receptura 2.3, „Formatowanie danych wyjściowych”. • Receptura 2.4, „Wyświetlanie wyniku bez znaku nowego wiersza”. • Receptura 15.1, „Przenośność skryptu — problem wiersza #!”. • Receptura 15.3, „Tworzenie przenośnych skryptów powłoki”. • Receptura 19.11, „Niestandardowe zachowanie instrukcji printf”. • Punkt „Instrukcja printf” w dodatku A.

368 |

Rozdział 15. Zaawansowane mechanizmy skryptowe

15.7. Dzielenie danych wyjściowych tylko wtedy, gdy jest to konieczne Problem Chcemy podzielić listing wynikowy tylko wtedy, gdy ilość danych wejściowych przekracza pewną wartość. Rzecz w tym, że polecenie split zawsze tworzy przynajmniej jeden nowy plik.

Rozwiązanie # plik receptury: func_split #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Podzał danych wejściowych na bloki o jednakowym rozmiarze - TYLKO jeśli # ilość danych wejściowych przekracza określoną wartość # Wywołanie: Split # np. Split $dane ${dane}_ --lines 100 # Informacje na temat opcji są zamieszczone w podręczniku systemowym # dla instrukcji split i wc function Split { local file=$1 local prefix=$2 local limit_type=$3 local limit_size=$4 local wc_option # Sprawdzenie poprawności parametrów if [ -z "$file" ]; then printf "%b" "Split: brak nazwy pliku! \n" return 1 fi if [ -z "$prefix" ]; then printf "%b" "Split: brak prefiksu pliku wyjściowego! \n" return 1 fi if [ -z "$limit_type" ]; then printf "%b" "Split: brak opcji limitu (np. --lines), zobacz 'man split'! \n" return 1 fi if [ -z "$limit_size" ]; then printf "%b" "Split: brak wielkości limitu (np. 100), zobacz 'man split'! \n" return 1 fi # Przekształcenie opcji polecenia split w opcje instrukcji wc # W niektórych systemach pewne opcje instrukcji wc i split nie są obsługiwane case $limit_type in -b|--bytes) wc_option='-c';; -C|--line-bytes) wc_option='-L';; -l|--lines) wc_option='-l';; esac # Jeżeli którykolwiek z limitów został przekroczony if [ "$(wc $wc_option $file | awk '{print $1}')" -gt $limit_size ]; then

15.7. Dzielenie danych wyjściowych tylko wtedy, gdy jest to konieczne

| 369

# wykonanie zasadniczego zadania split --verbose $limit_type $limit_size $file $prefix fi } # koniec funkcji Split

Analiza W niektórych systemach pewne opcje (np. –C) poleceń split i wc mogą być niedostępne.

Zobacz również • Receptura 8.13, „Zliczanie wierszy, słów i znaków pliku”.

15.8. Przeglądanie danych wynikowych w formacie szesnastkowym Problem Chcemy sprawdzić, czy w treści zestawienia wynikowego występują znaki odstępu lub znaki niedrukowalne. W tym celu listing musi zostać wyświetlony w formacie szesnastkowym.

Rozwiązanie Zestawienie wynikowe powinno zostać przekazane za pomocą potoku do polecenia hexdump, wywoływanego z opcją –C: $ hexdump 00000000 00000010 00000020 0000002a

-C 57 20 69

plik 69 65 72 73 7a 20 31 32 2e 0a 0a 57 69 65 65 72 73 7a 20 35 2e

2e 0a 57 69 65 72 73 7a 72 73 7a 20 34 2e 0a 57 0a 0a

|Wiersz 1..Wiersz| | 2...Wiersz 4..W| |iersz 5...|

Na przykład wykonanie polecenia nl powoduje dodanie znaków spacji (kod ASCII 20), numeru wiersza oraz znaku tabulatora (kod ASCII 9): $ nl -ba plik | hexdump -C 00000000 20 20 20 20 20 31 00000010 0a 20 20 20 20 20 00000020 2e 0a 20 20 20 20 00000030 09 57 69 65 72 73 00000040 35 09 57 69 65 72 00000050 20 36 09 0a 00000054

09 32 20 7a 73

57 09 33 20 7a

69 57 09 34 20

65 69 0a 2e 35

72 65 20 0a 2e

73 72 20 20 0a

7a 73 20 20 20

20 7a 20 20 20

31 20 20 20 20

2e 32 34 20 20

| 1.Wiersz 1.| |. 2.Wiersz 2| |.. 3.. 4| |.Wiersz 4.. | |5.Wiersz 5.. | | 6..|

Analiza Program hexdump jest narzędziem systemu BSD, które jest dołączane do wielu dystrybucji Linuksa. W innych systemach, przede wszystkim na platformie Solaris, instrukcja ta nie jest dostępna. Można wówczas zastosować polecenie od, które generuje listing w formacie ósemkowym. Niestety jest on znacznie trudniejszy do przeanalizowania:

370

|

Rozdział 15. Zaawansowane mechanizmy skryptowe

$ nl -ba plik | od -x 0000000 2020 2020 3120 0000020 200a 2020 2020 0000040 0a2e 2020 2020 0000060 5709 6569 7372 0000100 0935 6957 7265 0000120 3620 0a09 0000124 $ nl -ba plik 0000000 20 20 0000020 0a 20 0000040 2e 0a 0000060 09 57 0000100 35 09 0000120 20 36 0000124

5709 0932 3320 207a 7a73

| od -tx1 20 20 20 31 20 20 20 20 20 20 20 20 69 65 72 73 57 69 65 72 09 0a

09 32 20 7a 73

6569 6957 0a09 2e34 3520

57 09 33 20 7a

7372 7265 2020 200a 0a2e

69 57 09 34 20

65 69 0a 2e 35

207a 7a73 2020 2020 2020

72 65 20 0a 2e

73 72 20 20 0a

2e31 3220 3420 2020 2020

7a 73 20 20 20

20 7a 20 20 20

31 20 20 20 20

2e 32 34 20 20

Można się również posłużyć skryptem języka Perl, który został udostępniony na stronie http:// ´www.khngai.com/perl/bin/hexdump.txt: $ ./hexdump.pl plik /0 /1 0000 : 57 69 0010 : 20 32 0020 : 69 65

/2 65 2E 72

/3 72 0A 73

/4 73 0A 7A

/5 7A 57 20

/6 20 69 35

/7 31 65 2E

/8 2E 72 0A

/9/ A /B /C /D /E /F 0A 57 69 65 72 73 7A 73 7A 20 34 2E 0A 57 0A

0123456789ABCDEF Wiersz 1..Wiersz 2...Wiersz 4..W iersz 5...

Zobacz również • Polecenie man hexdump. • Polecenie man od. • http://www.khngai.com/perl/bin/hexdump.txt. • http://gnuwin32.sourceforge.net/packages/hextools.htm. • Punkt „Tabela wartości ASCII” w dodatku A.

15.9. Wykorzystanie mechanizmów przekierowania sieciowego Problem Chcemy przesłać dane przez sieć lub pobrać informacje z sieci, ale nie dysponujemy narzędziem takim jak netcat.

Rozwiązanie Jeśli w systemie jest zainstalowany interpreter bash w wersji 2.04 lub późniejszej, który został skompilowany z opcją --enable-net-redirections (opcja ta nie jest włączana w systemach Debian i pochodnych), do wykonania zadania wystarczy sama powłoka. Przedstawiony poniżej przykład został wykorzystany również w recepturze 15.10, „Ustalenie własnych adresów IP”.

15.9. Wykorzystanie mechanizmów przekierowania sieciowego

|

371

$ exec 3 /dev/tcp/www.ippages.com/80 $ echo -e "GET /simple/?se=1 HTTP/1.0\n" >&3 $ cat /dev/udp/serwer.przyklad.pl/514

Z uwagi na bezpołączeniowy charakter protokołu UDP jego wykorzystanie jest znacznie łatwiejsze niż w przypadku stosowanego wcześniej protokołu TCP. Wartość odpowiada priorytetowi komunikatu syslog o wartości local0.notice (zgodnie z zaleceniem RFC 3164). Szczegółowe informacje na ten temat są zawarte w części 4.1.1 PRI Part dokumentu RFC oraz na stronie podręcznika systemowego, odnoszącej się do polecenia logger. Zmienna $0 przechowuje nazwę bieżącego procesu, a zmienna $$ jego identyfikator. W przypadku powłoki logowania nazwą jest –bash.

Zobacz również • Polecenie man logger. • Dokument RFC 3164 „The BSD Syslog Protocol” — http://www.faqs.org/rfcs/rfc3164.html. • Receptura 15.10, „Ustalenie własnych adresów IP”. 372

|

Rozdział 15. Zaawansowane mechanizmy skryptowe

• Receptura 15.12, „Przekierowanie wyjścia na czas działania skryptu”. • Receptura 15.14, „Wysyłanie komunikatów syslog z poziomu skryptu”. • Dodatek B, w szczególności plik ./functions/gethtml.

15.10. Ustalenie własnych adresów IP Problem Chcemy poznać adresy IP własnego komputera.

Rozwiązanie Nie istnieje jedno uniwersalne rozwiązanie tego problemu, które prowadziłoby do uzyskania spodziewanych rezultatów w każdym systemie operacyjnym. Z tego względu w dalszej części receptury zostanie omówionych kilka różnych technik umożliwiających wykonanie tego zadania. Pierwsza metoda polega na przeanalizowaniu wyniku polecenia ifconfig i wyodrębnieniu z listingu wynikowego ciągów adresów IP. Zamieszczony poniżej skrypt zwraca pierwszy adres IP, który nie jest adresem pętli zwrotnej, lub nie zwraca niczego, jeśli żaden z interfejsów nie został skonfigurowany bądź uruchomiony. # plik receptury: finding_ipas # Wyodrębnienie adresu IPv4 za pomocą poleceń awk, cut i head /sbin/ifconfig -a | awk '/(cast)/ { print $2 }' | cut -d':' -f2 | head -1 # Wyodrębnienie adresu IPv4 za pomocą instrukcji języka Perl (dla zabawy) /sbin/ifconfig -a | perl -ne 'if ( m/^\s*inet (?:addr:)?([\d.]+).*?cast/ ) { print qq($1\n); exit 0; }' # Wyodrębnienie adresu IPv6 za pomocą poleceń awk, cut i head /sbin/ifconfig -a | egrep 'inet6 addr: |address: ' | cut -d':' -f2- | cut -d'/' -f1 | head -1 | tr -d ' ' # Wyodrębnienie adresu IPv6 za pomocą instrukcji języka Perl (dla zabawy) /sbin/ifconfig -a | perl -ne 'if ( m/^\s*(?:inet6)? \s*addr(?:ess)?: ([0-9A-Fa-f:]+)/ ) { print qq($1\n); exit 0; }'

Drugi sposób sprowadza się do ustalenia nazwy domenowej jednostki i odwzorowaniu jej na adres IP. To rozwiązanie często jest jednak niewiarygodne, ponieważ dzisiejsze systemy (szczególnie stacje robocze) posługują się niekompletnymi lub niepoprawnymi nazwami domenowymi, a ponadto mogą pracować w sieciach z adresacją dynamiczną, w których nie ma odpowiednich mechanizmów odwrotnego odwzorowania DNS. $ host $(hostname)

Trzecie rozwiązanie pozwala na ustalenie zewnętrznego routowalnego adresu IP, a nie wewnętrznego adresu jednostki (adresu zgodnego z zaleceniem RFC 1918). Wymaga jednak wykorzystania zewnętrznego systemu, takiego jak http://www.ippages.com/ lub FollowMeIP, który pozwoli na ustalenie adresu firewalla lub urządzenia realizującego translację adresów (NAT). Niestety, w systemach innych niż Linux narzędzia najbardziej nadające się do tego celu, takie jak wget,

15.10. Ustalenie własnych adresów IP

|

373

zazwyczaj nie są instalowane automatycznie. Można by w tym przypadku zastosować również aplikacje lynx lub curl, ale również one nie zawsze są domyślnie uwzględniane (choć aplikacja curl jest dostępna w systemie Mac OS X 10.4). Sposób pozyskania informacji o adresie IP został przedstawiony poniżej. Wymaga jednak zastosowania dodatkowej instrukcji, która umożliwi wyodrębnienie poszukiwanej wartości z listingu wynikowego. $ wget -qO - http://www.ippages.com/simple/ 213.XXX.XXX.94 (PL-Poland) http://www.ippages.com Thu, 01 Nov 2007 08:21:48 UTC/GMT (4 of 149 allowed today) alternate access in XML format at: http://www.ippages.com/xml alternate access via SOAP at: http://www.ippages.com/soap/server.php alternate access via RSS feed at: http://www.ippages.com/rss.php alternate access in VoiceXML format at: http://www.ippages.com/voicexml $ wget -qO - http://www.ippages.com/simple/?se=1 213.XXX.XXX.94 (PL-Poland) http://www.ippages.com Thu, 01 Nov 2007 08:24:22 UTC/GMT $ wget -qO - http://www.ippages.com/simple/?se=1 | cut -d' ' -f1 213.XXX.XXX.94 $ lynx -dump http://www.ippages.com/simple/?se=1 | cut -d' ' -f1 213.XXX.XXX.94 $ curl -s http://www.ippages.com/simple/?se=1 | cut -d' ' -f1 213.XXX.XXX.94

Jeżeli żaden z wymienionych programów nie został zainstalowany w systemie, ale jest dostępna powłoka bash w wersji 2.04 lub późniejszej z włączoną opcją --enable-net-redirections (opcja ta nie jest włączana podczas kompilacji powłoki dla systemów Debian i pochodnych), do wykonania zadania można wykorzystać samą powłokę. Szczegółowo zagadnienie to zostało opisane w recepturze 15.9, „Wykorzystanie mechanizmów przekierowania sieciowego”. $ exec 3 /dev/tcp/www.ippages.com/80 $ echo -e "GET /simple/?se=1 HTTP/1.0\n" >&3 $ cat &3 $ egrep '^[0-9.]+ ' &1 | egrep '[^0-9]+' 213.XXX.XXX.94 # Zastosowanie mechanizmu powłoki (łatwiejsze, jeśli działa poprawnie) $ exec 3 /dev/tcp/ipserver.fmip.org/42750 && cat &2; exit 1; } if [ $result = 1 ]; then echo "Plik $REMOTE_FILE jest dostępny w systemie $REMOTE_HOST" else echo "Plik $REMOTE_FILE nie jest dostępny w systemie $REMOTE_HOST" fi

Analiza Przedstawiony kod odpowiada za kilka bardzo interesujących operacji. Na szczególną uwagę zasługuje tutaj sposób użycia zmiennych $SSH_USER i $SSH_ID. Są one istotne dla działania skryptu tylko wówczas, gdy przechowują jakąkolwiek wartość. W przeciwnym przypadku zostają zastąpione pustym zbiorem znaków, czyli są ignorowane. Dzięki temu możemy uniezależnić kod od konkretnych parametrów połączenia, co z kolei pozwala na zdefiniowanie wartości w pliku konfiguracyjnym bądź przekazanie ich do funkcji lub wykonanie jednej i drugiej operacji jednocześnie. # Efektywna treść wiersza zawierającego wszystkie wartości: ssh –i ~/.ssh/cos.id [email protected] [...] # Bez wartości: ssh host.przyklad.pl [...]

Samo polecenie dla połączenia SSH zostało zdefiniowane w taki sposób, aby wynikiem zawsze była wartość 0 lub 1. Wynik ten jest zapisywany w zmiennej $result, co gwarantuje, że nie będzie ona pusta. Jest to jedna z metod upewnienia się, że polecenie zostało wykonane (więcej informacji na ten temat znajduje się w recepturze 4.2, „Sprawdzenie, czy polecenie zostało wykonane poprawnie”). Jeżeli zmienna $result przechowuje wartość pustą, grupa poleceń otoczonych znakami {} zapewnia wyświetlenie stosownego komunikatu oraz przerwanie skryptu. Ponieważ jednak polecenie połączenia SSH zawsze zwraca jakąś wartość, musi ona zostać sprawdzona — nie można po prostu zastosować instrukcji if [ $result ]; then. Gdyby w skrypcie nie zostały użyte znaki {}, komunikat o błędzie byłby co prawda wyświetlany tylko w przypadku, w którym zmienna $result jest pusta, ale skrypt kończyłby się za każdym razem, niezależnie od wartości zmiennej. Gdyby zamiast bloku {} została użyta podpowłoka (), działanie byłoby niezgodne z założeniami. Instrukcja exit 1 powodowałaby zakończenie pracy podpowłoki, a nie skryptu. Działanie skryptu byłoby wówczas kontynuowane nawet po nieudanym wykonaniu polecenia SSH — sam kod mógłby się jednak wydawać poprawny, a odszukanie błędu wymagałoby poświęcenia znacznej ilości czasu na debugowanie. Ostatnią instrukcję warunkową można również zapisać w sposób pokazany poniżej. Rodzaj wykorzystanego zapisu zależy od preferencji programisty oraz liczby instrukcji do wykonania w każdej gałęzi kodu. W tym przypadku to, która składania zostanie zastosowana, nie ma żadnego znaczenia dla działania skryptu. [ $result=1 ] && echo "Plik $REMOTE_FILE jest dostępny w systemie $REMOTE_HOST" \ || echo "Plik $REMOTE_FILE nie jest dostępny w systemie $REMOTE_HOST"

Sama treść skryptu została zapisana w taki sposób, aby żaden z wierszy nie był zbyt długi i żeby można go było łatwo przeanalizować.

378

|

Rozdział 15. Zaawansowane mechanizmy skryptowe

Zobacz również • Receptura 2.14, „Zapisywanie i grupowanie danych wyjściowych większej liczby poleceń”. • Receptura 4.2, „Sprawdzenie, czy polecenie zostało wykonane poprawnie”. • Receptura 14.21, „Wykorzystanie usługi SSH bez hasła”. • Receptura 17.18, „Filtrowanie wyniku polecenia ps za pomocą instrukcji grep, ale z pomi-

nięciem w zestawieniu samej instrukcji grep”. • Receptura 17.19, „Sprawdzenie, czy dany proces działa”.

15.12. Przekierowanie wyjścia na czas działania skryptu Problem Chcemy przekierować strumień danych wyjściowych dla całego skryptu bez konieczności modyfikowania każdej instrukcji echo lub printf.

Rozwiązanie Aby przekierować dane strumienia STDOUT i STDERR, można wykorzystać mało znaną instrukcję exec. # Zachowanie "starego" deskryptora STDERR – opcjonalne exec 3>&2 # Skierowanie wyszystkich danych STDERR do pliku dziennika exec 2> /ścieżka/do/pliku.log # Poniżej można umieścić treść skryptu, którego strumień STDERR został globalnie # przekierowany do pliku # Wyłączenie przekierowania – przywrócenie standardowego strumienia STDERR i zamknięcie # deskryptora pliku 3 exec 2>&3-

Analiza Polecenie exec zazwyczaj zastępuje działającą powłokę poleceniem podanym jako parametr — przerywając działanie pierwotnej powłoki. Jeżeli jednak nie zostanie zdefiniowane żadne polecenie, instrukcja exec umożliwia zmianę zasad przekierowywania danych. Jej działanie obejmuje nie tylko strumienie STDOUT i STDERR, choć to one najczęściej podlegają przekierowaniu.

Zobacz również • Polecenie help exec. • Receptura 15.9, „Wykorzystanie mechanizmów przekierowania sieciowego”.

15.12. Przekierowanie wyjścia na czas działania skryptu

|

379

15.13. Eliminacja błędów typu „argument list too long” Problem Podczas wykonywania zadania wymagającego podmiany symboli wieloznacznych na ekranie zostaje wyświetlony komunikat argument list too long (zbyt długa lista parametrów).

Rozwiązanie Aby podzielić listę parametrów, wystarczy zastosować instrukcję xargs (często współdziałającą z poleceniem find). Na przykład zamiast polecenia ls można użyć pętli for lub instrukcji find. $ ls /ścieżka/do/katalogu/z/wieloma/plikami/*z* bin/bash: /bin/ls: Argument list too long # Skrócona lista – znaki ~ są wykorzystane tylko do demonstracji działania mechanizmu $ for i in ./pliki/Pliki/*z*; do echo "~$i~"; done ~./pliki/Plik ze wstawionym znakiem nowego wiersza w nazwie~ ~./pliki/Plik ze znakami nawiasu [kwadratowego] w nazwie~ ~./pliki/Plik ze znakami nawiasu (okrągłego) w nazwie~ ~./pliki/Plik ze znakami spacji w nazwie~ ~./pliki/Plik ze znakiem | spacji w nazwie~ ~./pliki/Plik ze znakiem ; w nazwie~ ~./pliki/Plik ze znakiem : w nazwie~ ~./pliki/Plik ze znakiem = w nazwie~ ~./pliki/Plik ze znakiem | w nazwie~ ~./zwykły_plik~ $ find ./pliki -name "*z*" -exec echo ~{}~ \; ~./pliki/Plik ze znakiem ; w nazwie~ ~./pliki/Plik ze znakami spacji w nazwie~ ~./pliki/Plik ze znakiem | spacji w nazwie~ ~./pliki/Plik ze znakami nawiasu [kwadratowego] w nazwie~ ~./pliki/Plik ze znakiem | w nazwie~ ~./zwykły_plik~ ~./pliki/Plik ze znakami nawiasu (okrągłego) w nazwie~ ~./pliki/Plik ze wstawionym znakiem nowego wiersza w nazwie~ ~./pliki/Plik ze znakiem : w nazwie~ ~./pliki/Plik ze znakiem = w nazwie~ $ for i in /ścieżka/do/katalogu/z/wieloma/plikami/*z*; do echo "$i"; done [Kod działa, ale listing wynikowy jest zbyt długi, by mógł zostać zamieszczony] $ find /ścieżka/do/katalogu/z/wieloma/plikami –name '*z*' [Kod działa, ale listing wynikowy jest zbyt długi, by mógł zostać zamieszczony]

Przedstawione powyżej rozwiązania działają poprawnie z instrukcją echo. Jednak przekazanie zmiennej "$i" do innego programu, a szczególnie uwzględnienie jej w konstrukcjach programistycznych powłoki, może spowodować błędy wynikające z wykorzystania zmiennej $IFS lub innych parserów danych wejściowych. Problem ten można zminimalizować, stosując instrukcje find –print0 lub xargs -0. Dodana do polecenia find opcja nakazuje temu programowi wykorzystać jako separator pól danych wyjściowych znak null zamiast znaku odstępu (znak null nie 380 |

Rozdział 15. Zaawansowane mechanizmy skryptowe

może występować w nazwie pliku). Z kolei opcja -0 instrukcji xargs definiuje znak null jako separator pól danych wejściowych. Dzięki temu nazwy zawierające nietypowe znaki są poprawnie obsługiwane. $ find /ścieżka/do/katalogu/z/wieloma/plikami –name '*z*' –print0 | xargs -0 program

Analiza Domyślny sposób działania powłoki bash (oraz powłoki sh) zakłada zwrócenie niezmienionego wzorca w przypadku niedopasowania do niego żadnej nazwy. Oznacza to, że pętla for może przypisać zmiennej $i wartość ./pliki/*z*, jeśli żaden z plików nie spełni kryteriów dopasowania tak zdefiniowanego wzorca. Aby zastąpić niedopasowany wzorzec pustym ciągiem tekstowym (zamiast zwrócenia samej treści wzorca), wystarczy wykonać instrukcję shopt –s null ´glob (włączającą opcję nullglob). Analizując przykładowy kod, można by przypuszczać, że podczas realizowania instrukcji pętli for wystąpi ten sam problem, z którym mamy do czynienia w przypadku polecenia ls. Taka sytuacja nie ma jednak miejsca. Chet Ramey wyjaśnia to w następujący sposób: Wartość ARG_MAX wyznacza całkowity rozmiar pamięci dla wywołań systemowych z grupy exec*. Dzięki niej jądro otrzymuje informację o tym, jaki jest maksymalny rozmiar alokowanego bufora. W wywołaniu execve występują trzy parametry — nazwa programu, wektor parametrów i dane środowiskowe. Polecenie ls kończy się z błędem ponieważ całkowita liczba bajtów zajętych przez parametry wywołania execve przekracza wartość ARG_MAX. Pętla for jest wykonywana poprawnie, ponieważ cała operacja jest wykonywana wewnętrznie — cała lista jest generowana i przechowywana wewnętrznie, a wywołanie execve w ogóle nie następuje.

Stosując opisane rozwiązanie, trzeba pamiętać o tym, że działanie instrukcji find ma charakter rekurencyjny w przeciwieństwie do polecenia ls. Oznacza to, że instrukcja find może zwrócić znacznie więcej nazw plików. Niektóre wersje polecenia udostępniają użytkownikowi opcję –d, która pozwala na określenie „głębokości” przeszukiwania. Zastosowanie pętli for wydaje się więc lepszym rozwiązaniem. Aby poznać wartość limitu nakładanego przez dany system operacyjny, wystarczy wykonać instrukcję getconf ARG_MAX. Wartość ta zmienia się w dużym zakresie w różnych systemach (wynik polecenia getconf ARG_MAX z różnych systemów operacyjnych został zamieszczony w tabeli 15.1). Tabela 15.1. Ograniczenia systemowe System

Ograniczenie ARG_MAX (w bajtach)

HP-UX 11

2048000

Solaris (8, 9, 10)

1 048320

NetBSD 2.0.2, OpenBSD 3.7, OS/X

262144

Linux (Red Hat, Debian, Ubuntu)

131072

FreeBSD 5.4

65536

15.13. Eliminacja błędów typu „argument list too long”

|

381

Zobacz również • http://www.gnu.org/software/coreutils/faq/coreutils-faq.html#Argument-list-too-long. • Receptura 9.2, „Przetwarzanie nazw plików zawierających niestandardowe znaki”.

15.14. Wysyłanie komunikatów syslog z poziomu skryptu Problem Chcemy umożliwić skryptowi przekazywanie komunikatów do systemu syslog.

Rozwiązanie Należy wykorzystać polecenie logger, program Netcat lub wbudowaną opcję przekierowania sieciowego powłoki bash. Polecenie logger jest dołączane domyślnie do większości systemów i pozwala na łatwe przekazywanie komunikatów do lokalnej usługi syslog. Nie umożliwia jednak przesyłania informacji do jednostek zdalnych. Jeśli funkcja ta jest niezbędna, trzeba wykorzystać program Netcat lub stosowne mechanizmy powłoki bash. $ logger –p local0.notice –t $0[$$] komunikat testowy

Program Netcat znany jest ze swojej uniwersalności, ale niestety zazwyczaj nie jest domyślnie instalowany. Ponadto często obowiązująca w danej firmie polityka bezpieczeństwa nie dopuszcza stosowania narzędzi hakerskich, za które może być uznawany również program Netcat. Nic jednak nie stoi na przeszkodzie, aby do realizacji zadania została wykorzystana powłoka bash. Szczegółowe omówienie kodu $0[$$] zostało zamieszczone w recepturze 15.9, „Wykorzystanie mechanizmów przekierowania sieciowego”. # Netcat $ echo "$0[$$]: Komunikat testowy z programu Netcat" | nc –w1 –u loghost 514 # bash $ echo "$0[$$]: Komunikat testowy z powłoki bash" \ > /dev/udp/serwer.przyklad.pl/514

Analiza Programy logger i Netcat mają znacznie więcej funkcji niż opisane w tej recepturze. Szczegółowe informacje na ich temat są dostępne w podręczniku systemowym.

Zobacz również • Polecenie man logger. • Polecenie man nc. • Receptura 15.9, „Wykorzystanie mechanizmów przekierowania sieciowego”. 382

|

Rozdział 15. Zaawansowane mechanizmy skryptowe

15.15. Wysyłanie wiadomości e-mail ze skryptu Problem Chcemy, aby skrypt wysyłał wiadomości e-mail z ewentualnymi załącznikami.

Rozwiązanie Rozwiązania opisywane w dalszej części receptury zależą od rodzaju zainstalowanych programów pocztowych (mail, mailer lub mailto), aplikacji przekazywania poczty (MTA — Message Transfer Agent) oraz od konfiguracji środowiska e-mail. Z tego względu wszystkie przedstawione techniki trzeba dokładnie przetestować w konkretnym systemie, w którym będą implementowane. Pierwszy sposób na przesłanie wiadomości e-mail polega na wygenerowaniu stosownego komunikatu i dostarczeniu go do programu pocztowego zgodnie z poniższymi instrukcjami: # Sam komunikat $ cat treść_wiadomości | mail –s "Temat wiadomości" [email protected] [email protected]

lub # Tylko załącznik $ uuencode /ścieżka/do/pliku/załącznika nazwa_załącznika | mail –s "Temat wiadomości" [email protected] [email protected]

lub # Załącznik i komunikat $ (cat treść_wiadomości; uuencode /ścieżka/do/pliku/załącznika nazwa_załącznika) | mail –s "Temat wiadomości" [email protected] [email protected]

W praktyce nie zawsze jest to takie łatwe. O ile narzędzie uuencode prawdopodobnie jest instalowane w każdym systemie, program mail i jemu podobne nie zawsze są dostępne. Poza tym ich funkcje mogą się różnić. Często również polecenia mail i mailx są w istocie tym samym programem udostępnianym za pośrednictwem dowiązań symbolicznych lub dowiązań „twardych”. W komercyjnych aplikacjach niezbędne jest więc zastosowanie pewnego rozwiązania abstrakcyjnego, które zagwarantuje jego przenośność. Omawiany problem uwidacznia się chociażby w przypadku użycia polecenia mail, które znajduje zastosowanie w systemach Linux i BSD. Jednak w systemach Solaris musi być zastąpione instrukcją xmail, ponieważ program mail nie ma opcji –s. Narzędzie xmail jest również dostępne w niektórych dystrybucjach systemu Linux (np. w systemie Debian), ale nie we wszystkich (np. w systemie Red Hat). Wybór programu pocztowego powinien być więc uzależniony od wyniku wykonania instrukcji uname –o, czyli od informacji o rodzaju środowiska systemowego. # plik receptury: email_sample # Definicja niektórych parametrów wysyłania poczty. Wykorzystanie w instrukcji # case wyniku polecenia uname lub hostname pozwala na dostosowanie parametrów # do określonego systemu. case $HOSTNAME in *.firma.pl ) MAILER='mail' ;; # Linux i BSD host1.* ) MAILER='mailx' ;; # Solaris, BSD i niektóre systemy Linux host2.* ) MAILER='mailto' ;; # Użyteczne narzędzie, jeśli jest # zainstalowne

15.15. Wysyłanie wiadomości e-mail ze skryptu

| 383

esac RECIPIENTS='[email protected] [email protected]' SUBJECT="Dane z $0" [...] # Przygotowanie treści wiadomości z wykorzystaniem zewnętrznego pliku, # zmiennych i instrukcji echo lub printf bądź osadzonych dokumentów. # Jeśli jest to konieczne, można zmodyfikować wartość zmiennych # $SUBJECT i (lub) $RECEPIENTS. [...] ( echo $email_body ; uuencode $attachment $(basename $attachment) ) \ | $MAILER -s "$SUBJECT" "$RECIPIENTS"

Powodzenie dostarczania załączników w sposób przedstawiony powyżej zależy w dużym stopniu od rodzaju aplikacji klienckiej, która zostanie użyta do odczytania widomości. Większość nowoczesnych programów pocztowych, takich jak Thunderbird (i Outlook), automatycznie wykrywa blok danych zakodowanych za pomocą programu uuencode i udostępnia go w formie załącznika. Niestety, niektóre aplikacje mogą działać w inny sposób. Oczywiście, zawsze można zapisać wiadomość na dysku i poddać ją działaniu polecenia uudecode (instrukcja ta jest dostatecznie „inteligentna”, aby pominąć część tekstową i wyodrębnić jedynie część załącznika), ale wymaga to dodatkowych operacji. Inny sposób przesłania wiadomości ze skryptu polega na przekazaniu tego zadania mechanizmowi cron. Choć poszczególne funkcje mechanizmu cron są różnie implementowane w różnych systemach, jedna jest wspólna dla wszystkich wersji programu — każda forma listingu wygenerowanego przez polecenia wykonywane w ramach mechanizmu cron są dostarczane na adres zdefiniowany w zmiennej MAILTO. Nic więc nie stoi na przeszkodzie, aby wykorzystać ten fakt i posłużyć się działającym mechanizmem dostarczania poczty (oczywiście, jeśli działa poprawnie). Właściwie zaprojektowany skrypt pracujący pod kontrolą mechanizmu cron (a wiele osób twierdzi, że również każdy skrypt lub program systemu Unix) nie powinien generować żadnych danych wynikowych, jeśli podczas jego pracy nie wystąpi błąd. Jeśli użytkownik życzy sobie dostarczenia informacji zwrotnych, powinien mieć możliwość dodania opcji –v, która spowoduje włączenie funkcji wyświetlania komunikatów. W tym trybie nie należy jednak uruchamiać narzędzi działających pod kontrolą systemu cron. Wynika to z zasady działania mechanizmu cron, który wysyła za pomocą poczty elektronicznej wszystkie listingi wynikowe. Jeżeli każde uruchomienie skryptu będzie się kończyło przesłaniem listu e-mail, ich odbiorca szybko zacznie je ignorować. Właściwe rozwiązanie polega więc na przygotowaniu skryptu, który nie generuje żadnych komunikatów w przypadku bezbłędnego wykonania zadania i przekazuje informacje ostrzegawcze wówczas, gdy nie może poprawnie zakończyć pracy.

Analiza Narzędzie mailto zostało opracowane z myślą o uzupełnieniu polecenia mail o obsługę multimediów oraz dokumentów MIME. Dzięki temu nie wymaga zastosowania instrukcji uuencode podczas przesyłania załączników. Nie jest niestety tak powszechnie stosowane jak programy mail i mailx. Jeżeli żaden z wymienionych programów nie jest dostępny w systemie, można je zastąpić narzędziami elm lub mutt, choć prawdopodobieństwo ich dostępności jest jeszcze mniejsze niż w przypadku programów mail*. Niektóre wersje aplikacji pocztowych pozwalają rów-

384 |

Rozdział 15. Zaawansowane mechanizmy skryptowe

nież na dodanie opcji –r odpowiedzialnej za dołączenie adresu zwrotnego. Program mutt udostępnia dodatkowo opcję –a, która znacznie ułatwia przesyłanie załączników. cat "$tresc_komunikatu" | mutt –s "$temat" –a "$plik_zalacznika" "$odbiorcy"

Kolejnym wartym uwagi programem jest narzędzie mpack. Niestety, prawdopodobieństwo, że będzie w systemie zainstalowane domyślnie, jest bardzo małe. Warto więc poszukać go w repozytorium systemowym lub pobrać kod źródłowy spod adresu ftp://ftp.andrew.cmu.edu/pub/ ´mpack. Poniżej został przytoczony fragment opisu programu zamieszczony w podręczniku systemowym: Program mpack jest narzędziem, które przekształca określony plik w komunikat MIME lub większą liczbę komunikatów MIME. Wiadomość wynikowa jest przesyłana do odbiorcy lub odbiorców zdefiniowanych w pliku lub zbiorze plików bądź publikowana na listach dyskusyjnych.

Inny sposób na wykorzystanie różnych programów pocztowych przechowywany w różnych katalogach systemowych został opisany w rozdziale 8. książki Nelsona H.F. Beebe’a i Arnolda Robbinsa Programowanie skryptów powłoki (Helion 2005). # plik receptury: email_sample_css # Kod zaczerpnięty z rozdziału 8. książki Programowanie skryptów powłoki for MAIL in /bin/mailx /usr/bin/mailx /usr/sbin/mailx /usr/ucb/mailx /bin/mail /usr/bin/mail; do [ -x $MAIL ] && break done [ -x $MAIL ] || { echo 'Nie można znaleźć programu pocztowego!' >&2; exit 1; }

Program uuencode jest starym narzędziem przekształcania danych binarnych w tekst ASCII, które było wykorzystywane podczas transmisji danych w połączeniach nieobsługujących binarnego formatu informacji, czyli w połączeniach większości sieci rozległych, zanim stały się one Internetem i siecią WWW. Nieliczne łącza tego typu są wykorzystywane do dzisiaj. Jednak nawet gdyby nie były wykorzystywane, mechanizm uuencode i tak znalazłby zastosowanie w przekształcaniu binarnych załączników w treść ASCII. Format ten jest bowiem poprawnie interpretowany przez nowoczesne aplikacje pocztowe. Warto również zapoznać się ze sposobem działania poleceń uudecode oraz mimencode. Pliki zakodowane za pomocą instrukcji uuencode są o jedną trzecią większe od ich binarnych odpowiedników. Dlatego często przed zakodowaniem są poddawane kompresji. Zagadnienia dostarczania poczty — poza różnymi aplikacjami e-mail (MUA — Mail User Agent), takimi jak mail i mailx — komplikuje również konieczność współdziałania wielu komponentów systemu zaangażowanych w transport listów. Nie bez znaczenia jest tu problem spamu. Wielu administratorów zabezpiecza serwery poczty tak dokładnie, że ograniczenia w transmisji danych obejmują także informacje generowane przez skrypty. Jedyne rozwiązanie w takim przypadku polega na dokładnym przetestowaniu własnej aplikacji i ewentualnym omówieniu zagadnienia z administratorem serwera pocztowego. Czasami twórcy skryptów spotykają się z jeszcze innym problemem. Niektóre dystrybucje systemów Linux ukierunkowane na pracę w charakterze stacji roboczej (np. Ubuntu) nie uwzględniają w domyślnej instalacji aplikacji MTA zakładając, że użytkownik będzie korzystał z graficznego klienta poczty, takiego jak Evolution czy Thunderbird. W takich przypadkach rozwiązania bazujące na tekstowych programach MUA lub mechanizmie cron nie będą działały poprawnie. Właściwych rozwiązań trzeba wówczas szukać na stronach grup dyskusyjnych związanych z daną dystrybucją systemu.

15.15. Wysyłanie wiadomości e-mail ze skryptu

| 385

Zobacz również • Polecenie man mail. • Polecenie man mailx. • Polecenie man mailto. • Polecenie man mutt. • Polecenie man uuencode. • Polecenie man cron. • Polecenie man 5 crontab.

15.16. Automatyzacja zadań z wykorzystaniem podziału procesu na etapy Problem Określone zadanie lub proces muszą zostać zautomatyzowane, mimo że ich działanie jest długie i wymaga wielu interwencji użytkownika. Chcemy mieć możliwość przywracania działania procesu na różnych etapach wykonywania całej procedury. Dobrym rozwiązaniem byłoby użycie instrukcji GOTO, ale interpreter bash nie obsługuje tego typu polecenia.

Rozwiązanie Dzięki instrukcji case można podzielić skrypt na kilka sekcji, odpowiadających kolejnym etapom pracy. Najpierw zestandaryzujemy odpowiedzi użytkownika: # cookbook filename: func_choice function choice { # Użytkownik może zdecydować o wyborze opcji i wprowadzić wstępnie zdefiniowaną # odpowiedź. Sposób wykorzystania odpowiedzi (w tym odpowiedzi domyślnej) zależy # od instrukcji if-then, umieszczonej w głównej części skryptu local answer printf "%b" "\a" # Dzwonek read -p "$*" answer case "$answer" in [tT1] ) choice='t';; [nN0] ) choice='n';; * ) choice="$answer";; esac } # koniec funkcji choice

Następnie możemy wyznaczyć kolejne etapy pracy skryptu: # plik receptury: using_phases # Pętla główna until [ "$phase" = "Koniec." ]; do

386 |

Rozdział 15. Zaawansowane mechanizmy skryptowe

case $phase in phase0 ) ThisPhase=0 NextPhase="$(( $ThisPhase + 1 ))" echo '############################################' echo "Etap$ThisPhase = Rozpoczęcie kompilacji programu SuperCoś" # Tutaj należy umieścić instrukcje, które odpowiadają za inicjowanie # nowej procedury kompilacji # ... echo "Etap${ThisPhase}=zakończony" phase="etap$NextPhase" ;; # ... phase20 ) ThisPhase=20 NextPhase="$(( $ThisPhase + 1 ))" echo '############################################' echo "Etap$ThisPhase = Zasadnicza część kompilacji programu SuperCoś" # ... choice "[Etap$ThisPhase] Czy chcesz wstrzymać kompilację i coś poprawić? [t/N]:" if [ "$choice" = "t" ]; then echo "Kontynuuacja pracy skryptu: '$MYNAME etap${ThisPhase}'." exit $ThisPhase fi echo "Etap${ThisPhase}=zakończony" phase="etap$NextPhase" ;; # ... * ) echo "Co się dzieje?!? Ten fragment nie powinien być wykonywany!" echo "Spróbuj $0 -h" exit 99 phase="Koniec." ;; esac printf "%b" "\a" done

# Dzwonek

Analiza Ponieważ kod wynikowy nie może być większy niż 255, instrukcja exit $ThisPhase ogranicza liczbę etapów procedury do takiej wartości. W przedstawionym przykładzie wiersz exit 99 wprowadził jeszcze większe ograniczenie, ale to akurat można łatwo zmienić. Szczerze współczujemy osobom, które muszą zdefiniować więcej niż 254 etapy (255 kodów zakończenia) procedury. Warto rozważyć wówczas zastosowanie innego formatu kodów wyników lub połączenie kilku skryptów.

15.16. Automatyzacja zadań z wykorzystaniem podziału procesu na etapy

|

387

Niezbędne z pewnością okaże się również utworzenie procedury, która będzie dostarczała informacji na temat działań podejmowanych na poszczególnych etapach pracy skryptu: Etap0 = Rozpoczęcie kompilacji programu SuperCoś ... Etap20 = Zasadnicza część kompilacji programu SuperCoś ... Etap28 ...

Odpowiedni komunikat można wówczas wyodrębnić za pomocą instrukcji grep 'Etap$This ´Phase' mójskrypt. Niekiedy konieczne okazuje się także zapisanie odpowiednich komunikatów w pliku, w systemie syslog lub w innym dzienniku zdarzeń. Wystarczy wówczas utworzyć funkcję rejestracji zdarzeń (np. logmsg) i wywoływać ją w odpowiedni sposób w kodzie skryptu. Oto przykład tego typu funkcji: function logmsg { # Wyświetlenie na ekranie i zapisanie w dzienniku komunikatu opatrzonego # znacznikiem czasu. # Uwaga: do polecenia tee została dodana opcja –a. printf "%b" "`date '+%Y-%m-%d %H:%M:%S'`: $*"| tee –a $LOGFILE } # koniec funkcji logmsg

Działanie skryptu jest sprzeczne z zasadą niegenerowania komunikatów w przypadku poprawnej realizacji zadania, ale ponieważ z założenia jest rozwiązaniem interaktywnym, odstępstwo to jest akceptowalne.

Zobacz również • Receptura 3.5, „Pobieranie danych od użytkownika”. • Receptura 3.6, „Wprowadzanie odpowiedzi typu tak-nie”. • Receptura 15.14, „Wysyłanie komunikatów syslog z poziomu skryptu”.

388 |

Rozdział 15. Zaawansowane mechanizmy skryptowe

ROZDZIAŁ 16.

Konfiguracja i dostosowanie powłoki bash

Prawdopodobnie nikt z nas nie chciałby pracować w środowisku, w którym nie ma możliwości zmiany ustawień zgodnie z własnymi preferencjami. Trudno sobie wyobrazić niemożność zmiany wielkości znaków lub konieczność wprowadzania długich ścieżek dostępu podczas uruchamiania programów tylko dlatego, że ktoś uznał, że „tak będzie właściwie”. Brak takiej elastyczności w dłuższym czasie byłby nie do zaakceptowania. Użytkownicy środowisk komputerowych spodziewają się, a wręcz wymagają możliwości pełnego dostosowania wykorzystywanych przez nich platform. Interfejs użytkownika nie może być czymś niezmiennym. Na szczęście interpreter bash został zaprojektowany w taki sposób, aby wspomagać działania użytkownika, a nie mu w nich przeszkadzać. Powłoka bash stanowi niezwykle wydajne i elastyczne środowisko pracy. Elastyczność ta w pewnej mierze wynika z możliwości dostosowywania parametrów interfejsu do preferencji użytkownika. Osoby korzystające z systemu Unix lub innych mniej elastycznych środowisk niekiedy nie są nawet świadome możliwości powłoki bash. W tym rozdziale zostaną omówione zagadnienia związane z dostosowywaniem interpretera bash do własnych potrzeb i własnego stylu pracy. Jeżeli ktoś sądzi, że uniksowe polecenie cat ma niedorzeczną nazwę (większość osób niezwiązanych z systemem Unix prawdopodobnie zgodziłaby się z takim stwierdzeniem), może sobie zdefiniować alias, który zmieni nazwę dla instrukcji. Podobnie, jeśli wykorzystuje zawsze tę samą grupę poleceń, może utworzyć dla nich skróty lub nawet aliasy odpowiadające często popełnianym błędom typograficznym (np. „mroe” dla instrukcji more). Nic nie stoi na przeszkodzie, żeby opracować własne polecenia, które będą wykorzystywane w taki sam sposób, jak standardowe instrukcje systemu Unix. Użytkownik może dodatkowo zmienić treść znaku zachęty, uwzględniając w nim istotne informacje (np. nazwę bieżącego katalogu). Możliwe jest również modyfikowanie sposobu działania samej powłoki bash, na przykład przez włączenie opcji nierozróżniania wielkości znaków, dzięki której interpreter będzie poprawnie wykonywał instrukcje zapisane małymi i wielkimi literami. To zadziwiające, jak bardzo można zwiększyć wydajność pracy z systemem przez wprowadzenie kilku niewielkich zmian w pracy powłoki, a szczególnie w działaniu mechanizmu readline. Informacje na temat dostosowywania i konfigurowania powłoki bash są również zawarte w rozdziale 3. książki Camerona Newhama bash. Wprowadzenie (Helion 2006).

389

16.1. Opcje startowe powłoki bash Problem Chcemy poznać opcje startowe powłoki bash, ale polecenie bash --help nie dostarcza użytecznych informacji.

Rozwiązanie Poza instrukcją bash --help można wykorzystać polecenia bash –c "help set" oraz bash –c help, a jeśli powłoka jest uruchomiona, to również help set i help.

Analiza Interpreter bash czasami umożliwia zdefiniowanie tej samej opcji na kilka różnych sposobów, a omawiany problem jest doskonałym tego przykładem. Daną opcję można włączyć podczas uruchamiania powłoki (np. bash –x), a później (w czasie pracy interaktywnej) wyłączyć za pomocą instrukcji set +x.

Zobacz również • Dodatek A. • Receptura 19.12, „Sprawdzanie składni skryptu powłoki”.

16.2. Dostosowanie znaku zachęty Problem Domyślny znak zachęty powłoki bash zazwyczaj nie dostarcza zbyt wielu użytecznych informacji i jest zakończony „nic niemówiącym” znakiem $. Chcemy zmienić treść znaku zachęty w taki sposób, aby uwzględniała istotne dla użytkownika dane.

Rozwiązanie Należy zmodyfikować wartości zmiennych $PS1 i $PS2. Treść domyślnego znaku zachęty jest różna w różnych systemach. Sam interpreter standardowo uwzględnia w nim jedynie informacje o głównej i pobocznej wersji oprogramowania (\s-\v\$) — na przykład bash-3.00$. Dany system operacyjny może jednak narzucić własne ustawienie. Na przykład w dystrybucji Fedora Core 7 wyświetlany jest ciąg [użytkownik@jednostka ~]$ ([\u@\h \W]\$). W dalszej części receptury zostało przestawionych osiem nieskomplikowanych znaków zachęty i trzy bardziej wyrafinowane.

390

|

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

Podstawowe znaki zachęty 1. Nazwa użytkownika, nazwa systemu, data i czas oraz ścieżka do bieżącego katalogu: $ export PS1='[\u@\h \d \A] \w \$ ' [marek@fedora nie lis 04 11:05] ~ $ cd /usr/local/bin [marek@fedora nie lis 04 11:06] /usr/local/bin $

2. Nazwa użytkownika, pełna nazwa systemu, data i czas w formacie ISO 8601 i nazwa bieżącego katalogu (\W):

$ export PS1='[\u@\H \D{%Y-%m-%d %H:%M:%S%z}] \W \$ ' [[email protected] 2007-11-04 11:08:29+0100] ~ $ cd /usr/local/bin [[email protected] 2007-11-04 11:08:41+0100] bin $

3. Nazwa użytkownika, nazwa systemu, wersja oprogramowania bash, ścieżka do bieżącego katalogu (\w):

$ export PS1='[\u@\h \V \w] \$ ' [marek@fedora 3.1.17 ~] $ cd /usr/local/bin/ [marek@fedora 3.1.17 /usr/local/bin] $

4. Znak nowego wiersza, nazwa użytkownika, nazwa systemu, terminal PTY, poziom powłoki,

numer wpisu w historii poleceń, znak nowego wiersza i ścieżka dostępu do bieżącego katalogu ($PWD): $ export PS1='\n[\u@\h \l:$SHLVL:\!]\n$PWD\$ ' [marek@fedora 0:3:11] /home/marek$ cd /usr/local/bin [marek@fedora 0:3:12] /usr/local/bin$

Wartość PTY jest numerem pseudoterminala (w terminologii linuksowej), z którym pracuje użytkownik. Numer ten przydaje się wtedy, gdy pracujemy z wykorzystaniem kilku sesji i próbujemy ustalić, która jest którą. Poziom powłoki to informacja o poziomie zagnieżdżenia podpowłoki w powłokach nadrzędnych. Po pierwszym zalogowaniu poziom powłoki ma wartość 1 i jest zwiększany z chwilą uruchomienia każdego podprocesu (np. screen). Zatem po uruchomieniu procesu screen będzie miał wartość 2.

5. Nazwa użytkownika, kod zakończenia wcześniejszego polecenia oraz ścieżka do bieżącego katalogu. Kod zakończenia zostanie zmieniony (a tym samym stanie się bezużyteczny) po wykonaniu dowolnej instrukcji wiersza poleceń. $ export PS1='[\u@\h $? \w \$ ' [marek@fedora 0 ~ $ cd /usr/local/bin/ [marek@fedora 0 /usr/local/bin $ true [marek@fedora 0 /usr/local/bin $ false [marek@fedora 1 /usr/local/bin $ true [marek@fedora 0 /usr/local/bin $

6. Jednym z ciekawszych zastosowań znaku zachęty jest również wyświetlanie liczby zadań

realizowanych w danej chwili przez powłokę. Może się ono przydać w przypadku uruchomienia większej liczby procesów drugoplanowych, o których nie chcielibyśmy zapomnieć: $ export PS1='\n[\u@\h zadania:\j]\n$PWD\$ ' [marek@fedora zadania:0] /tmp$ ls -lar /etc > /dev/null & [1] 3357 [marek@fedora zadania:1] /tmp$

16.2. Dostosowanie znaku zachęty

|

391

[1]+

Done

ls --color=tty -lar /etc >/dev/null

[marek@fedora zadania:0] /tmp$

7. Wyświetlmy wszystkie wartości naraz — nazwę użytkownika, nazwę systemu, nazwę ter-

minala, poziom powłoki, numer wiersza historii poleceń, liczbę zadań, wersję interpretera i ścieżkę dostępu do bieżącego katalogu: $ export PS1='\n[\u@\h t:\l l:$SHLVL h:\! j:\j v:\V]\n$PWD\$ ' [marek@fedora t:0 l:3 h:22 j:0 v:3.1.17] /home/marek$

8. Kolejne zgłoszenie nie pozostawia użytkownika obojętnym — albo się je uwielbia, albo nie-

nawidzi. Obejmuje nazwę użytkownika, nazwę systemu, nazwę terminala (T), poziom powłoki (L), numer polecenia (C) oraz datę i czas w formacie ISO 8601: $ export PS1='\n[\u@\h:T\l:L$SHLVL:C\!:\D{%Y-%m-%d_%H:%M:%S_%Z}]\n$PWD\$ ' [marek@fedora:T0:L3:C24:2007-11-04_11:35:39_CET] /home/marek$ cd /usr/local/bin/ [marek@fedora:T0:L3:C25:2007-11-04_11:36:22_CET] /usr/local/bin$

Taka treść znaku zachęty precyzyjnie określa kto, gdzie, kiedy i co zrobił. Doskonale nadaje się więc do dokumentowania kolejnych kroków podczas realizacji dowolnego zadania. Niektóre osoby uznają go jednak za zbyt nieczytelny i rozpraszający uwagę.

Zabawne znaki zgłoszenia Poniżej zostały przedstawione trzy bardziej zabawne znaki zgłoszenia, które wykorzystują znaki specjalne kodu ANSI odpowiedzialne za zmianę koloru bądź znaki, które modyfikują nagłówek okna terminala xterm. Trzeba jednak pamiętać, że nie we wszystkich sytuacjach tego typu znaki zachęty będą działać zgodnie z założeniami. Obejmują bowiem zmienne systemowe, parametry terminala xterm oraz ustawienia oprogramowania klienckiego dla usług SSH i Telnet. Symbole specjalne muszą być otoczone znakami \[ i \], które informują powłokę o tym, że zapisane wewnątrz nawiasu wartości odpowiadają znakom niedrukowalnym. W przeciwnym przypadku powłoka mogłaby niewłaściwie zinterpretować długość wierszy i przenieść tekst w nieodpowiednie miejsce.

1. Nazwa użytkownika, nazwa systemu oraz ścieżka do bieżącego katalogu wyświetlane w jasnoniebieskim kolorze (kolor nie jest widoczny na wydruku); $ export PS1='\[\033[1;34m\][\u@\h:\w]\$\[\033[0m\] ' [marek@fedora:~]$ [marek@fedora:~]$ cd /tmp [marek@fedora:/tmp]$

2. Nazwa użytkownika, nazwa systemu i ścieżka do bieżącego katalogu wyświetlane jedno-

cześnie w znaku zachęty i nagłówku okna terminala xterm. Wprowadzenie instrukcji poza oknem terminala może spowodować znaczny bałagan w znaku zgłoszenia. $ export PS1='\[\033]0;\u@\h:\w\077\][\u@\h:\w]\$ ' [marek@ubuntu:~]$ [marek@ubuntu:~]$ cd /tmp [marek@ubuntu:/tmp]$

392

|

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

3. Jednoczesna zmiana koloru i treści nagłówka okna: $ export PS1='\[\033]0;\u@\h:\w\007\]\[\033[1;34m\][\u@\h:\w]\$\[\033[0m\] ' [marek@ubuntu:~]$ [marek@ubuntu:~]$ cd /tmp [marek@ubuntu:/tmp]$

Chcąc zaoszczędzić sobie żmudnego wpisywania przedstawionych w recepturze instrukcji, można wykorzystać dołączony do książki plik skryptu ./rozdzial16/prompts (plik jest dostępny w serwisie FTP wydawnictwa Helion: ftp.helion.pl). # plik receptury: prompts # Nazwa użytkownika @ nazwa systemu, data i czas oraz ścieżka do bieżącego katalogu # directory (CWD): export PS1='[\u@\h \d \A] \w \$ ' # Nazwa użytkownika @ nazwa systemu, data i czas w formacie ISO 8601 oraz # nazwa bieżącego katalogu (\W) export PS1='[\u@\H \D{%Y-%m-%d %H:%M:%S%z}] \W \$ ' # Nazwa użytkownika @ nazwa systemu, wersja oprogramowania bash, ścieżka # do bieżącego katalogu (\w) export PS1='[\u@\h \V \w] \$ ' # Znak nowego wiersza, nazwa użytkownika, nazwa systemu, terminal PTY, poziom powłoki, # numer wpisu w historii poleceń, znak nowego wiersza i # ścieżka dostępu do bieżącego katalogu ($PWD) export PS1='\n[\u@\h \l:$SHLVL:\!]\n$PWD\$ ' # Nazwa użytkownika @ kod zakończenia wcześniejszego polecenia oraz ścieżka # do bieżącego katalogu. export PS1='[\u@\h $? \w \$ ' # Liczba procesów drugoplanowych export PS1='\n[\u@\h jobs:\j]\n$PWD\$ ' # Nazwa użytkownika @ nazwa systemu, nazwa terminala, poziom powłoki, numer wiersza # historii poleceń,liczba zadań, wersja interpretera i ścieżka dostępu do bieżącego # katalogu export PS1='\n[\u@\h t:\l l:$SHLVL h:\! j:\j v:\V]\n$PWD\$ ' # Nazwa użytkownika @ nazwa systemu, nazwa terminala (T), poziom powłoki (L), # numer polecenia (C) oraz data i czas w formacie ISO 8601 export PS1='\n[\u@\h:T\l:L$SHLVL:C\!:\D{%Y-%m-%d_%H:%M:%S_%Z}]\n$PWD\$ ' # Nazwa użytkownika @ nazwa systemu oraz ścieżka do bieżącego katalogu # wyświetlane w jasnoniebieskim kolorze export PS1='\[\033[1;34m\][\u@\h:\w]\$\[\033[0m\] ' # Nazwa użytkownika @ nazwa systemu oraz ścieżka do bieżącego katalogu # wyświetlane jednocześnie w znaku zachęty i nagłówku okna terminala xterm. export PS1='\[\033]0;\u@\h:\w\007\][\u@\h:\w]\$ '

16.2. Dostosowanie znaku zachęty

|

393

# Jednoczesna zmiana koloru i treści nagłówka okna. export PS1='\[\033]0;\u@\h:\w\007\]\[\033[1;34m\][\u@\h:\w]\$\[\033[0m\] '

Analiza Polecenie export musi być użyte tylko jeden raz, aby oznaczyć zmienną, jako parametr eksportowany do procesów potomnych. Jeśli opcja powłoki promptvars jest włączona, co jest domyślnym ustawieniem, ciągi znaku zachęty podlegają kolejno operacjom dekodowania, podmiany wartości parametrów, podstawienia wyników poleceń, obliczenia wyrażeń arytmetycznych, usunięcia znaków cudzysłowu i apostrofu, a w końcu wyświetlenia sformowanego tekstu. Ciągi znaku zachęty są przechowywane w zmiennych $PS1, $PS2, $PS3 i $PS4. Zgłoszenie wiersza poleceń jest zapisywane w zmiennej $PS1. Wartość przechowywana w zmiennej $PS2 jest drugim znakiem zachęty, wyświetlanym wówczas, gdy interpreter wymaga dostarczenia dodatkowych informacji niezbędnych do zakończenia polecenia. Domyślnie składa się on ze znaku >, ale nic nie stoi na przeszkodzie, żeby został on zastąpiony ciągiem tekstowym o dowolnej treści. W zmiennej $PS3 jest zdefiniowany znak zgłoszenia dla instrukcji select (więcej informacji na ten temat zostało zamieszczonych w recepturze 16.16, „Dodawanie nowych funkcji przez zastosowanie ładowanych poleceń wbudowanych”, oraz w recepturze 16.17, „Usprawnienie mechanizmu uzupełniania poleceń”). Jego domyślną wartością jest ciąg #?. Zmienna $PS4 przechowuje natomiast znak zgłoszenia mechanizmu xtrace (wykorzystywanego do debugowania kodu) — domyślnie jest to znak +. Pierwszy znak zmiennej $PS4 może być wielokrotnie powielany. Liczba powtórzeń odpowiada liczbie zagnieżdżeń danego bloku kodu. $ export PS2='Drugi> ' $ for i in * Drugi> do Drugi> echo $i Drugi> done command_substitution email_sample email_sample_css finding_ipas func_choice $ export PS3='Wybierz coś: ' $ select item in 'jeden dwa trzy'; do echo $item; done 1) jeden dwa trzy Wybierz coś: ^C $ export PS4='+ uruchamianie> ' $ set -x $ echo $( echo $( +++ uruchamianie> +++ uruchamianie> +++ uruchamianie> +++ uruchamianie> +++ uruchamianie> +++ uruchamianie> +++ uruchamianie> +++ uruchamianie> +++ uruchamianie> +++ uruchamianie>

394 |

for i in *; do echo $i; done ) ) for i in '*' echo command_substitution for i in '*' echo email_sample for i in '*' echo email_sample_css for i in '*' echo finding_ipas for i in '*' echo func_choice

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

++ uruchamianie> echo command_substitution email_sample email_sample_css finding_ipas func_choice + uruchamianie> echo command_substitution email_sample email_sample_css finding_ipas func_choice command_substitution email_sample email_sample_css finding_ipas func_choice

Ponieważ znak zachęty jest użyteczny tylko wtedy, gdy pracujemy w powłoce interaktywnej, najlepszym miejscem do zdefiniowania jego treści jest plik globalnej konfiguracji /etc/bashrc lub plik lokalny ~/.bashrc. Zgodnie z przedstawionymi przykładami zaleca się zapisywanie znaku spacji jako ostatniego znaku w ciągu przypisywanym zmiennej $PS1. Dzięki temu treść znaku zachęty jest oddzielana od samego polecenia, co czyni je łatwiejszym w analizie. Ponieważ ciąg przypisywany zmiennej $PS1 zawiera znaki spacji i inne symbole specjalne, należy otaczać go znakami cudzysłowu lub apostrofu. Chcąc wyświetlić w znaku zachęty nazwę bieżącego katalogu, można wykorzystać jedną z trzech wartości: \w, \W, $PWD. Symbol \W odpowiada za wyświetlenie samej nazwy katalogu, czyli ostatniego członu ścieżki dostępu do tego katalogu. Pełna ścieżka dostępu do katalogu jest udostępniana przez symbol \w. W obydwu przypadkach katalog domowy ($HOME) jest oznaczany za pomocą tyldy (~). Ponieważ wielu osobom nie odpowiada taki sposób zapisu ścieżki, wykorzystują one do wyświetlenia informacji o katalogu zmienną $PWD. Uwzględnienie danych na temat bieżącego katalogu w znaku zachęty powoduje, że długość ciągu zgłoszenia nieustannie się zmienia, a sam ciąg może zajmować więcej niż jeden wiersz (w przypadku katalogów zapisanych na dalszych pozycjach w strukturze katalogów). Taki sposób działania nie jest z kolei akceptowany przez inną grupę użytkowników. Właściwym rozwiązaniem może się więc okazać przedstawiona poniżej funkcja oraz kod znaku zachęty wykorzystujący tę funkcję. # plik receptury: func_trunc_PWD function trunc_PWD { # Kod skracania wartości $PWD został zaczerpnięty z dokumentu # The Bash Prompt HOWTO - punkt 11.10, Controlling the Size # and Appearance of $PWD # http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x783.html # Ile znaków $PWD powinno zostać zachowanych? local pwdmaxlen=30 # Znacznik informujący, że ciąg został przycięty: local trunc_symbol="..." # Tymczasowa zmienna dla wartości $PWD local myPWD=$PWD # # # #

Zastąpienie znakiem "~" tej części ciagu $PWD, która odpowiada wartości zapisanej w zmiennj $HOME OPCJONALNE - poniższy blok kodu można objąć komentarzem, jeśli powinna zostać wyświetlona pełna ścieżka dostępu do katalogu

myPWD=${PWD/$HOME/'~'} if [ ${#myPWD} -gt $pwdmaxlen ]; then local pwdoffset=$(( ${#myPWD} - $pwdmaxlen )) echo "${trunc_symbol}${myPWD:$pwdoffset:$pwdmaxlen}" else echo ${myPWD} fi }

16.2. Dostosowanie znaku zachęty

|

395

Oto przykład wykorzystania funkcji: $ source plik/zawierający/funkcję/trunc_PWD [marek@fedora 0:1:988] /tmp/bardzo/bardzo/długa ścieżka dostępu/do katalogu/który będzie/wykorzystany/w/ przykładzie$ export PS1='\n[\u@\h \l:$SHLVL:\!]\n$(trunc_PWD)\$ ' [marek@fedora 0:1:989] ...zie/wykorzystany/w/przykładzie$

Przedstawione powyżej definicje znaku zachęty są otoczone znakami apostrofu, co gwarantuje literalne zapisanie znaków $ i innych symboli specjalnych. Ciąg zgłoszenia jest interpretowany w czasie wyświetlania, zatem wartości wszystkich zmiennych zostają wyświetlone zgodnie z przewidywaniami. Możliwe jest również zastosowanie znaków cudzysłowu, ale wówczas trzeba poprzedzać symbole specjalne powłoki znakami lewego ukośnika (na przykład zamiast znaku $ trzeba użyć dwuznaku \$). Numer polecenia i numer wpisu na liście poleceń historycznych nie muszą się zgadzać. Podczas wyznaczania numeru w zestawieniu historycznym uwzględniane są również polecenia wprowadzone we wcześniejszych sesjach powłoki. Natomiast numer polecenia określa jedynie numer instrukcji wprowadzonej w danej sesji powłoki. Powłoka udostępnia również specjalną zmienną $PROMPT_COMMAND, która (jeśli została zdefiniowana) jest interpretowana jako polecenie wykonywane przed ustaleniem wartości zmiennej $PS1 i wyświetleniem jej. Wadą tego mechanizmu jest to, że polecenie musi być wykonane przed każdym wyświetleniem znaku zachęty, czyli bardzo często. Za jego pomocą można na przykład zdefiniować operację wstawienia w znaku zachęty wyniku polecenia $(ls -1 | wc –l), odpowiedzialnego za ustalenie liczby plików zapisanych w bieżącym katalogu. Jednak w starszych komputerach lub w mocno obciążonych systemach użytkownik z pewnością zauważyłby znaczne opóźnienia w wyświetlaniu ciągu zgłoszenia. Z tego względu najkorzystniejsze wydaje się definiowanie krótkich i nieskomplikowanych znaków zachęty (nie takich, jakie zostały przedstawione w punkcie „Rozwiązanie”). Zamiast spowalniać wyświetlanie znaku zachęty, korzystniejsze jest przygotowanie kilku funkcji lub aliasów, które będą mogły zostać wykonane na żądanie. Aby uniknąć bałaganu wprowadzanego przez znaki specjalne terminala xterm i symbole ANSI w przypadku, w którym nie są one obsługiwane, wystarczy do pliku rc dodać następujący kod: case $TERM in xterm*) export PS1='\[\033]0;\u@\h:\w\007\]\[\033[1;34m\][\u@\h:\w]\$\[\033[0m\] ' ;; *) export PS1='[\u@\h:\w]\$ ' ;; esac

Więcej informacji na ten temat znajduje się w dodatku A, w punkcie „Dostosowanie znaku zachęty”.

Kolory W przedstawionym wcześniej przykładzie wykorzystania kodów ANSI wartość 1;34m oznacza „ustaw dla danego znaku atrybut jasnego koloru i zmień kolor na niebieski”, wartość 0m oznacza „wyczyść wszystkie atrybuty i nie przypisuj żadnego koloru”. Zestawienie kodów ANSI zostało zamieszczone w dodatku A, w punkcie „Symbole specjalne ANSI”. Końcowy znak m informuje, że wartość jest symbolem specjalnym koloru.

396

|

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

Poniżej został zamieszczony skrypt, który wyświetla tekst we wszystkich kombinacjach kolorystycznych. Gdyby jednak po jego uruchomieniu okazało się, że listing jest czarno-biały, oznacza to, że dany terminal nie obsługuje sekwencji specjalnych ANSI lub że opcja ta nie została włączona. #!/bin/bash # plik receptury: colors # # Jest to skrypt Daniela Crismana publikowany w dokumencie # The Bash Prompt HOWTO — punkt 6.1., Colours. # http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html # # Skrypt ten generuje zbiór kodów koloru, demonstrując # możliwości teminala. Każdy wiersz odpowiada jednemu # z siedemnastu kolorów tekstu (kolor domyślny + 16 ustawianych) # i składa się z tekstu testowego wyświetlanego # na dziewięciu różnych kolorach tła (kolor domyślny + 8 ustawianych). # T='gYw'

# Tekst testowy

echo -e "\n 44m

45m

46m

40m 47m";

41m

42m

43m\

for FGs in ' m' ' 1m' ' 30m' '1;30m' ' 31m' '1;31m' ' 32m' \ '1;32m' ' 33m' '1;33m' ' 34m' '1;34m' ' 35m' '1;35m' \ ' 36m' '1;36m' ' 37m' '1;37m'; do FG=${FGs// /} echo -en " $FGs \033[$FG $T " for BG in 40m 41m 42m 43m 44m 45m 46m 47m; do echo -en "$EINS \033[$FG\033[$BG $T \033[0m"; done echo; done echo

Zobacz również • Podręcznik użytkownika powłoki bash. • Plik ./examples/noah/prompt.bash z archiwum plików źródłowych pakietu bash. • http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/index.html. • http://sourceforge.net/projects/bashish. • Receptura 1.1, „Rozszyfrowanie znaku zachęty”. • Receptura 3.7, „Wybór opcji z listy”. • Receptura 16.10, „Wykorzystanie dodatkowych znaków zachęty: $PS2, $PS3, $PS4”. • Receptura 16.16, „Dodawanie nowych funkcji przez zastosowanie ładowanych poleceń wbu-

dowanych”.

• Receptura 16.17, „Usprawnienie mechanizmu uzupełniania poleceń”. • Receptura 16.18, „Właściwe wykorzystanie plików startowych”. • Receptura 16.19, „Tworzenie samodzielnych, przenośnych plików RC”. • Receptura 16.20, „Uruchomienie powłoki z własną konfiguracją”.

16.2. Dostosowanie znaku zachęty

|

397

• Punkt „Dostosowanie znaku zachęty” w dodatku A. • Punkt „Symbole specjalne ANSI” w dodatku A.

16.3. Trwała zmiana wartości $PATH Problem Chcemy trwale zmienić wartość zmiennej $PATH.

Rozwiązanie Najpierw trzeba ustalić, w którym skrypcie zmienna $PATH jest definiowana. Następnie wystarczy zmodyfikować treść definicji. W przypadku lokalnych kont użytkownika instrukcja wyznaczająca wartość zmiennej jest najprawdopodobniej umieszczona w pliku ~/.profile lub ~/.bash_ ´profile. Aby odszukać właściwy plik, wystarczy wykonać instrukcję grep -l PATH ~/.[^.]*. Do edycji pliku nadaje się dowolny edytor tekstu. Chcąc od razu uaktywnić wprowadzone zmiany, trzeba włączyć kod skryptu za pomocą instrukcji source. Administratorzy systemu (osoby posługujące się kontem root) mogą dokonać zmian, które obejmą cały system. Procedura jest niemal identyczna. Różnica polega jedynie na tym, że modyfikowane pliki są zapisane w katalogu /etc. O tym, które to pliki, decyduje rodzaj i wersja systemu operacyjnego. Zazwyczaj rzecz dotyczy pliku /etc/profile, ale często są to również pliki /etc/bashrc, /etc/rc, /etc/default/login, ~/.ssh/environment oraz /etc/environment.

Analiza Działanie polecenia grep –l PATH ~/.[^.]* okazuje się bardzo ciekawe, jeśli uwzględni się pracę mechanizmu podstawiania wartości symboli wieloznacznych oraz występowanie katalogów /. i /... Więcej informacji na ten temat znajduje się w recepturze 1.5, „Wyświetlenie plików ukrytych z bieżącego katalogu”. Wartości zapisane w zmiennej $PATH mają bezpośredni wpływ na bezpieczeństwo systemu, szczególnie jeśli zmiany obejmują konto użytkownika root. Jeśli standardowe katalogi systemowe (takie jak /bin, /sbin) zostaną poprzedzone nazwą katalogu, w którym wszyscy użytkownicy mogą zapisywać dane, wówczas każdy użytkownik może przygotować program, który po uruchomieniu przez administratora (root) wykona każde zdefiniowane w nim zadanie. Z tego powodu oznaczenie bieżącego katalogu (.) nigdy nie powinno występować w treści zmiennej $PATH konta root. Chcąc mieć pewność, że opisany problem nigdy nie wystąpi, wystarczy stosować się do kilku wymienionych poniżej zasad: • Ciąg zmiennej $PATH dla konta root powinien być jak najkrótszy i nie powinien uwzględ-

niać ścieżek względnych. • Nie wolno umieszczać w zmiennej $PATH nazw katalogów, w których wszyscy użytkownicy

mogą zapisywać swoje pliki.

398 |

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

• Warto rozważyć jawne definiowanie wartości zmiennej $PATH we wszystkich skryptach,

które są uruchamiane przez użytkownika root. • Warto rozważyć korzystanie z bezwzględnych ścieżek dostępu we wszystkich skryptach,

które są uruchamiane przez użytkownika root. • Katalogi domowe użytkowników i katalogi aplikacji powinny być wymieniane w ciągu

zmiennej $PATH na ostatniej pozycji i jedynie w odniesieniu do kont użytkowników nieuprzywilejowanych.

Zobacz również • Receptura 1.5, „Wyświetlenie plików ukrytych z bieżącego katalogu”. • Receptura 4.1, „Uruchamianie pliku wykonywalnego”. • Receptura 14.3, „Wyznaczanie bezpiecznej wartości $PATH”. • Receptura 14.9, „Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfi-

kowanie zawartości”. • Receptura 14.10, „Dodawanie bieżącego katalogu do listy $PATH”. • Receptura 16.4, „Chwilowa zmiana wartości $PATH”.

16.4. Chwilowa zmiana wartości $PATH Problem Chcemy mieć możliwość łatwego dodawania nowych katalogów do zmiennej $PATH lub ich usuwania, ale tylko w czasie trwania danej sesji.

Rozwiązanie Istnieje kilka metod rozwiązania tego problemu. Aby dołączyć nazwę katalogu na początku lub końcu ciągu zmiennej $PATH, wystarczy użyć instrukcji PATH="nowy_katalog:$PATH" lub PATH="$PATH:nowy_katalog". Najpierw trzeba się jednak upewnić, że katalog nie został wcześniej zapisany w ciągu $PATH. Jeżeli konieczne jest wstawienie pewnej wartości w środku ciągu $PATH, można wyświetlić cały ciąg na ekranie za pomocą polecenia echo, a następnie, korzystając z funkcji „kopiuj i wklej” terminala, powielić ciąg w nowym wierszu i odpowiednio wyedytować. Można również zastosować makro opisane w punkcie „Macros that are convenient for shell interaction” dokumentacji mechanizmu readline, publikowanej pod adresem http://tiswww.tis.case.edu/php/chet/readline/ ´readline.html#SEC12: # edycja ścieżek zmiennej $PATH "\C-xp": "PATH=${PATH}\e\C-e\C-a\ef\C-f" # [...] # Edycja zmiennej w bieżacym wierszu. "\M-\C-v": "\C-a\C-k$\C-y\M-\C-e\C-a\C-y="

16.4. Chwilowa zmiana wartości $PATH

| 399

Naciśnięcie klawiszy Ctrl+X i P powoduje wyświetlenie wartości zmiennej $PATH w bieżącym wierszu i pozwala na wprowadzanie zmian. Podobnie wpisanie nazwy zmiennej i naciśnięcie klawiszy Ctrl+V powoduje wyświetlenie wartości tej zmiennej w trybie edycji. W przypadku mniej skomplikowanych zadań można także wykorzystać krótką funkcję (zapisaną w pliku /etc/profile dystrybucji Red Hat Linux). # plik receptury: func_pathmunge # Kod pobrany z systemu Red Hat Linux function pathmunge { if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then if [ "$2" = "after" ] ; then PATH="$PATH:$1" else PATH="$1:$PATH" fi fi }

Do realizacji bardziej skomplikowanych zadań, wymagających szczegółowego sprawdzania błędów, można zastosować kilka bardziej ogólnych funkcji: # plik receptury: func_tweak_path #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Dodanie katalogu na początku lub końcu ciągu $PATH, jeśli katalog ten # nie został wcześniej dodany. Dowiązania symboliczne nie są brane pod uwagę. # Funkcja zwraca wartość 1 lub ustawia nową wartość $PATH # Przykład wywołania: add_to_path (przed|za) function add_to_path { local location=$1 local directory=$2 # Sprawdzenie,czy zostały podane parametry funkcji if [ -z "$location" -o -z "$directory" ]; then echo "$0:$FUNCNAME: brak definicji położenia lub katalogu do dodania" >&2 echo "np. add_to_path przed /bin" >&2 return 1 fi # Sprawdzenie, czy katalog nie został opisany za pomocą ścieżki względnej if [ $(echo $directory | grep '^/') ]; then : echo "$0:$FUNCNAME: '$directory' jest ścieżką bezwględną" >&2 else echo "$0:$FUNCNAME: nie można dodać ścieżki względnej '$directory' do zmiennej \$PATH" >&2 return 1 fi # Sprawdzenie, czy dodawany katalog istnieje if [ -d "$directory" ]; then : echo "$0:$FUNCNAME: katalog istnieje" >&2 else echo "$0:$FUNCNAME: '$directory' nie istnieje -- praca przerwana" >&2 return 1 fi # Sprawdzenie, czy katalog nie został wcześniej uwzględniony w ciągu $PATH if [ $(contains "$PATH" "$directory") ]; then echo "$0:$FUNCNAME: '$directory' występuje w zmiennej \$PATH--praca przerwana" >&2

400 |

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

else : echo "$0:$FUNCNAME: dodanie katalogu do zmiennej \$PATH" >&2 fi # Ustalenie, co należy zrobić case $location in przed* ) PATH="$directory:$PATH" ;; za* ) PATH="$PATH:$directory" ;; * ) PATH="$PATH:$directory" ;; esac # Oczysczenie wartości nowej ścieżki i przypisanie do zmiennej $PATH PATH=$(clean_path $PATH) } # koniec funkcji add_to_path #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Usunięcie katalogu ze zmiennej $PATH (jeśli został zdefiniowany). # Wynik: Ustawienie nowej wartości $PATH # Przykład wywołania: rm_from_path function rm_from_path { local directory=$1 # Usunięcie wszystkich wystąpień katalogu $directory z ciągu $PATH PATH=${PATH//$directory/} # Oczyszczenie wartości nowej ścieżki i przypisanie do zmiennej $PATH PATH=$(clean_path $PATH) } # koniec funkcji rm_from_path #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Usunięcie początkowych, końcowych i powielonych znaków ':', # usunięcie zduplikowanych wpisów # Wynik: wyświetlenie oczyszczonej wartości $PATH # Przykład wywołania: cleaned_path=$(clean_path $PATH) function clean_path { local path=$1 local newpath local directory # Sprawdzenie,czy zostały podane parametry funkcji [ -z "$path" ] && return 1 # Usunięcie zduplikowanych katalogów, jeśli występują for directory in ${path//:/ }; do contains "$newpath" "$directory" && newpath="${newpath}:${directory}" done # Usunięcie początkowego znaku ':' # Usunięcie końcowego znaku ':' # Usunięcie zduplikowanych separatorów ':' newpath=$(echo $newpath | sed 's/^:*//; s/:*$//; s/::/:/g') # Zwrócenie nowej wartości $PATH echo $newpath } # koniec funkcji clean_path #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Sprawdzenie, czy zmienna $PATH zawiera nazwę podanego katalogu

16.4. Chwilowa zmiana wartości $PATH

|

401

# Funkcja zwraca wartość 1, jeśli katalog występuje; 0 w przeciwnym przypadku # Przykład wywołania: contains $PATH $dir function contains { local pattern=":$1:" local target=$2 # W porównaniu jest uwzględniana wielkość liter, chyba że została # włączona opcja nocasematch case $pattern in *:$target:* ) return 1;; * ) return 0;; esac } # koniec funkcji contains

Oto kilka przykładów wykorzystania funkcji: $ source func_tweak_path $ echo $PATH /usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/marek/bin:/home/marek/bin $ add_to_path przed katalog -bash:add_to_path: nie można dodać ścieżki względnej 'katalog' do zmiennej $PATH $ add_to_path przed ~/katalog -bash:add_to_path: '/home/marek/katalog' nie istnieje -- praca przerwana $ add_to_path przed '~/katalog' -bash:add_to_path: nie można dodać ścieżki względnej '~/katalog' do zmiennej $PATH $ rm_from_path /home/marek/bin $ echo $PATH /usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin $ add_to_path /home/marek/bin -bash:add_to_path: brak definicji położenia lub katalogu do dodania np. add_to_path przed /bin $ add_to_path za /home/marek/bin $ echo $PATH /usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/marek/bin $ rm_from_path /home/marek/bin $ add_to_path przed /home/marek/bin $ echo $PATH /home/marek/bin:/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin

Analiza Z opisywanym problemem i przedstawionymi w skrypcie func_tweak_path funkcjami wiążą się cztery niezwykle interesujące zagadnienia. Po pierwsze, ewentualna próba zmodyfikowania wartości zmiennej $PATH lub jakiejkolwiek innej zmiennej środowiskowej z poziomu kodu skryptu nie odnosi zamierzonego efektu, ponieważ skrypty są uruchamiane w podpowłokach, które są usuwane po zakończeniu pracy programu.

402 |

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

Wraz z podpwołoką są również usuwane wszystkie zmienione wartości zmiennych. Dlatego w prezentowanym rozwiązaniu funkcje zostały załadowane (za pomocą polecenia source) do bieżącej powłoki i w niej uruchomione. Po drugie, wykonanie instrukcji add_to_path ~/katalog kończy się wyświetleniem komunikatu katalog nie istnieje, a wywołanie add_to_path '~/katalog' powoduje wygenerowanie komunikatu nie można dodać ścieżki względnej. Różnica wynika z faktu, że ciąg ~/katalog przed przekazaniem do funkcji zostaje przekształcony do postaci /home/marek/katalog. Przyjęcie założenia, że mechanizm podmiany wartości w tym przypadku nie obowiązuje, jest błędem. Chcąc sprawdzić, jaka wartość jest rzeczywiście przekazywana do funkcji, można na jej początku wstawić instrukcję echo. Po trzecie, w kodzie skryptu zostały wykorzystane instrukcje typu echo "$0:$FUNCNAME: brak definicji położenia lub katalogu do dodania". Konstrukcja $0:$FUNCNAME znacznie ułatwia określenie, w którym miejscu wystąpił błąd. Zmienna $0 zawsze przechowuje nazwę realizowanego programu (w tym przypadku jest to sama powłoka, czyli –bash, w innych sytuacjach może to być konkretny skrypt lub program). Dołączenie do niej nazwy funkcji dodatkowo precyzuje źródło problemów, co jest szczególnie użyteczne podczas debugowania kodu. Dołączenie na końcu instrukcji echo operatora >&2 powoduje przekazanie danych do strumienia STDERR, czyli strumienia właściwego dla ostrzeżeń i komunikatów o błędach. Po czwarte po analizie kodu większość osób uzna, że poszczególne funkcje mają niespójny interfejs programistyczny, ponieważ funkcje add_to_path i remove_from_path rzeczywiście ustawiają wartość zmiennej $PATH, natomiast funkcja clean_path wyświetla oczyszczony ciąg, a funkcja contains zwraca wartość true lub false. Takie rozwiązanie nie jest może idealne dla aplikacji komercyjnych, ale czyni przykład jeszcze bardziej interesującym, gdyż demonstruje różne sposoby przetwarzania wartości $PATH. Ponadto, biorąc pod uwagę przeznaczenie poszczególnych funkcji, taki interfejs wydaje się bardziej użyteczny.

Zobacz również • Podobne, ale bardziej spójne, choć mniej przejrzyste, funkcje przetwarzania wartości zmien-

nej $PATH są zawarte w archiwum powłoki bash w pliku ./examples/functions/pathfuncs. • Receptura 10.5, „Wykorzystanie funkcji. Parametry i zwracane wartości”. • Receptura 14.3, „Wyznaczanie bezpiecznej wartości $PATH”. • Receptura 14.9, „Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfi-

kowanie zawartości”. • Receptura 14.10, „Dodawanie bieżącego katalogu do listy $PATH”. • Receptura 16.3, „Trwała zmiana wartości $PATH”. • Receptura 16.20, „Uruchomienie powłoki z własną konfiguracją”. • Dodatek B.

16.4. Chwilowa zmiana wartości $PATH

| 403

16.5. Wyznaczanie wartości $CDPATH Problem Chcemy ułatwić sobie przechodzenie między kilkoma katalogami zapisanymi w różnych miejscach systemu plików.

Rozwiązanie Należy wyznaczyć odpowiednią wartość zmiennej $CDPATH. Nazwy często wykorzystywanych katalogów zazwyczaj nie powtarzają się, więc jako przykład możemy rozważyć pracę z kilkoma katalogami skryptów startowych. /home/marek$ cd rc3.d -bash: cd: rc3.d: Nie ma takiego pliku ani katalogu /home/marek$ export CDPATH='.:/etc' /home/marek$ cd rc3.d /etc/rc3.d /etc/rc3.d$ cd rc5.d /etc/rc5.d /etc/rc5.d$ /etc/rc5.d$ cd games -bash: cd: games: Nie ma takiego pliku ani katalogu /etc/rc5.d$ export CDPATH='.:/etc:/usr' /etc/rc5.d$ cd games /usr/games /usr/games$

Analiza Zgodnie z dokumentacją powłoki bash, zmienna $CDPATH przechowuje listę rozdzielanych znakami dwukropka nazw katalogów, które są przeszukiwane przez wbudowane polecenie cd. Pełni ona taką samą funkcję dla instrukcji cd, jak zmienna $PATH dla uruchamianych skryptów. Bywa więc bardzo użyteczna. Zmienna $CDPATH nie jest wykorzystywana, gdy parametr instrukcji cd rozpoczyna się od znaku ukośnika. W przypadku zastosowania wartości zdefiniowanej w zmiennej $CDPATH do strumienia STDOUT jest przekazywana pełna ścieżka dostępu do nowego katalogu, co można zaobserwować podczas wykonywania zaprezentowanych poleceń.

404 |

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

Wykorzystanie zmiennej $CDPATH jest nieco odmienne w przypadku uruchomienia powłoki bash w trybie zgodności ze standardem POSIX (na przykład za pomocą wywołania /bin/sh lub przez dodanie opcji --posix). W dokumentacji powłoki bash znajduje się następujące ostrzeżenie: „Jeśli wartość zmiennej $CDPATH jest zdefiniowana, wbudowana instrukcja cd nie dołącza automatycznie do listy katalogów katalogu bieżącego. Oznacza to, że wykonanie polecenia cd zakończy się błędem, jeśli na podstawie informacji zawartych w ciągu $CDPATH nie będzie można ustalić poprawnej nazwy katalogu, nawet jeśli katalog o takiej samej nazwie, jaka została wymieniona w instrukcji cd, znajduje się w bieżącym katalogu”. Aby uniknąć problemów związanych z takim sposobem interpretacji zmiennej $CDPATH, wystarczy jawnie dodać do jej wartości znak kropki (.). Z drugiej strony dodanie tego znaku powoduje, że istotna staje się inna wzmianka z dokumentacji powłoki bash. „Gdy powłoka wykorzysta nazwę katalogu zdefiniowaną w zmiennej $CDPATH lub gdy pierwszym parametrem polecenia jest znak ‘-‘, a operacja zmiany katalogu zostanie wykonana poprawnie, do standardowego strumienia wyjściowego jest przekazywana pełna ścieżka dostępu do nowo wybranego katalogu”. Innymi słowy, niemal po każdym użyciu polecenia cd do strumienia STDOUT będzie przekazywana nazwa nowego katalogu bieżącego, co nie jest standardowym sposobem działania powłoki.

Do katalogów najczęściej wymienianych w treści zmiennej $CDPATH należą: .

Katalog bieżący (zgodnie z wcześniejszym ostrzeżeniem). ~/

Katalog domowy użytkownika. ..

Katalog nadrzędny. ../..

Katalog występujący o dwa poziomy wyżej w strukturze katalogów. ~/.katalog_dowiązań

Ukryty katalog przechowujący jedynie dowiązania symboliczne do innych często wykorzystywanych katalogów

Chcąc uwzględnić wszystkie wymienione katalogi, należałoby wykonać następującą instrukcję: export CDPATH='.:~/:..:../..:~/.katalog_dowiązań'

Zobacz również • Polecenie help cd. • Receptura 16.13, „Utworzenie lepszego polecenia cd”. • Receptura 16.20, „Uruchomienie powłoki z własną konfiguracją”. • Receptura 18.1, „Szybkie przechodzenie między określonymi katalogami”.

16.5. Wyznaczanie wartości $CDPATH

| 405

16.6. Skracanie i zmienianie nazw poleceń Problem Chcemy skrócić długą lub skomplikowaną nazwę często wykorzystywanego polecenia bądź zmienić nazwę polecenia tak, aby było ono łatwiejsze do zapamiętania lub wyszukania z zastosowaniem pewnych kryteriów dopasowania.

Rozwiązanie Nigdy nie należy zmieniać nazw plików wykonywalnych, gdyż funkcjonowanie wielu modułów systemu Unix lub Linux zależy od dostępności określonych poleceń, a tym samym od składowania ich plików wykonywalnych w określonych katalogach. Właściwe rozwiązanie polega na utworzeniu aliasu, funkcji lub dowiązania symbolicznego. Zgodnie z informacjami zawartymi w dokumentacji interpretera bash, „mechanizm aliasów zapewnia wstawienie ciągu tekstowego w miejsce pierwszego słowa zwykłego polecenia. Powłoka przechowuje zbiór aliasów, które mogą być definiowane lub usuwane za pomocą wbudowanych instrukcji alias i unalias”. Oznacza to, że każdy użytkownik może zmienić nazwę polecenia lub utworzyć makro, zapisując większą liczbę poleceń w jednym aliasie. Przykładami mogą tu być instrukcje alias copy='cp' lub alias ll.='ls –ld .*'. Aliasy są zastępowane zdefiniowanymi instrukcjami tylko jeden raz. Zatem do zmiany sposobu wykonywania polecenia ls może zostać wykorzystana instrukcja alias ls='ls –F', bez obawy o powstanie pętli nieskończonej. W większości przypadków podmiana aliasu dotyczy tylko pierwszego słowa wiersza polecenia i ma charakter podmiany wartości tekstowej — aliasy nie mogą pobierać parametrów. Innymi słowy, alias alias='mkdir $1 && cd $1' nie będzie działał zgodnie z przewidywaniami. Funkcje są wykorzystywane w dwojaki sposób. Po pierwsze można je załadować za pomocą polecenia source do interaktywnej powłoki, co w praktyce powoduje przekształcenie ich w skrypty powłoki, które są nieustannie przechowywane w pamięci. Ładowane w ten sposób funkcje zazwyczaj nie są szczególnie rozbudowane, a ich wykonanie jest bardzo szybkie — funkcje te rezydują w pamięci i są uruchamiane w ramach bieżącego procesu, bez konieczności uruchamiania podpowłoki. Drugi technika polega na wywoływaniu funkcji w kodzie skryptu jako podprocedur tego skryptu. W odróżnieniu od aliasów, do funkcji można przekazywać parametry. Oto przykład: # plik receptury: func_calc # Prosty kalkulator uruchamiany z wiersza poleceń function calc { # TYLKO LICZBY CAŁKOWITE! --> echo Wynikiem jest: $(( $* )) # Liczby rzeczywiste awk "BEGIN {print \"Wynikiem jest: \" $* }"; } # koniec funkcji calc[root@siemens skrypty]#

Do zmiany nazwy poleceń lub modyfikacji ich wywołania (zarówno w ramach konta jednego użytkownika, jak i w całym systemie) właściwsze okazują się aliasy i funkcje. Z kolei dowiązania symboliczne doskonale się sprawdzają wówczas, gdy trzeba zapewnić dostępność określonych poleceń w więcej niż jednym miejscu. Na przykład systemy Linux w odwołaniach do 406 |

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

pliku powłoki niemal zawsze korzystają ze ścieżki /bin/bash, natomiast inne systemy używają ścieżek /usr/bin/bash, /usr/local/bin/bash lub /usr/pkg/bin/bash. Choć w tym konkretnym przypadku korzystniejsze jest zastosowanie polecenia env (zagadnienie zostało opisane w recepturze 15.1, „Przenośność skryptu — problem wiersza #!”), generalnie dowiązania symboliczne stanowią efektywne rozwiązanie tego problemu. Nie zalecamy jednak stosowania dowiązań twardych, które są znacznie trudniejsze do zauważenia i mogą być łatwo zerwane przez błędnie działające edytory. Dowiązania symboliczne są znacznie łatwiejsze w użyciu.

Analiza Operacja podmiany aliasu dotyczy zazwyczaj jedynie pierwszego słowa instrukcji. Jeżeli jednak ostatnim znakiem ciągu aliasu jest znak spacji, sprawdzeniem zostanie objęty również wyraz kolejny. W praktyce jednak takie rozwiązanie jest rzadko stosowane. Ponieważ aliasy nie pobierają parametrów (z wyjątkiem powłoki csh), w przypadkach, w których ich przekazanie jest konieczne, trzeba zastosować funkcje. Funkcje, podobnie jak aliasy, są przechowywane w pamięci, więc nie ma istotnej różnicy w ich wykonywaniu. Operacja zastępowania aliasów nie jest realizowana w powłokach nieinterakcyjnych, chyba że zostanie włączona opcja expand_aliases. Doświadczenie uczy, że opracowując skrypt, nie powinno się polegać na aliasach, ponieważ nie zawsze są one definiowane w systemach docelowych. Również w takich przypadkach konieczne jest definiowanie funkcji w kodzie skryptu lub włączanie jej za pomocą instrukcji source (więcej informacji na ten temat znajduje się w recepturze 19.14, „Unikanie komunikatów «command not found» podczas korzystania z funkcji”). Najlepszym miejscem do definiowania funkcji jest zatem plik konfiguracji globalnej /etc/bashrc lub plik lokalny ~/.bashrc.

Zobacz również • Receptura 10.4, „Definiowanie funkcji”. • Receptura 10.5, „Wykorzystanie funkcji. Parametry i zwracane wartości”. • Receptura 10.7, „Zmiana definicji poleceń za pomocą aliasów”. • Receptura 14.4, „Usuwanie wszystkich aliasów”. • Receptura 15.1, „Przenośność skryptu — problem wiersza #!”. • Receptura 16.18, „Właściwe wykorzystanie plików startowych”. • Receptura 16.19. „Tworzenie samodzielnych, przenośnych plików RC”. • Receptura 16.20. „Uruchomienie powłoki z własną konfiguracją”. • Receptura 19.14, „Unikanie komunikatów «command not found» podczas korzystania

z funkcji”.

16.6. Skracanie i zmienianie nazw poleceń

|

407

16.7. Dostosowanie domyślnego sposobu działania powłoki i jej środowiska Problem Chcemy dostosować środowisko powłoki do własnych potrzeb. Chcemy zdefiniować własną lokalizację, ustawić odpowiedni język itp.

Rozwiązanie Propozycje konkretnych rozwiązań zostały zestawione w tabeli zamieszczonej w punkcie „Zmiana sposobu działania powłoki za pomocą instrukcji set, shopt i zmiennych środowiskowych”, w dodatku A.

Analiza Istnieją trzy techniki ustawiania różnych parametrów pracy powłoki. Pierwsza polega na wykorzystaniu zgodnej z zaleceniem POSIX instrukcji set i jednoliterowej opcji danego ustawienia. Zastosowanie polecenia shopt jest rozwiązaniem właściwym jedynie dla wyznaczania parametrów powłoki bash. Trzecia metoda bazuje na wykorzystaniu jednej z wielu zmiennych środowiskowych, które są udostępniane w celu zachowania zgodności z wcześniejszymi wersjami powłok oraz z aplikacjami firm zewnętrznych. Ustalenie właściwego sposobu zmiany określonego ustawienia może być nieco kłopotliwe. Dlatego wszystkie operacje tego typu zostały zestawione w tabeli zamieszczonej w punkcie „Zmiana sposobu działania powłoki za pomocą instrukcji set, shopt i zmiennych środowiskowych”, w dodatku A.

Zobacz również • Polecenie help set. • Polecenie help shopt. • Dokumentacja powłoki bash (http://www.bashcookbook.com). • Punkt „Zmiana sposobu działania powłoki za pomocą instrukcji set, shopt i zmiennych śro-

dowiskowych” w dodatku A.

16.8. Zmiana sposobu działania mechanizmu readline za pomocą skryptu .inputrc Problem Chcemy zmienić sposób przetwarzania przez powłokę danych wejściowych. Dotyczy to szczególnie mechanizmu uzupełniania poleceń. Chcemy, aby nie rozróżniał wielkości liter.

408 |

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

Rozwiązanie Należy utworzyć lub wyedytować plik ~/.inputrc lub /etc/inputrc. W ten sposób można zmienić wiele parametrów funkcjonowania mechanizmu readline. Jednak aby plik z parametrami został w ogóle uwzględniony podczas inicjalizacji mechanizmu readline, konieczne jest zdefiniowanie zmiennej $INPUTRC — na przykład w następujący sposób: set INPUTRC='~/.inputrc'. Aby plik konfiguracyjny został odczytany ponownie, a zmiany w nim zapisane zostały uwzględnione, wystarczy wykonać instrukcję bind –f nazwa_pliku. Przed zastosowaniem polecenia bind warto zapoznać się z jego dokumentacją oraz z dokumentacją mechanizmu readline. Jest to szczególnie istotne w przypadku korzystania z instrukcji bind –v, bind –s oraz bind –p. Niestety dokumentacja jest dość długa i trudna do przyswojenia. Poniżej zostało wymienionych kilka ustawień użytecznych przede wszystkim dla osób, które korzystały wcześniej z innych środowisk, w tym z systemu Windows (więcej informacji na ten temat znajduje się w dodatku A, w punkcie „Składnia pliku konfiguracyjnego mechanizmu readline”). # plik receptury: inputrc # # settings/inputrc: # ustawienia readline # Aby ponownie odczytać plik (i uaktywnić zmiany), należy użyć instrukcji # bind -f $SETTINGS/inputrc # Uwzględnienie systemowych dyrektyw odwzorowania # i instrukcji przypisania wartości zmiennym, które są # zapisane w pliku /etc/inputrc # ("ciche" zakończenie operacji, jeśli plik nie istnieje) $include /etc/inputrc $if Bash # Zignorowania informacji o wielkości liter podczas uzupełniania polecenia set completion-ignore-case on # Dołączenie znaku ukośnika do nazw katalogów (podczas uzupełniania nazwy) set mark-directories on # Dołączenie znaku ukośnika do nazw dowiązań wskazujących na katalogi #(podczas uzupełniania nazwy) set mark-symlinked-directories on # Wykorzystanie podczas uzupełniania polecenia instrukcji ls -F set visible-stats on # Cykliczna zmiana niejednoznacznych nazw zamiast wyświetlania listy "\C-i": menu-complete # Włączenie słyszalnego dzwonka set bell-style audible # Wyświetlenie listy możliwych nazw zamiast włączenia dzwonka set show-all-if-ambiguous on # # # # # # # # #

Ustawienia pobrane z dokumentacji mechanizmu readline dostępnej pod adresem http://tiswww.tis.case.edu/php/chet/readline/readline.html#SEC12 Makra użyteczne podczas pracy z powłoką edycja zmiennej $PATH "\C-xp": "PATH=${PATH}\e\C-e\C-a\ef\C-f" przygotowanie do wpisania słowa otoczonego znakami cudzysłowu -- wstawienie początkowego i końcowego znaku cudzysłowu i przesunięcie kursora za cudzysłów otwierający "\C-x\"": "\"\"\C-b" wstawienie znaku lewego ukośnika (testowanie symboli specjalnych w sekwencjach znaków i makrach) "\C-x\\": "\\"

16.8. Zmiana sposobu działania mechanizmu readline za pomocą skryptu .inputrc

| 409

# Otoczenie znakami cudzysłowu bieżacego lub poprzednioego słowa "\C-xq": "\eb\"\ef\"" # Odświeżenie wiersza - domyślnie nieprzypisane "\C-xr": redraw-current-line # Edycja zmiennej w bieżącym wierszu #"\M-\C-v": "\C-a\C-k$\C-y\M-\C-e\C-a\C-y=" "\C-xe": "\C-a\C-k$\C-y\M-\C-e\C-a\C-y=" $endif

Warto poeksperymentować zarówno z przedstawionymi, jak i z innymi ustawieniami. Nie można jednak zapomnieć o włączeniu ($include) ustawień systemowych, które oczywiście również można dostosować do własnych potrzeb. Szczegółowe informacje na temat włączania plików konfiguracyjnych zostały zamieszczone w recepturze 16.20, „Uruchomienie powłoki z własną konfiguracją”.

Analiza Wielu użytkowników systemu nie wie o możliwości dostosowania środowiska pracy. Nie zna też funkcji biblioteki Readline projektu GNU i nie ma pojęcia o jej niezwykłej elastyczności. Niestety nie ma takiej konfiguracji domyślnej, która odpowiadałaby wszystkim. Dlatego chcąc dostosować sposób działania powłoki do własnych potrzeb, trzeba poświęcić trochę czasu na opracowanie właściwych ustawień. W trakcie uruchamiania mechanizm readline odczytuje pliki konfiguracyjne wymienione w zmiennej $INPUTRC lub, jeśli zmienna nie jest zdefiniowana, poszukuje domyślnego pliku ~/.inputrc.

Zobacz również • Polecenie help bind. • Dokumentacja mechanizmu readline dostępna pod adresem http://www.bashcookbook.com. • Receptura 16.19, „Tworzenie samodzielnych, przenośnych plików RC”. • Receptura 16.20, „Uruchomienie powłoki z własną konfiguracją”.

16.9. Własny zbiór narzędzi — dodanie ścieżki ~/bin Problem Dysponujemy zbiorem często wykorzystywanych programów, ale z powodu braku dostępu do konta root nie możemy ich zapisać w typowych dla narzędzi katalogach /bin lub /usr/local/bin. Analogiczny problem występuje również wtedy, gdy musimy z pewnych względów odseparować określony zbiór programów od innych aplikacji.

Rozwiązanie Należy utworzyć katalog ~/bin, umieścić w nim wszystkie wykorzystywane narzędzia programowe i dodać katalog do zmiennej $PATH. $ PATH="$PATH:~/bin"

410

|

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

Instrukcję zmiany wartości zmiennej $PATH można również umieścić w jednym z plików startowych, na przykład w pliku ~/.bashrc. Warto jednak najpierw sprawdzić, czy w wykorzystywanym systemie katalog $HOME/bin nie jest dołączany do listy ścieżek w sposób automatyczny.

Analiza Doświadczony użytkownik powłoki (a tylko tacy kupują tę książkę) z pewnością będzie tworzył wiele skryptów. Uruchamianie własnych skryptów z użyciem pełnej ścieżki dostępu do pliku jest bardzo niewygodne. Dzięki zapisaniu programów w katalogu ~/bin wydają się one standardowymi narzędziami systemu Unix — przynajmniej dla użytkownika, który zapisał je we wspomnianym katalogu. Ze względów bezpieczeństwa nie należy umieszczać ścieżki dostępu do prywatnego katalogu bin na początku ciągu $PATH. Dołączenie wartości ~/bin na początku ciągu zmiennej $PATH niesie ryzyko przesłonięcia poleceń systemowych — co bywa bardzo mylące w przypadku omyłkowego użycia takiego polecenia oraz bardzo niebezpieczne w przypadku celowego nadpisania polecenia systemowego.

Zobacz również • Receptura 14.9, „Wyszukiwanie w zmiennej $PATH katalogów umożliwiających modyfi-

kowanie zawartości”. • Receptura 14.10, „Dodawanie bieżącego katalogu do listy $PATH”. • Receptura 16.3, „Trwała zmiana wartości $PATH”. • Receptura 16.4, „Chwilowa zmiana wartości $PATH”. • Receptura 16.6, „Skracanie i zmienianie nazw poleceń”. • Receptura 19.4, „Nadawanie skryptowi nazwy test”.

16.10. Wykorzystanie dodatkowych znaków zachęty — $PS2, $PS3, $PS4 Problem Chcemy poznać przeznaczenie znaków zachęty $PS2, $PS3 i $PS4.

Rozwiązanie Ciąg przechowywany w zmiennej $PS2 jest nazywany drugim znakiem zachęty i jest wykorzystywany podczas interaktywnego wprowadzania poleceń, które nie zostały zakończone w pierwszym wierszu. Zazwyczaj składa się z jednego znaku >, ale nic nie stoi na przeszkodzie, żeby treść zgłoszenia została zmieniona. Oto przykład:

16.10. Wykorzystanie dodatkowych znaków zachęty — $PS2, $PS3, $PS4

|

411

[marek@fedora zadania:0] /home/marek$ export PS2='Drugi: ' [marek@fedora zadania:0] /home/marek$ for i in $(ls) Drugi: do Drugi: echo $i Drugi: done bin colors func_calc

Zmienna $PS3 przechowuje znak zachęty dla polecenia select, który jest wyświetlany w chwili, gdy użytkownik musi wybrać odpowiednią opcję menu. Jego domyślną wartością jest intuicyjny ciąg #?. Aby zmienić treść zgłoszenia, wystarczy przed wywołaniem instrukcji select przypisać zmiennej $PS2 nową wartość. [marek@fedora zadania:0] /home/marek$ select i in $(ls) Drugi: do Drugi: echo $i Drugi: done 1) bin 2) colors 3) func_calc #? 1 bin #? ^C [marek@fedora zadania:0] /home/marek$ export PS3='Wybierz katalog do wyświetlenia: ' [marek@fedora zadania:0] /home/marek$ select i in $(ls); do echo $i; done 1) bin 2) colors 3) func_calc Wybierz katalog do wyświetlenia: 2 colors Wybierz katalog do wyświetlenia: ^C

Ciąg zapisany w zmiennej $PS4 jest wyświetlany podczas debugowania skryptu. Liczba powtórzeń pierwszego znaku ciągu odzwierciedla poziom zagnieżdżenia kodu. Domyślnym symbolem jest w tym przypadku znak (+). Oto przykład: [marek@fedora zadania:0] /home/marek$ cat demo #!/usr/bin/env bash set -o xtrace alice=dziewczyna echo "$alice" ls -l $(type -path vi) echo wiersz 10 ech0 wiersz 11 echo wiersz 12 [marek@fedora zadania:0] /home/marek$ ./demo +alice=dziewczyna +echo dziewczyna

412

|

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

dziewczyna ++type -path vi +ls -l /bin/vi -rwxr-xr-x 1 root root 594740 sty 7 2007 /bin/vi +echo wiersz 10 wiersz 10 +ech0 wiersz 11 ./demo: line 11: ech0: command not found +echo wiersz 12 wiersz 12 [marek@fedora zadania:0] /home/marek$ export PS4='xtrace $LINENO: ' [marek@fedora zadania:0] /home/marek$ ./demo +xtrace 5: alice=dziewczyna +xtrace 6: echo dziewczyna dziewczyna ++xtrace 8: type -path vi +xtrace 8: ls -l /bin/vi -rwxr-xr-x 1 root root 594740 sty 7 2007 /bin/vi +xtrace 10: echo wiersz 10 wiersz 10 +xtrace 11: ech0 wiersz 11 ./demo: line 11: ech0: command not found +xtrace 12: echo wiersz 12 wiersz 12

Analiza W zgłoszeniu $PS4 została wykorzystana zmienna $LINENO, która w przypadku wywołania funkcji powłoki bash w wersji wcześniejszej niż 2.0 zwraca informację o liczbie wykonanych poleceń, a nie numer realizowanego wiersza funkcji. Istotne jest tu również zastosowanie znaków apostrofu, które zapobiegają podmianie wartości zmiennej, aż do chwili wyświetlenia znaku zachęty.

Zobacz również • Receptura 1.1, „Rozszyfrowanie znaku zachęty”. • Receptura 3.7, „Wybór opcji z listy”. • Receptura 6.16, „Utworzenie prostego menu”. • Receptura 6.17, „Zmiana znaku zachęty w tekstowym menu”. • Receptura 16.2, „Dostosowanie znaku zachęty”. • Receptura 19.13, „Śledzenie przebiegu skryptów”.

16.10. Wykorzystanie dodatkowych znaków zachęty — $PS2, $PS3, $PS4

|

413

16.11. Synchronizowanie historii poleceń wprowadzanych w różnych sesjach Problem Korzystamy z większej liczby sesji bash w jednej chwili i chcemy dysponować wspólnym zbiorem informacji o wprowadzonych instrukcjach. Zakończenie ostatniej sesji nie powinno spowodować usunięcia danych historycznych z pracy innych sesji.

Rozwiązanie Do ręcznego lub automatycznego synchronizowania danych historycznych między sesjami trzeba wykorzystać polecenie history.

Analiza Domyślne ustawienia powodują, że podczas kończenia pracy ostatniej sesji powłoki plik historii poleceń zostaje nadpisany. Jeśli więc w tym momencie nie jest zsynchronizowany z innymi sesjami, dane historyczne zostaną usunięte. Pomocne może tu być zastosowanie opcji powłoki, które zostały opisane w recepturze 16.12, „Włączanie opcji związanych z historią poleceń”. Niemniej osobiste zadbanie o synchronizację historii poleceń między sesjami daje pewne dodatkowe korzyści. Manualne synchronizowanie pliku historii poleceń sprowadza się do utworzenia aliasu, który zapewni dodanie bieżących informacji historycznych do pliku, a następnie odczyta nowo dodane wpisy i przekaże do bieżącej sesji powłoki. $ history –a $ history –n # Alias 'historia – synchornizacja' alias hs='history –a; history –n'

Wadą tego rozwiązania jest konieczność ręcznego wprowadzania poleceń w każdej powłoce, która powinna zsynchronizować swoje informacje historyczne. Aby zautomatyzować procedurę synchronizacji, można wykorzystać zmienną $PROMPT_COMMAND. PROMPT_COMMAND='history –a; history –n'

Wartość zmiennej $PROMPT_COMMAND jest interpretowana jako polecenie przeznaczone do wykonania przed każdorazowym wyświetleniem znaku zachęty $PS1 w powłoce interaktywnej. Wadą tego rozwiązania jest to, że instrukcja będzie realizowana za każdym razem, gdy wyświetlana jest treść $PS1, czyli bardzo często. Mocno obciążone systemy oraz starsze komputery mogą więc spowolnić pracę powłoki, szczególnie wtedy, gdy liczba wpisów w pliku historii jest dość duża.

414

|

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

Zobacz również • Polecenie help history. • Receptura 16.12, „Włączanie opcji związanych z historią poleceń”.

16.12. Włączanie opcji związanych z historią poleceń Problem Chcemy mieć większą kontrolę nad sposobem działania mechanizmu historii poleceń.

Rozwiązanie Należy dostosować wartości zmiennych $HIST* oraz opcji powłoki.

Analiza Zmienna $HISTFILESIZE wyznacza maksymalną liczbę wierszy historii poleceń zapisywanych w pliku $HISTFILE. Domyślna wartość parametru $HISTFILESIZE wynosi 500 wierszy, a parametr $HISTFILE ma wartość ~/.bash_history. Wyjątkiem jest praca w trybie zgodności z zaleceniami POSIX, podczas której zmienna $HISTFILE odnosi się do pliku ~./sh_history. Zwiększenie liczby $HISTFILESIZE czasami może się okazać korzystne. Usunięcie zmiennej $HISTFILESIZE powoduje zniesienie jakichkolwiek ograniczeń rozmiaru pliku historii. Zmiana ustawienia $HIST ´FILE rzadko bywa zasadna. Wyjątkiem mogą być sytuacje, w których zmienna nie jest zdefiniowana lub wskazuje plik, którego nie można modyfikować. W takich przypadkach żadne polecenia nie są zapamiętywane w formie instrukcji historycznych. Liczbę poleceń przechowywanych w pamięci definiuje zmienna $HISTSIZE. Zmienne $HISTIGNORE i $HISTCONTROL określają, które instrukcje będą zapisywane w pliku historii. Parametr $HISTIGNORE cechuje się większą elastycznością, ponieważ umożliwia definiowanie wzorców opisujących polecenia, które powinny zostać zarejestrowane w pliku historii. Zmienna $HISTCONTROL może przechowywać jedynie kilka słów kluczowych, których znaczenie zostało opisane poniżej. ignorespace

W pliku historii nie należy zapisywać poleceń, których treść rozpoczyna się od znaku spacji. ignoredups

W pliku historii nie należy zapisywać poleceń, które dokładnie odpowiadają poleceniu zapisanemu jako ostatnie (nie należy zapisywać duplikatów). ignoreboth

Połączenie ustawień ignorespace i ignoredups.

erasedups

Przed zapisaniem instrukcji w pliku historii wszystkie wcześniejsze wpisy odpowiadające danemu poleceniu muszą zostać usunięte.

16.12. Włączanie opcji związanych z historią poleceń

|

415

Jeżeli zmienna $HISTCONTROL nie jest zdefiniowana lub nie zawiera żadnego z wymienionych słów kluczowych, wszystkie polecenia są rejestrowane i analizowane zgodnie z ustawieniem $HISTIGNORE. Drugie i kolejne wiersze wieloelementowych poleceń nie są sprawdzane i są dodawane do pliku historii niezależnie od ustawienia parametru $HISTCONTROL. Informacje zawarte w powyższych akapitach zostały zaczerpnięte z podręcznika GNU Bash Reference Manual opisującego działanie powłoki bash w wersji 2.05b. Data ostatniej aktualizacji podręcznika to 15 lipca 2002 roku. Dokument jest dostępny pod adresem http://www.gnu.org/software/ ´bash/manual/bashref.html. Z chwilą wydania oprogramowania bash w wersji 3., użytkownicy zyskali możliwość korzystania z bardzo użytecznej zmiennej $HISTTIMEFORMAT. Jej wartość określa ciąg formatu dla polecenia strftime, który jest analizowany podczas wyświetlania oraz zapisywania poszczególnych wierszy pliku historii. Osoby, które nie korzystają z powłoki bash w wersji 3., ale posługują się terminalem umożliwiającym odwołania do wcześniej wprowadzonych poleceń, mogą dodać informacje o dacie i czasie do znaku zachęty. Więcej informacji na ten temat znajduje się w recepturze 16.2, „Dostosowanie znaku zachęty”. Standardowo interpreter bash nie umieszcza znaku spacji za ciągiem czasu, ale w niektórych systemach (np. w systemie Debian) domyślnie są instalowane nakładki rozwiązujące ten problem. bash-3.1# history 1 ls -la 2 help history 3 help fc 4 history # Nieczytelny zapis bash-3.1# export HISTTIMEFORMAT='%Y-%m-%d_%H:%M:%S' bash-3.1# history 1 2007-11-08_07:29:09ls -la 2 2007-11-08_07:29:14help history 3 2007-11-08_07:29:17help fc 4 2007-11-08_07:30:51history 5 2007-11-08_07:31:24export HISTTIMEFORMAT='%Y-%m-%d_%H:%M:%S' 6 2007-11-08_07:31:27history # Czytelniejsza forma bash-3.1# export HISTTIMEFORMAT='%Y-%m-%d_%H:%M:%S; ' bash-3.1# history 1 2007-11-08_07:29:09;ls -la 2 2007-11-08_07:29:14;help history 3 2007-11-08_07:29:17;help fc 4 2007-11-08_07:29:21;history 5 2007-11-08_07:31:24;export HISTTIMEFORMAT='%Y-%m-%d_%H:%M:%S' 6 2007-11-08_07:31:27;history 7 2007-11-08_07:32:50;export HISTTIMEFORMAT='%Y-%m-%d_%H:%M:%S; ' 8 2007-11-08_07:32:53;history # Format użyteczny bash-3.1# export HISTTIMEFORMAT=': %Y-%m-%d_%H:%M:%S; ' bash-3.1# history 1 : 2007-11-08_07:29:09; 2 : 2007-11-08_07:29:14; 3 : 2007-11-08_07:29:17; 4 : 2007-11-08_07:29:21; 5 : 2007-11-08_07:31:24;

416

|

ls -la help history help fc history export HISTTIMEFORMAT='%Y-%m-%d_%H:%M:%S'

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

6 7 8 9 10

: : : : :

2007-11-08_07:31:27; 2007-11-08_07:32:50; 2007-11-08_07:32:53; 2007-11-08_07:34:00; 2007-11-08_07:34:02;

history export HISTTIMEFORMAT='%Y-%m-%d_%H:%M:%S;' history export HISTTIMEFORMAT=': %Y-%m-%d_%H:%M:%S; ' history

Zastosowanie w ostatnim przykładzie wbudowanego polecenia : wraz z metaznakiem ; powoduje objęcie znacznika czasowego instrukcją „nie rób nic” (np. : 2007-11-08_07:31:27). Dzięki temu użytkownik może powtórnie wykorzystać wiersz polecenia z pliku historii bez konieczności wycinania ciągu znacznika czasu. Za znakiem : musi występować znak spacji. Zasady przetwarzania pliku historii są również regulowane przez ustawienia powłoki. Na przykład włączenie opcji histappend powoduje, że dane są dołączane do pliku historii. W przeciwnym przypadku plik będzie nadpisywany. Nie zmienia to faktu, że liczba wpisów jest ograniczona parametrem $HISTSIZE. Jeżeli zostanie włączona opcja cmdhist, wielowierszowe polecenia zostaną zarejestrowane w formie instrukcji jednowierszowej z odpowiednio dodanymi znakami średnika. W przypadku włączenia opcji listhist wielowierszowe polecenia są rejestrowane wraz ze znakami nowego wiersza.

Zobacz również • Polecenie help history. • Polecenie help fc. • Receptura 16.2, „Dostosowanie znaku zachęty”. • Receptura 16.7, „Dostosowanie domyślnego sposobu działania powłoki i jej środowiska”. • Receptura 16.11, „Synchronizowanie historii poleceń wprowadzanych w różnych sesjach”.

16.13. Utworzenie lepszego polecenia cd Problem Wykorzystujemy polecenie cd do przemieszczania się między różnymi katalogami zapisanymi na odległych poziomach struktury katalogów. Chcemy zmieniać katalogi o cztery poziomy w kierunku katalogu głównego za pomocą instrukcji cd . zamiast polecenia cd ../../../...

Rozwiązanie Wystarczy wykorzystać następującą funkcję: # plik receptury: func_cd # Funkcja umożliwia wykorzystanie polecenia 'cd ...' do zmiany katalogów o 2 poziomy # w górę, 'cd ....' do zmiany o 3 poziomy w górę itd. (podobnie jak 4NT/4DOS) # Użycie: cd ..., itp. function cd { local option= length= count= cdpath= i= # Zasięg lokalny i pusta wartość początkowa

16.13. Utworzenie lepszego polecenia cd

|

417

# Jeśli zostały podane opcje -L lub -P (dowiązania symbolicznego), należy je # zachować, a następnie usunąć if [ "$1" = "-P" -o "$1" = "-L" ]; then option="$1" shift fi # Czy jest wykorzystywana specjalna składnia? Sprawdzenie, czy zmienna $1 nie jest # pusta. Sprawdzenie, czy trzy pierwsze znaki $1 to '...'. Sprawdzenie, czy nie # występuje znak ukośnika - jeżeli operacja podstawienia się nie powiedzie, # znak ukośnika nie występuje. Wykonanie procedur startowych jest możliwe tylko w # powłokach bash 2.0 i wersjach późniejszych. if [ -n "$1" -a "${1:0:3}" = '...' -a "$1" = "${1%/*}" ]; then # Specjalna składnia jest wykorzystywana length=${#1} # Zakładamy, że zmienna $1 przechowuje tylko znaki kropki, # które należy zliczyć. count=2 # 'cd ..' nadal oznacza przejście o jeden poziom w górę, # pierwsze dwa znaki są więc ignorowane. # Przechodzenie o jeden katalog w górę, aż do wyczerpania znaków kropki for ((i=$count;i ' # Drugi znak zachęty (tj. znak # kontynuacji) #export PS3='Please make a choice: ' # Zgłoszenie instrukcji select export PS4='+xtrace $LINENO: ' # Zgłoszenie mechanizmu debugowania # xtrace ;; esac # Jeżeli uda się odnaleźć plik inputrc, zostanie on wykorzystany. # Przeszukiwanie obejmuje kilka nazw. Warto również zwrócić uwagę na

440 |

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

# kolejność ich występowania. Ustawienia użytkownika powinny być ważniejsze # niż ustawienia systemowe. for file in $SETTINGS/inputrc ~/.inputrc /etc/inputrc; do [ -r "$file" ] && export INPUTRC="$file" && break # Wykorzystanie pierwszego # znalezionego done # Bez plików zrzutu (domyślnie) # W wielu systemach ustawienie to jest definiowane w pliku /etc/security/limits.conf ulimit -S -c 0 > /dev/null 2>&1 # Naciśnięcie Ctrl+D nie powinno kończyć pracy powłoki set -o ignoreeof # Parametry rejestracji poleceń export HISTSIZE=5000 # Liczba poleceń przechowywanych w pamięci export HISTFILESIZE=5000 # Liczba poleceń przechowywanych w pliku historii export HISTCONTROL=ignoreboth # bash < 3, pominięcie duplikatów i wierszy # rozpoczynających się od spacji export HISTIGNORE='&:[ ]*' # bash >= 3, pominięcie duplikatów i wierszy # rozpoczynających się od spacji #export HISTTIMEFORMAT='%Y-%m-%d_%H:%M:%S_%Z=' # bash >= 3, znacznik czasu shopt -s histappend # Dodawanie wpisów zamiast nadpisywania pliku historii shopt -q -s cdspell # Automatyczna korekta prostych błędów w użyciu # polecenia 'cd' shopt -q -s checkwinsize # Uaktualnienie wartości zmiennych LINES i COLUMNS shopt -q -s cmdhist # Zapisanie wielowierszowych poleceń jako # jednowierszowych set -o notify # (or set -b) # Natychmiastowe powiadamianie o zakończeniu procesów # drugoplanowych # Inne ustawienia powłoki bash export LC_COLLATE='C' # Włączenie standardowej kolejności sortowania (tj. # najpierw wielkie litery) export HOSTFILE='/etc/hosts' # Uzupełnianie nazw jednostek na podstwie pliku # /etc/hosts export CDPATH='~/:.:..:../..' # Zmienna o działaniu podobnym do $PATH, ale # przeznaczona dla polecenia 'cd' # Znak '.' jest niezbędny, aby zmienna $CDPATH była poprawnie przetwarzana w trybie # POSIX, ale jednocześnie powoduje przekazanie do strumienia STDOUT nazwy katalogu. # # # # #

Import ustawień mechanizmu uzupełniania poleceń, jeśli są zapisane w standardowym pliku. W powolnych systemach operacja ta może zająć parę sekund, więc dołączanie ich nie zawsze jest wskazane, nawet jeśli zostały zdefiniowane (w wielu systemach ustawienie to nie jest uwzględniane - np. w systemie Red Hat Linux). [ -r /etc/bash_completion ] && source /etc/bash_completion

# Wykorzystanie filtru lesspipe (jeśli uda się go odnaleźć). Ustawienie zmiennej # $LESSOPEN. Zastąpienie znaku ':' w treści $PATH znakami spacji pozwala na # wykorzystanie ciągu $PATH w pętli for path in $SETTINGS /opt/bin ~/ ${PATH//:/ }; do # Wykorzystanie pierwszego znalezionego pliku 'lesspipe.sh' (preferowane) lub # 'lesspipe' (Debian) [ -x "$path/lesspipe.sh" ] && eval $("$path/lesspipe.sh") && break [ -x "$path/lesspipe" ] && eval $("$path/lesspipe") && break done # Ustawienie innych parametrów polecenia less i edytorów tekstowych export LESS="--LONG-PROMPT --LINE-NUMBERS --QUIET" export VISUAL='vi' # Edytor domyślny (zawsze dostępny) # Można by w tym miejscu zastosować instrukcję 'type -P', ale została ona # dodana do powłoki bash-2.05b, a korzystamy także ze starszych systemów. # Nie można również zastosować instrukcji 'which', gdyż w niektórych systemach # zwraca ona wynik tylko wtedy, gdy plik istnieje.

16.20. Uruchomienie powłoki z własną konfiguracją

|

441

for path in ${PATH//:/ }; do # Nadpisanie zmiennej VISUAL, jeśli jest edytor nano [ -x "$path/nano" ] \ && export VISUAL='nano --smooth --const --nowrap --suspend' && break done # Zobacz uwagę dotyczącą wyszukania edytora nano for path in ${PATH//:/ }; do # Utworzenie aliasu vi dla edytora vim w trybie binarnym [ -x "$path/vim" ] && alias vi='vim -b' && break done export EDITOR="$VISUAL" # Jeszcze inna możliwość export SVN_EDITOR="$VISUAL" # System Subversion alias edit=$VISUAL # Polecenie działające we wszystkich systemach # Ustawienie opcji polecenia ls i aliasów # Ustawienia związane z kolorami mogą nie działać w danym terminalu. # Nie przeszkadzają jednak w prawidłowym jego funkcjonowaniu. # Powód wykorzystania takiej instrukcji pętli został opisany w uwadze do edytora nano for path in ${PATH//:/ }; do [ -r "$path/dircolors" ] && eval "$(dircolors)" \ && LS_OPTIONS='--color=auto' && break done export LS_OPTIONS="$LS_OPTIONS -F -h" # Użycie pliku dircolors może spowodować przerwanie pracy skryptu csh z błędem # "Unknown colorls variable `do'.". Jest to spowodowane występowaniem kodu # ":do=01;35:"w treści zmiennej środowiskowej LS_COLORS. Rozwiązanie problemu zostało # opisane na stronie http://forums.macosxhints.com/showthread.php?t=7287 # eval "$(dircolors)" alias ls="ls $LS_OPTIONS" alias ll="ls $LS_OPTIONS -l" alias ll.="ls $LS_OPTIONS -ld" # Użycie: ll. ~/.* alias la="ls $LS_OPTIONS -la" # Użyteczne aliasy alias bot='cd $(dirname $(find . | tail -1))' alias clr='cd ~/ && clear' # Wyczyszczenie ekranu i przejście do katalogu $HOME alias cls='clear' # DOS-owa wersja instrukcji clear alias copy='cp' # DOS-owa wersja instrukcji cp #alias cp='cp -i' # Irytujące ustawienie systemu Red Hat z pliku # /root/.bashrc alias cvsst='cvs -qn update' # Spójny status CVS (podobnie jak svn st) alias del='rm' # DOS-owa wersja instrukcji rm alias diff='diff -u' # Domyślne użycie zunifikowanego formatu wyjściowego diff alias jdiff="diff --side-by-side --ignore-case --ignore-blank-lines\ --ignore-all-space --suppress-common-lines" # Użyteczne opcje polecenia GNU diff alias dir='ls' # DOS-owa wersja polecenia ls alias hr='history -a && history -n' # Dodanie bieżącej historii do pliku i ponowne jego odczytanie alias ipconfig='ifconfig' # Windowsowa wersja polecenia ifconfig alias md='mkdir' # DOS-owa wersja polecenia mkdir alias move='mv' # DOS-owa wersja polecenia mv #alias mv='mv -i' # Irytujące ustwienie systemu Red Hat z pliku /root/.bashrc alias ntsysv='rcconf' # Instrukcja rcconf w systemie Debian jest zbliżona do # instrukcji ntsysv systemu Red Hat alias pathping='mtr' # mtr - sieciowe narzędzie diagnostyczne alias r='fc -s' # Przywrócenie i uruchomienie 'polecenia' rozpoczynającego # się od... alias rd='rmdir' # DOS-owa wersja polecenia rmdir alias ren='mv' # DOS-owa wersja polecenia mv/rename #alias rm='rm -i' # Irytujące ustwienie systemu Red Hat z pliku # /root/.bashrc alias svnpropfix='svn propset svn:keywords "Id URL"'

442 |

Rozdział 16. Konfiguracja i dostosowanie powłoki bash

alias tracert='traceroute' # DOS-owa wersja polecenia traceroute alias vzip='unzip -lvM' # Przeglądanie zawartości pliku ZIP alias wgetdir="wget --non-verbose --recursive --no-parent --no-directories\ --level=1" # Pobranie całej zawartości katalogu za pomocą # polecenia wget alias zonex='host -l' # Zrzut strefy DNS # Jeśli skrypt istnieje i jest wykonywalny, tworzony jest alias do pobierania # nagłówków z serwera WWW for path in ${PATH//:/ }; do [ -x "$path/lwp-request" ] && alias httpdinfo='lwp-request -eUd' && break done # Przyspieszenie działania klawiatury, ale bez zgłaszania błędu, jeśli nie ma pliku # kbdrate. Jeśli nie ma pliku, najłatwiejszym i najszybszym rozwiązaniem jest # zignorowanie błędu. kbdrate -r 30.0 -d 250 &> /dev/null # Użyteczne funkcje # utworzenie nowego katalogu i przeście do niego # użycie: mcd () function mcd { local newdir='_polecenie_mcd_zawiodło_' if [ -d "$1" ]; then # Katalog istnieje - komunikat echo "$1 istnieje..." newdir="$1" else if [ -n "$2" ]; then # Określenie praw dostępu command mkdir -p -m $1 "$2" && newdir="$2" else # Standardowe polecenie mkdir command mkdir -p "$1" && newdir="$1" fi fi builtin cd "$newdir" # Przejście do katalogu } # koniec funkcji mcd

# Prosty kalkulator uruchamiany z wiersza poleceń function calc { # TYLKO LICZBY CAŁKOWITE! --> echo Wynikiem jest: $(( $* )) # Liczby rzeczywiste awk "BEGIN {print \"Wynikiem jest: \" $* }"; } # koniec funkcji calc # Funkcja umożliwia wykorzystanie polecenia 'cd ...' do zmiany katalogów o 2 poziomy # w góre, 'cd ....' do zmiany o 3 poziomy w góre itd. (podobnie jak 4NT/4DOS) # Użycie: cd ..., itp. function cd { local option= length= count= cdpath= i= # Zasięg lokalny i pusta wartość # początkowa # Jeśli zostały podane opcje -L lub -P (dowiązania symbolicznego), należy je # zachować, a następnie usunąć if [ "$1" = "-P" -o "$1" = "-L" ]; then option="$1" shift fi

16.20. Uruchomienie powłoki z własną konfiguracją

| 443

# Czy jest wykorzystywana specjalna składnia? Sprawdzenie, czy zmienna $1 nie jest # pusta. Sprawdzenie, czy trzy pierwsze znaki $1 to '...'. Sprawdzenie, czy nie # występuje znak ukośnika - jeżeli operacja podstawienia się nie powiedzie, # znak ukośnika nie występuje. Wykonanie procedur startowych jest możliwe tylko # w powłokach bash 2.0 i wersjach późniejszych. if [ -n "$1" -a "${1:0:3}" = '...' -a "$1" = "${1%/*}" ]; then # Specjalna składnia jest wykorzystywana length=${#1} # Zakładamy, że zmienna $1 przechowuje tylko znaki kropki, # które należy zliczyć. count=2 # 'cd ..' nadal oznacza przejście o jeden poziom w górę, # pierwsze dwa znaki są więc ignorowane. # Przechodzenie o jeden katalog w górę, aż do wyczerpania znaków kropki for ((i=$count;i &/; /^$/d"

W instrukcji Perl zamiast znaków cudzysłowu, które trzeba by poprzedzać znakami lewego ukośnika, została wykorzystana funkcja qq(). Ostatni fragment kodu definiuje wyrażenie regularne, które dopasowuje wiersze puste lub zawierające znaki odstępu. Symbol $_ w języku Perl oznacza bieżący wiersz. W poleceniu sed każdy wiersz zawierający co najmniej jeden znak jest zastępowany prefiksem i znakiem odpowiadającym symbolowi (&). Wszystkie puste wiersze są usuwane.

484 |

Rozdział 17. Zadania administracyjne

Zobacz również • Książka Arnolda Robbinsa Effective awk Programming (O’Reilly 2001). • Książka Arnolda Robbinsa i Dale’a Dougherty’ego sed i awk (Helion 2002). • Receptura 1.6, „Cudzysłowy i apostrofy w instrukcjach powłoki”. • Receptura 13.14, „Usuwanie krańcowych znaków odstępu”. • Receptura 13.17, „Przetwarzanie plików niezawierających znaków nowego wiersza”.

17.21. Numerowanie wierszy Problem Chcemy ponumerować wiersze tekstu, aby można było łatwiej się do nich odnosić.

Rozwiązanie Dziękujemy Michaelowi Wangowi za wkład w opracowanie rozwiązania bazującego tylko i wyłącznie na mechanizmach powłoki oraz z przypomnienie nam o istnieniu opcji –n w poleceniu cat. Ostatni wiersz w pliku dane jest wierszem pustym. $ 1 2 3 4 5 6

i=0; while IFS= read -r line; do (( i++ )); echo "$i $line"; done < dane Wiersz 1 Wiersz 2 Wiersz 4 Wiersz 5

To samo zadanie realizuje polecenie cat: $ cat -n dane 1 Wiersz 2 Wiersz 3 4 Wiersz 5 Wiersz 6

1 2 4 5

$ cat -b dane 1 Wiersz 1 2 Wiersz 2 3 4

Wiersz 4 Wiersz 5

Analiza Aby dołączyć numery wierszy jedynie podczas wyświetlania zestawienia na ekranie, można również użyć polecenia less –N: $ /usr/bin/less –N dane 1 Wiersz 1 2 Wiersz 2

17.21. Numerowanie wierszy

| 485

3 4 Wiersz 4 5 Wiersz 5 6 dane (END)

W starszych wersjach polecenia less i niektórych wcześniejszych systemach Red Hat opcja numerowania wierszy działa niepoprawnie. Polecenie less –V pozwala na ustalenie wykorzystywanej wersji programu. Wersje 358+iso254 (dostępne na przekład w systemach Red Hat 7.3 i 8.0) zawierają błąd. Wersje 378+iso254 (z systemu RHEL3) oraz wersja 382 (RHEL4 i Debian Sarge) na pewno działają poprawnie. Problem może być związany z nakładką iso254. Można to łatwo sprawdzić, włączając numerację wierszy w przykładach vi lub Perl.

Ten sam rezultat daje wykonanie instrukcji :set nu! w edytorze vi (lub view, który jest wersją edytora vi, przeznaczoną jedynie do przeglądania dokumentów). $ vi filename 1 Wiersz 2 Wiersz 3 4 Wiersz 5 Wiersz 6 ~ :set nu!

1 2 4 5

Edytor vi udostępnia wiele opcji. Na przykład wywołanie go za pomocą polecenia vi +3 –c 'set nu!' nazwa_pliku powoduje automatyczne włączenie numeracji i ustawienie kursora w trzecim wierszu. Większą kontrolę nad sposobem numerowania poszczególnych wierszy dokumentu zapewniają takie narzędzia, jak nl, awk czy perl: $ nl dane 1 Wiersz 1 2 Wiersz 2 3 4

Wiersz 4 Wiersz 5

$ nl -ba dane 1 Wiersz 2 Wiersz 3 4 Wiersz 5 Wiersz 6

1 2 4 5

$ 1 2 3 4 5 6

awk '{ print NR, $0 }' dane Wiersz 1 Wiersz 2

$ 1 2 3 4 5 6

perl -ne 'print qq($.\t$_);' dane --> Wiersz 1 --> Wiersz 2 --> --> Wiersz 4 --> Wiersz 5 -->

486 |

Wiersz 4 Wiersz 5

Rozdział 17. Zadania administracyjne

Zmienne NR i $. reprezentują w kodzie awk i Perl numer aktualnie analizowanego wiersza pliku danych. Nic więc nie stoi na przeszkodzie, żeby ich wartości zostały wyświetlone na ekranie. Symbol --> zastępuje znak tabulatora generowany przez kod Perl. Program awk domyślnie wykorzystuje do tego celu znaki spacji.

Zobacz również • Polecenie man cat. • Polecenie man nl. • Polecenie man awk. • Polecenie man less. • Polecenie man vi. • Receptura 8.15, „Dodatkowe funkcje polecenia less”.

17.22. Generowanie sekwencji liczbowych Problem Chcemy wygenerować sekwencję wartości liczbowych uzupełnionych ewentualnie o dodatkowe ciągi tekstowe. Procedura taka pozwala na przykład na przygotowanie danych do testów.

Rozwiązanie Zastosowanie programu awk daje gwarancję, że kod będzie działał w każdym systemie. $ 1 2 3 4 5

awk 'END { for (i=1; i > witaj

Kolejne polecenie pozwala na sprawdzenie statusu magazynu lokalnego. Instrukcja zastosowana w tym przypadku jest szczególną wersją polecenia, która generuje skrócony listing statusowy. Zasadnicza instrukcja wyświetlenia informacji statusowych zwraca zbyt dużo danych. /home/jp/skrypty$ cvs status cvs status: Examining . =================================================================== File: witaj Status: Locally Modified Working revision: Repository revision: Sticky Tag: Sticky Date: Sticky Options:

1.1.1.1 Mon Nov 19 10:29:27 2007 1.1.1.1 /home/jp/cvsroot/skrypty/witaj,v (none) (none) (none)

/home/jp/skrypty$ cvs -qn update M witaj

Dodajmy nowy skrypt do systemu kontroli wersji: /home/jp/skrypty$ cat mcd > #!/bin/sh > mkdir -p "$1" > cd "$1" > EOF /home/jp/skrypty$ cvs add mcd cvs add: scheduling file `mcd' for addition cvs add: use 'cvs commit' to add this file permanently

Możemy zatwierdzić zmiany: /home/jp/skrypty$ cvs commit cvs commit: Examining . GNU nano 1.3.12

File: /tmp/cvsooDyNa

* Zmodyfikowany skrypt witaj * Dodany skrypt mcd CVS: ---------------------------------------------------------------------CVS: Enter Log. Lines beginning with `CVS:' are removed automatically CVS:

582

|

Dodatek D Kontrola wersji

CVS: CVS: CVS: CVS: CVS: CVS: CVS:

Committing in . Modified Files: witaj Added Files: mcd ----------------------------------------------------------------------

[ Wrote 13 lines ] RCS file: /home/jp/cvsroot/skrypty/mcd,v done Checking in mcd; /home/jp/cvsroot/skrypty/mcd,v #!/bin/sh > echo 'Witaj świecie!' > EOF /tmp/skrypty/trunk$ cd .. /tmp/skrypty$ svn import /tmp/skrypty file:///home/jp/svnroot/skrypty GNU nano 1.3.12

File: svn-commit.tmp

Początkowy import skryptów powłoki --Ta linia i następne zostaną zignorowane-A

. [ Wrote 4 lines ]

Dodawanie Dodawanie Dodawanie Dodawanie

/tmp/skrypty/trunk /tmp/skrypty/trunk/witaj /tmp/skrypty/branches /tmp/skrypty/tags

Zatwierdzona wersja 1.

Pobierzmy projekt i zaktualizujmy go: /tmp/skrypty$ cd /home/jp$ svn checkout file:///home/jp/svnroot/skrypty A skrypty/trunk A skrypty/trunk/witaj A skrypty/branches A skrypty/tags Pobrano wersję 1. /home/jp$ cd skrypty /home/jp/skrypty$ ls -l razem 3 drwxrwxr-x 3 jp jp 1024 lis 19 13:06 branches drwxrwxr-x 3 jp jp 1024 lis 19 13:06 tags drwxrwxr-x 3 jp jp 1024 lis 19 13:06 trunk /home/jp/skrypty$ cd trunk/ /home/jp/skrypty/trunk$ ls -l razem 1 -rw-rw-r-- 1 jp jp 33 lis 19 13:06 witaj /home/jp/skrypty/trunk$ echo "Witaj Mamo..." >> witaj

Sprawdźmy status magazynu lokalnego. Polecenie svn status działa podobnie do instrukcji cvs –qn update, która została opisana we wcześniejszym punkcie dotyczącym systemu CVS. /home/jp/skrypty/trunk$ svn info Ścieżka: . URL: file:///home/jp/svnroot/skrypty/trunk Katalog główny repozytorium: file:///home/jp/svnroot UUID repozytorium: 983468cf-7520-410f-be0b-509f28642126 Wersja: 1 Rodzaj obiektu: katalog Zlecenie: normalne Autor ostatniej zmiany: jp Ostatnia zmiana w wersji: 1

Subversion

|

587

Data ostatniej zmiany: 2007-11-19 13:04:38 +0100 (pon, 19 lis 2007) /home/jp/skrypty/trunk$ svn status -v 1 1 jp M 1 1 jp /home/jp/skrypty/trunk$ svn status M witaj /home/jp/skrypty/trunk$ svn update W wersji 1.

. witaj

Dodajmy nowy skrypt do systemu kontroli wersji. /home/jp/skrypty/trunk$ cat mcd > #!/bin/sh > mkdir -p "$1" > cd "$1" > EOF /home/jp/skrypty/trunk$ svn st ? mcd M witaj /home/jp/skrypty/trunk$ svn add mcd A mcd

Zatwierdźmy zmiany: /home/jp/skrypty/trunk$ svn ci GNU nano 1.3.12

File: svn-commit.tmp

* Zmodyfikowany skrypt witaj * Dodany skrypt mcd --Ta linia i następne zostaną zignorowane-A M

trunk/mcd trunk/witaj [ Wrote 6 lines ]

Dodawanie trunk/mcd Wysyłanie trunk/witaj Przesyłanie treści pliku.. Zatwierdzona wersja 2.

Teraz zaktualizujemy magazyn lokalny, wprowadzimy jeszcze jedną zmianę i sprawdzimy różnicę między plikami. /home/jp/skrypty/trunk$ svn up W wersji 2. /home/jp/skrypty/trunk$ vi witaj /home/jp/skrypty/trunk$ svn diff witaj Index: witaj =================================================================== --- witaj (wersja 2) +++ witaj (kopia robocza) @@ -1,3 +1,3 @@ #!/bin/sh echo 'Witaj świecie!' -Witaj Mamo... +echo 'Witaj Mamo...'

588 |

Dodatek D Kontrola wersji

Jeżeli zatwierdzając zmianę, umieścimy w wierszu poleceń treść wpisu dziennika, zapobiegniemy uruchomieniu edytora tekstowego: /home/jp/skrypty/trunk$ svn -m '* Poprawiony błąd składni' commit Wysyłanie trunk/witaj Przesyłanie treści pliku. Zatwierdzona wersja 3.

Przeanalizujmy historię zmian w pliku. /home/jp/skrypty/trunk$ svn log witaj -----------------------------------------------------------------------r3 | jp | 2007-11-19 13:20:06 +0100 (pon, 19 lis 2007) | 1 line * Poprawiony błąd składni -----------------------------------------------------------------------r2 | jp | 2007-11-19 13:16:21 +0100 (pon, 19 lis 2007) | 3 lines * Zmodyfikowany skrypt witaj * Dodany skrypt mcd -----------------------------------------------------------------------r1 | jp | 2007-11-19 13:04:38 +0100 (pon, 19 lis 2007) | 1 line Początkowy import skryptów powłoki ------------------------------------------------------------------------

Dodamy pewne metadane związane z wersją, które będą automatycznie aktualizowane przez system kontroli wersji. Zatwierdzimy je i sprawdzimy zmiany. /home/jp/skrypty/trunk$ vi witaj /home/jp/skrypty/trunk$ cat witaj #!/bin/sh # $Id$ echo 'Witaj świecie!' echo 'Witaj Mamo...' /home/jp/skrypty/trunk$ svn propset svn:keywords "Id" witaj atrybut 'svn:keywords' ustawiony dla 'witaj' /home/jp/skrypty/trunk$ svn ci -m'* Dodane słowo kluczowe ID' witaj Wysyłanie witaj Przesyłanie treści pliku. Zatwierdzona wersja 4. /home/jp/skrypty/trunk$ cat witaj #!/bin/sh # $Id: witaj 4 2007-11-19 12:24:15Z jp $ echo 'Witaj świecie!' echo 'Witaj Mamo...'

Porównajmy bieżącą wersją pliku z wersją 2. Przywrócimy tę wersję pliku, a po przeanalizowaniu treści powrócimy do ostatniej wersji. /home/jp/skrypty/trunk$ svn diff -r2 witaj Index: witaj =================================================================== --- witaj (wersja 2) +++ witaj (kopia robocza) @@ -1,3 +1,4 @@ #!/bin/sh +# $Id$ echo 'Witaj świecie!'

Subversion

| 589

-Witaj Mamo... +echo 'Witaj Mamo...' Zmiany atrybutów dla: witaj ___________________________________________________________________ Nazwa: svn:keywords + Id /home/jp/skrypty/trunk$ svn update -r2 witaj UU witaj Uaktualnione do wersji 2. /home/jp/skrypty/trunk$ cat witaj #!/bin/sh echo 'Witaj świecie!' Witaj Mamo... /home/jp/skrypty/trunk$ svn update -rHEAD witaj UU witaj Uaktualnione do wersji 4. /home/jp/skrypty/trunk$ cat witaj #!/bin/sh # $Id: witaj 4 2007-11-19 12:24:15Z jp $ echo 'Witaj świecie!' echo 'Witaj Mamo...'

Zobacz również • Polecenie man svn. • Polecenie man svnadmin. • Polecenie man svndumpfilter. • Polecenie man svnlook. • Polecenie man svnserve. • Polecenie man svnversion. • Oficjalny serwis projektu Subversion znajduje się pod adresem http://subversion.tigris.org. • Nakładka na aplikację Explorer dodająca obsługę SVN (TortoiseSVN) jest dostępna pod adre-

sem http://tortoisesvn.tigris.org.

• Książka Version Control with Subversion — http://svnbook.red-bean.com. • Skompilowane pakiety oprogramowania SVN dla systemów Solaris, Linux i Mac OS X są

dostępne pod adresem http://www.uncc.org/svntools/clients. • Dokument „Subversion for CVS Users” — http://osdir.com/Article203.phtml. • Artykuł na temat porównania systemów kontroli wersji — http://better-scm.berlios.de/compari

´son/comparison.html. • Receptura 16.14, „Utworzenie katalogu i przejście do niego w jednym kroku”.

590

|

Dodatek D Kontrola wersji

RCS W chwili, gdy powstał system RCS, był on postrzegany jako rewolucja w dziedzinie kontroli wersji. Z czasem stał się jednak tylko bazą dla nowszego systemu CVS.

Zalety • Lepszy niż brak jakiegokolwiek systemu.

Wady • Brak możliwości jednoczesnej pracy z tym samym plikiem. • Brak jednolitej obsługi centralnego repozytorium. Oczywiście, można samodzielnie przy-

gotować odpowiednie środowisko, na przykład za pomocą dowiązań symbolicznych. • Brak obsługi zdalnych repozytoriów. • Zapisywanie zmian jedynie w plikach, bez jakiejkolwiek obsługi katalogów. • Niedostateczna obsługa plików binarnych i brak jakiejkolwiek obsługi dla innych obiektów

(na przykład dowiązań symbolicznych). W przeciwieństwie do aplikacji CVS i SVN, które są obsługiwane przez użytkownika za pośrednictwem jednego pliku binarnego, system RCS składa się z wielu plików binarnych.

Przykład Przygotujmy nowe repozytorium w katalogu domowym, przeznaczone do wykorzystania we własnym zakresie: /home/jp$ mkdir -m 0754 bin

Utwórzmy skrypt. /home/jp$ cd bin /home/jp/bin$ cat witaj > #!/bin/sh > echo 'Witaj świecie!' > EOF /home/jp/bin$ ci witaj witaj,v > Klasyczny skrypt Witaj świecie >> . initial revision: 1.1 done /home/jp/bin$ ls -l razem 1 -r--r--r-- 1 jp jp 240 lis 19 13:57 witaj,v

Co się stało? Okazuje się, że jeśli katalog o nazwie RCS nie istnieje, pliki systemu RCS są umieszczane w katalogu bieżącym. Jeżeli nie dodamy opcji –u lub –l, plik zostanie pobrany, a następnie usunięty. Opcja –l oznacza odesłanie pliku i zablokowanie go, dzięki czemu można w nim RCS

|

591

wprowadzać zmiany. Opcja –u pozostawia plik w stanie odblokowanym. Spróbujmy wykonać tę operację jeszcze raz. Najpierw odeślemy plik, potem utworzymy katalog RCS i pobierzemy plik jeszcze raz. /home/jp/bin$ co -u witaj,v witaj,v --> witaj revision 1.1 (unlocked) done /home/jp/bin$ ls -l razem 2 -r--r--r-- 1 jp jp 33 lis 19 14:05 witaj -r--r--r-- 1 jp jp 240 lis 19 13:57 witaj,v /home/jp/bin$ rm witaj,v rm: usunąć zabezpieczony przed zapisem zwykły plik `witaj,v'? y /home/jp/bin$ mkdir -m 0755 RCS /home/jp/bin$ ci -u witaj RCS/witaj,v > Klasyczny skrypt Witaj świecie >> . initial revision: 1.1 done /home/jp/bin$ ls -l razem 2 drwxr-xr-x 2 jp jp 1024 lis 19 14:06 RCS -r--r--r-- 1 jp jp 33 lis 19 14:05 witaj /home/jp/bin$ ls -l RCS razem 1 -r--r--r-- 1 jp jp 240 lis 19 14:06 witaj,v

Plik oryginalny został przekształcony w plik tylko do odczytu. Ma to na celu wymuszenie użycia instrukcji co –l przed rozpoczęciem jego edycji. /home/jp/bin$ co -l witaj RCS/witaj,v --> witaj revision 1.1 (locked) done /home/jp/bin$ echo "Witaj Mamo..." >> witaj

Zatwierdzimy zmiany, ale kopia pozostanie zablokowana, aby można było wprowadzać kolejne zmiany. /home/jp/bin$ ci -l witaj RCS/witaj,v > * Zmodyfikowany skrypt witaj >> . done /home/jp/bin$ ls -l razem 2 drwxr-xr-x 2 jp jp 1024 lis 19 14:10 RCS -rw-r--r-- 1 jp jp 47 lis 19 14:09 witaj

592

|

Dodatek D Kontrola wersji

Teraz możemy wprowadzić kolejne zmiany i sprawdzić różnicę między wersjami. /home/jp/bin$ vi witaj /home/jp/bin$ rcsdiff witaj =================================================================== RCS file: RCS/witaj,v retrieving revision 1.2 diff -r1.2 witaj 3c3 < Witaj Mamo... --> echo 'Witaj Mamo...'

Zatwierdźmy zmiany i odblokujmy kopię pliku, by mogła być wykorzystywana. /home/jp/bin$ ci -u -m'* Poprawiony błąd składni' witaj RCS/witaj,v witaj revision 1.3 (locked) done /home/jp/bin$ vi witaj

RCS

|

593

/home/jp/bin$ cat witaj #!/bin/sh # $Id$ echo 'Witaj świecie!' echo 'Witaj Mamo...' /home/jp/bin$ ci -u -m'Dodanie słowa kluczowego ID' witaj RCS/witaj,v # $Id: witaj,v 1.4 2007/11/19 13:18:29 jp Exp $ 3c4 < Witaj Mamo... --> echo 'Witaj Mamo...' /home/jp/bin$ co -r witaj RCS/witaj,v --> witaj revision 1.4 done /home/jp/bin$ cat witaj #!/bin/sh # $Id: witaj,v 1.4 2007/11/19 13:18:29 jp Exp $ echo 'Witaj świecie!' echo 'Witaj Mamo...'

Skrypt przetwarzania plików z repozytorium RCS Poniżej został przedstawiony skrypt, który może uczynić pracę z systemem RCS mniej skomplikowaną. Ułatwia on wykorzystanie repozytorium RCS i automatyzuje proces pobierania i odsyłania plików w czasie ich przetwarzania. Każdy, kto może skorzystać z systemu Subversion lub CVS, powinien bez wahania wykorzystać te aplikacji. Jeśli jednak nie jest to możliwe, warto posłużyć się przynajmniej przedstawionym skryptem. #!/usr/bin/env bash # plik receptury: workon # workon--Przetwarzenie plików w systemie RCS # Ustalenie bezpiecznej wartości ścieżki i wyeksportowanie jej PATH=/usr/local/bin:/bin:/usr/bin export PATH VERSION='$Version: 1.4 $' # JP Vossen COPYRIGHT='Copyright 2004-2006 JP Vossen (http://www.jpsdomain.org/)' LICENSE='GNU GENERAL PUBLIC LICENSE'

594 |

Dodatek D Kontrola wersji

CAT='/bin/cat' if [ "$1" = "-h" -o "$1" = "--help" -o -z "$1" ]; then ${CAT} , 61 **, 136 *=, 137 ., 524 .bash_history, 432 .bash_login, 432 .bash_logout, 433 .bash_profile, 432

607

apropos, 26, 27 apt-get, 40 ar, 206 archiwum, 204 docelowy katalog dla plików, 208 rozpakowywanie, 207 rozszerzenia plików, 207 tworzenie, 204 ARG_MAX, 220 argument list too long, 380 arytmetyka, 135 ataki man-in-the-middle, 352 authorized.keys, 347 authorized_keys, 347, 353 auto_resume, 529, 542 automatyczne generowanie dat z określonego zakresu, 255 automatyczne uzupełnianie nazw, 498 automatyzacja zadań, 386 awk, 186, 188, 302 odwracanie kolejności słów w każdym wierszu, 188 sumowanie zbioru wartości, 189 tablice asocjacyjne, 192 wyświetlenie fragmentu tekstu występującego za wyszukaną frazą, 194 zachowanie części listingu wynikowego, 186 zachowanie fragmentu wiersza wynikowego, 187 zliczanie wartości tekstowych, 190

.bashrc, 432 .inputrc, 408, 433 .lessfilter, 215, 216 .lesspipe, 215 .profile, 432 /=, 137 /dev, 65 /dev/null, 65 /etc/profile, 438 :, 524 ;, 98 ^, 496 ^=, 137 {}, 66 |, 68, 179 ||, 103 |=, 137 ~, 493 ~=, 150 +=, 137 , 62, 77, 142 >>=, 137 0?, 96 1>, 60 2>, 60

B

A add_to_path, 403 administracja, 449 administrator, 24 adres IP, 199, 373 agent SSH, 347 AIDE, 318 AIX, 363 algorytm ROT13, 210 alias, 231, 245, 406, 524 aliasy, 245, 406 usuwanie, 321 allexport, 537 ALLOPT, 508 AND, 144 apostrofy, 32, 576 AppArmor, 341

608 |

Skorowidz

BASE, 129 basename, 163, 266, 271, 279 bash, 16, 21, 22, 23 dokumentacja, 47 dostosowanie domyślnego sposobu działania powłoki, 408 --help, 390 konfiguracja, 389 korzystanie z powłoki bez jej pobierania, 46 opcje startowe, 390 opcje wywołania, 521 pliki startowe, 434 pobieranie interpretera dla systemu BSD, 42 pobieranie interpretera dla systemu Linux, 38 pobieranie interpretera dla systemu Mac OS X, 43 pobieranie interpretera dla systemu Unix, 43 pobieranie interpretera dla systemu Windows, 44 profile, 438 uzupełnianie poleceń, 426 --version, 37 wersje, 39 własny zbiór narzędzi, 410 znak zachęty, 390

BASH, 526, 544 BASH_ARGC, 526, 535 BASH_ARGV, 526, 535 BASH_COMMAND, 527, 535 bash_completion, 432 BASH_ENV, 527, 537 BASH_EXECUTION_STRING, 527, 537 BASH_LINENO, 527, 536 bash_profile, 339 BASH_REMATCH, 150, 527, 543 BASH_SOURCE, 527, 536 BASH_SUBSHELL, 527, 537 BASH_VERSINFO, 527, 537 BASH_VERSION, 527, 537 bash3, 39 bashrc, 407, 432 bashtop, 47 baza danych MySQL, 298 Beagle, 227 BEGIN, 189, 488 bezpieczeństwo aplikacji skryptowych, 317 bezpieczna wartość $IFS, 323 bezpieczna wartość $PATH, 319 bezpieczna wartość umask, 324 bezpieczne działania, 498 bezpieczne pliki tymczasowe, 328 bezpieczne skrypty powłoki, 315 bg, 524 bind, 524 Bourne Shell, 21 braceexpand, 539 break, 161, 524 BSD, 42 buforowanie, 74 builtin, 247, 420, 524, 576 BUILTIN_ENABLED, 423 bunzip2, 206 bzip2, 204

C C Shell, 21, 22 cal, 264 case, 36, 160, 524 cat, 16, 17, 57, 468 cd, 100, 417, 524 cdable_vars, 541 CDPATH, 404, 527, 541 cdrecord, 277 cdspell, 541 celowe nadpisanie pliku, 78 CentOS, 40 CHANGES, 47

checkhash, 539 checkwinsize, 541 chmod, 220, 334, 460 chpass, 37 chroot, 338, 340 chsh, 37 chwilowa zmiana wartości $PATH, 399 ci, 593 ciągi tekstowe, 133 cichy tryb pracy, 283 clean_path, 403 clear, 447, 459 cmdhist, 539 co, 592 comm, 475 command, 35, 231, 249, 361, 524, 576 command not found, 239, 507, 508, 518 COMP_CWORD, 527, 533 COMP_LINE, 527, 534 COMP_POINT, 527, 534 COMP_WORDBREAKS, 527, 534 COMP_WORDS, 527, 534 COMPAT, 48 compgen, 430, 431, 524 complete, 428, 524 COMPREPLY, 430, 527, 534 compress, 204 configure, 426, 601 continue, 524 cp, 275 cpio, 206 CPIO, 206 cron, 27, 227, 264, 384, 385 crontab, 263 crypt, 344 CS_PATH, 360 csh, 21, 26 CSV, 312, 314 przetwarzanie plików, 314 zapis pliku danych, 312 cudzysłowy, 32, 507, 576 drukarskie, 212 cut, 202, 299, 302 cvs, 581 CVS, 580 Cygwin, 45 czas, 251, 552 letni, 262 obliczenia, 260 czyszczenie ekran po wylogowaniu, 459 tablica odwzorowań plików wykonywalnych, 321 czytelność skryptu, 111

Skorowidz

| 609

D dane wejściowe, 81 wyjściowe, 51 dashes, 164 date, 252, 263 daty, 251, 552 bieżąca, 256 domyślna wartość, 253 format ISO8601, 253 formatowanie, 252 generowanie dat z określonego zakresu, 255 kolejny dzień, 259 lata przestępne, 262 obliczenia, 260 poprzedni dzień, 259 strefy czasowe, 262 znacznik czasu, 257 Debian, 40 debuger, 518 debugowanie, 517 declare, 524 default, 161 definiowanie funkcje, 238 powłoka logowania, 37 prawa dostępu, 334 demony, 233 deskryptor pliku, 61, 62 df, 276 diff, 281, 462, 465, 596 dirs, 493, 515, 524 DIRSTACK, 527, 541 disown, 524 do, 119, 524 doc, 600 docelowy katalog dla plików archiwum, 208 dodawanie bieżący katalog do listy $PATH, 327 dane wyjściowe, 62 funkcje, 422 prefiks lub sufiks do danych wynikowych, 483 dokumentacja, 451 man, 27 powłoka bash, 47 dokumentowanie kodu, 109, 112 dokumenty MIME, 384 POD, 111 Texinfo, 452 dołączanie danych na początku pliku, 468 dołączanie danych wyjściowych, 62

610

|

Skorowidz

domyślna powłoka, 37 domyślna wartość daty, 253 done, 119, 524 dopasowywanie wartości do wzorca, 148 dostosowanie powłoki bash, 389 domyślny sposób działania powłoki, 408 dostosowanie znaku zachęty, 390, 522 dotglob, 539 dowiązania symboliczne, 221 drugi znak zachęty, 411 dystrybucja Live CD, 340 działania arytmetyczne, 135 dzieciaki skryptowe, 315 dzielenie danych wyjściowych tylko wtedy, gdy jest to konieczne, 369

E echo, 31, 33, 52, 53, 55, 524 opcje, 546 przenośność, 366 znaki specjalne, 546 ed, 472 edycja treści pliku, 471 egrep, 301 eksportowanie zmiennych, 114 elif, 138, 524 eliminowanie nietypowego działania osadzonych dokumentów, 83 elm, 384 else, 138, 524 emacs, 541 EMIT, 271 emulacja instrukcji pause systemu DOS, 489 enable, 35, 422, 524, 576 END, 189, 488 env, 114, 358 –eq, 146 errexit, 544 ERROUT, 271 errtrace, 536 esac, 524 etapy przetwarzania polecenia, 573 EUID, 527, 544 eval, 215, 317, 347, 524, 576, 577 examples, 565 exec, 524 execfail, 544 exit, 96, 111, 387, 525 expand_aliases, 407, 539 export, 114, 525 extdebug, 536 extglob, 149, 305, 539

F failglob, 539 false, 154 FAQ, 47 fc, 525 FCEDIT, 528, 542 Fedora Core, 40 fg, 99, 525 fi, 525 FIFO, 30 FIGNORE, 528, 534 file, 28, 30, 207 FILESIZE, 275 filtrowanie wyniku polecenia ps, 481 filtry lesspipe, 216 find, 26, 28, 30, 218 -exec, 218 -follow, 221 -iname, 222 -mtime, 223 -print, 218 -print0, 219 -size, 225 -type, 224 fmt, 214 for, 105, 118, 132, 135, 158, 525 force_fignore, 534 format CSV, 312 szesnastkowy, 370 formatowanie czas, 552 dane wyjściowe, 54 daty, 252, 552 fprintf, 175 func_choose, 87 FUNCNAME, 527, 536 function, 238, 525 functrace, 536 funkcje, 238, 406, 519 definiowanie, 238 parametry, 239 wywołanie, 239 zwracanie wartości, 239

G gawk, 309 gcc, 179 generowanie daty z określonego zakresu, 255 komunikaty o błędach w przypadku niezdefiniowania parametrów, 130 sekwencje liczbowe, 487

getconf, 319, 360 getopt, 267 getopts, 284, 525 GLOBIGNORE, 528, 539 GNU, 15 GNU/Linux, 15 gnu_errfmt, 537 grep, 83, 174, 481 –i, 178 –l, 176 –q, 177 –v, 181 GROUPS, 528, 544 grupowanie danych wyjściowych większej liczby poleceń, 66 gsub, 308 gunzip, 206, 428 gzcat, 185 gzip, 204

H hash, 525 hashall, 539 hasła, 91, 335 w skryptach, 343 head, 63 help, 525 here-document, 82, 470 hexdump, 370 histappend, 539 histchars, 529, 540 HISTCMD, 528, 540 HISTCONTROL, 415, 528, 540 histexpand, 540 HISTFILE, 415, 528, 540 HISTFILESIZE, 415, 528 HISTIGNORE, 415, 528, 540 histogram, 192 historia poleceń, 414, 499 opcje, 415 history, 414, 525, 540 histreedit, 540 HISTSIZE, 528, 540 HISTTIMEFORMAT, 416, 528, 541 histverify, 541 HMUG, 43 HOME, 127, 528, 537 host, 373 hostcomplete, 534 HOSTFILE, 528, 534 hostname, 279 HOSTNAME, 528, 537 HOSTTYPE, 528, 538

Skorowidz

|

611

HP-UX, 44, 363 href, 288 huponexit, 538

I identyfikator procesu, 99 if, 101, 138, 145, 525 else, 138 ifconfig, 373, 375 IFS, 230, 289, 323, 528, 538 ignoreeof, 541 IGNOREEOF, 528, 541 iloczyn logiczny, 144 in, 525 include, 234 indeks plików, 461 info, 451 info2man, 452 info2www, 452 informacja o rozmiarze wolnej przestrzeni dyskowej, 276 informacje na temat plików, 28 inputrc, 409, 432 INPUTRC, 528, 535 instalacja interpretera bash, 603 INSTALL, 48 instrukcje warunkowe, 138 interactive_comments, 542 interfejs użytkownika, 21 internal_getopts, 424 interpretacja dane, 283 kod HTML, 288 parametry, 276 tekst, 292 interpreter poleceń, 23 ISO8601, 253 iteracyjna analiza parametrów przekazanych do skryptu, 118 i-węzły, 471

J jednoczesne wykonywanie kilku poleceń, 98 język Perl, 17 jobs, 525

K kalkulator obsługa z poziomu wiersza poleceń, 170 notacja RPN, 167

612

|

Skorowidz

katalogi, 58 bieżący, 25 domowy, 493 najniższy poziom, 421 keychain, 345, 349, 351 keyword, 538 kill, 99, 430, 525 –KILL, 244 klatka chroot, 340 klucz prywatny, 345 Knoppix, 40 kod HTML, 272, 288 kod zakończenia polecenia, 96 kolejki FIFO, 30 kolejność sortowania, 200 komentarze, 109 kompilacja powłoki bash, 599 archiwum, 600 dokumentacja, 600 kompilacja, 601 konfiguracja, 601 pobieranie pakietu bash, 599 problemy, 603 testowanie powłoki, 603 zadawanie pytań, 604 zgłaszanie błędów, 604 kompresja plików, 204 kompresja znaków odstępu, 307 komunikaty o błędach, 60, 103 komunikaty syslog, 382 konfiguracja powłoki bash, 389 kontrola wersji, 579 CVS, 580 RCS, 591 Subversion, 585 konwersja pliki DOS do formatu systemu Linux, 211 wartość znacznika czasu, 262 koń trojański, 318 końcowy fragment pliku, 63 kopiowanie plików, 275 Korn, 21, 22 korzystanie z powłoki bash bez jej pobierania, 46 krańcowe znaki odstępu, 303 ksh, 21

L LANG, 528, 538 last, 483, 484 lata przestępne, 262 LC_ALL, 528, 538 LC_COLLATE, 528, 538

LC_CTYPE, 528, 538 LC_MESSAGES, 528, 538 LC_NUMERIC, 528, 538 less, 215, 451 LESS, 215 LESSCLOSE, 215 LESSOPEN, 215 lesspipe, 216 lesspipe.sh, 215, 216 let, 136, 137, 525 liczba mnoga angielskich rzeczowników, 294 LIFO, 492 LINENO, 413, 528, 536 Linux, 38 lista dostępnych odpowiedzi, 90 lista parametrów zawierających znaki spacji, 121 lithist, 541 Live CD, 340 load_mp3, 274 local, 525 locate, 26, 27, 220, 227, 228 logger, 382 login_shell, 538 logout, 525 ls, 26, 28, 29, 284 –a, 30 zapisywanie wyniku polecenia, 59 L-wartość, 108

Ł ładowane polecenia wbudowane, 422 łączenie programy, 67 programy z wykorzystaniem danych wyjściowych jako parametrów, 71 testy, 144

M MAC, 341 Mac OS X, 43 MACHTYPE, 528, 539 mail, 383 MAIL, 528, 542 MAILCHECK, 528, 543 mailer, 383 MAILPATH, 529, 543 mailto, 383 MAILTO, 384 mailwarn, 543 make, 422, 601 clean, 603 install, 603

Makefile, 422 man, 27 Mandatory Access Control, 341 Mandrake, 40 Mandriva, 40 man-in-the-middle, 352 mcd, 419 mechanizm chroot, 338, 340 cron, 27, 264 osadzanie dokumentu, 82 przekierowanie danych wejściowych, 81 readline, 399, 408, 409 rozgałęzianie potoku, 69 stronicowanie, 215 uogólnianie nazw plików, 31 uzupełnianie poleceń, 426 wirtualizacja, 362 menu, 165 MEPIS, 40 Microsoft Services for Unix, 45 MIME, 384 mkalbum, 268 mkdir, 420 mkisofs, 277, 279 modyfikacja fragmenty ciągu tekstowego, 132 określone pola listingu danych, 302 monitor, 542 MP3, 218 MTA, 383 MUA, 385 mutt, 384 mv, 449, 467 MySQL, 298 myślniki, 265

N nadawanie skryptowi nazwy test, 505 nadpisanie pliku, 77, 78 nagrywanie płyt CD, 277 NAT, 373 nawiasy klamrowe, 66 nazwy zmiennych, 113 nc, 382 NEWS, 47 Nie ma takiego pliku ani katalogu, 502 NIP, 184 nl, 370 no_empty_cmd_completion, 534 nocaseglob, 539 nocasematch, 149, 555

Skorowidz

|

613

noclobber, 77, 78, 535 noexec, 537 noglob, 539 nohup, 102, 233 NOPASSWD, 341, 344 notacja RPN, 167 NOTES, 48 notify, 542 nounset, 544 now, 256 NTP, 261 null, 65 nullglob, 539 numerowanie wierszy, 485 numery NIP, 184

O obliczanie daty i czasu, 260 obowiązkowa kontrola dostępu, 341 obsługa parametrów zawierających znaki spacji, 119 odcisk klucza, 352 oddzielanie nazw zmiennych od otaczającego je tekstu, 113 ODF, 280, 311 odpowiedzi typu tak-nie, 87 odrzucanie dane wyjściowe, 65 niepotrzebne dane z procedury wyszukiwania, 181 odtwarzacz MP3, 273 odwracanie kolejności słów w każdym wierszu, 188 odwrotna notacja polska, 168 odwrotne odwzorowanie DNS, 373 OFS, 307 ograniczanie liczba poleceń SSH, 352 prawa konta gościa, 338 OLDPWD, 529, 542 onecmd, 535 opcje startowe powłoki bash, 390 opcje wywołania powłoki bash, 521 Open Document Format, 280, 311 OpenBSD, 315 OpenSSH, 315, 345 operatory łączenie testów, 144 porównania, 147 przetwarzanie ciągów tekstowych, 133 przypisanie, 137 rozszerzony mechanizm dopasowywania do wzorca extglob, 555 wyrażenia warunkowe, 545

614

|

Skorowidz

oprogramowanie GNU, 15 OPTARG, 285, 529, 544 OPTERR, 286, 529, 544 OPTIND, 529, 544 OR, 144 osadzanie dokumentacji w treści skryptu, 110 osadzanie dokumentu, 82 eliminowanie nietypowego działania, 83 jednoczesne wykonanie kilku poleceń, 98 wcinanie osadzonych dokumentów, 85 OSTYPE, 529, 544 out-of-band, 352

P parametry, 239 parametry skryptów powłoki, 117 analiza iteracyjna, 118 generowanie komunikatów o błędach w przypadku niezdefiniowania parametrów, 130 liczba, 123 pusty ciąg tekstowy jako poprawna wartość domyślna, 128 stosowanie, 125 ustawianie wartości domyślnych, 127 wartości domyślne, 126 wartości domyślne inne niż stały ciąg tekstowy, 129 znaki spacji, 119, 121 parametry wiersza poleceń, 162 passphrase, 345 passwd, 37, 338 patch, 462 PATH, 94, 319, 529, 539 pause, 489 PCRE, 301 perl, 260 Perl, 17, 111 pętle for, 105, 132, 158 przenośność kodu, 365 read, 156 seq, 159 wartości zmiennoprzecinkowe, 159 while, 153 zliczanie iteracji, 158 PGP, 352 physical, 535 PID, 429, 481 pinfo, 452 pipefail, 543 PIPESTATUS, 529, 543 pkg_add, 42

pliki, 28, 51 CSV, 312 dołączanie danych na początku, 468 dowiązania symboliczne, 221 edycja treści, 471 indeks, 461 kompresja, 204 konfiguracyjne, 236 kopiowanie, 275 MP3, 218, 273 nadpisanie, 78 pobieranie danych, 81 pomijanie nagłówka, 64 prawa dostępu, 334 rc, 431 rozpakowywanie, 207 startowe, 431, 434 tekstowe, 174 tymczasowe, 317, 328 ukryte, 30 usuwanie, 275 właściwości, 141 wsadowe, 97 wykonywalne, 93 XML, 281 zabezpieczanie przed przypadkowym nadpisaniem, 77 ZIP, 452 zliczanie wierszy, słów i znaków, 213 zmiana nazwy, 449 płyty CD, 277 pobieranie danych dane wynikowe z wykorzystaniem wywołania funkcji, 291 od użytkownika, 86 z innego komputera, 377 z pliku, 81 pobieranie daty poprzedniego lub kolejnego dnia, 259 pobieranie interpretera bash system BSD, 42 system Linux, 38 system Mac OS X, 43 system Unix, 43 system Windows, 44 pobieranie wartości domyślnych, 126 początkowy fragment pliku, 63 POD, 111 podmiana wartości składających się z większej liczby słów, 495 podpowłoki, 509 podział procesu na etapy, 386 podział wierszy, 214

polecenia, 24, 523 alias, 245, 406 apostrofy, 32 apropos, 26, 27 apt-get, 40 ar, 206 awk, 186, 188 basename, 163, 266, 279 builtin, 247 bunzip2, 206 bzip2, 204 cal, 264 cat, 17, 57, 468 cd, 100, 417 cdrecord, 277 chmod, 220, 334 chpass, 37 chsh, 37 ci, 593 clear, 459 co, 592 comm, 475 command, 231, 249, 361 compgen, 430, 431 complete, 428 compress, 204 cp, 275 cpio, 206 crypt, 344 cudzysłowy, 32 cut, 202, 299 cvs, 581 dashes, 164 date, 252, 263 df, 276 diff, 281, 462, 465, 596 dirs, 493 echo, 33, 52, 53 ed, 472 egrep, 301 elm, 384 enable, 35 env, 114, 358 etapy przetwarzania, 573 eval, 317, 577 exit, 96 export, 114 fg, 99 file, 28, 30, 207 find, 26, 28, 30, 218 fmt, 214 fprintf, 175 gawk, 309

Skorowidz

|

615

polecenia getconf, 319, 360 getopt, 267 getopts, 284 grep, 83, 174, 481 grupujące, 141 gsub, 308 gunzip, 206 gzcat, 185 gzip, 204 head, 63 hexdump, 370 history, 414 host, 373 hostname, 279 ifconfig, 373, 375 include, 234 info, 451 kill, 99, 430 kod zakończenia, 96 less, 215, 451 lesspipe.sh, 215 let, 136, 137 locate, 26, 27, 220, 227, 228 logger, 382 ls, 26, 28, 29 mail, 383 make, 422 man, 27 mkdir, 420 mkisofs, 277, 279 mutt, 384 mv, 449 nc, 382 nl, 370 nohup, 102, 233 passwd, 37 patch, 462 pkg_add, 42 popd, 492 printf, 54, 548 ps, 335, 430 pushd, 492 pwd, 26 rcsdiff, 594 read, 86, 91, 292, 293 rename, 451 reset, 512 rm, 275 rpm2cpio, 206 screen, 445, 453 script, 458 sdiff, 476

616

|

Skorowidz

sed, 289, 468 sekwencyjne wykonanie kilku poleceń, 97 select, 165 seq, 159 set, 77, 116, 436, 530 shift, 163, 266 shopt, 531 składnia wywołania, 35 slocate, 26, 27, 227 sort, 68, 197 source, 229, 234, 437 sprawdzenie, czy polecenie zostało wykonane poprawnie, 95 ssh, 347, 350 ssh-add, 349 ssh-keygen, 346 ssh-keygen2, 346 stat, 28 state, 30 stty, 512 su, 474 sudo, 38, 342, 473, 474 sudoers, 342 svn, 587 tail, 63, 64 tar, 205 tee, 69, 457 telnet, 375 test, 108, 140, 337 touch, 28 tr, 209, 210, 307, 555 trap, 242, 244 tset, 512 tty, 422 type, 26, 34 ulimit, 322 unalias, 246 uniq, 199 unset, 128 unzip, 452 up2date, 40 uruchamianie w tle, 98 usermod, 37 uuencode, 383, 385 vi, 472 wbudowane, 34 wc, 213 wdiff, 465 wget, 374 which, 26, 34 wykonywanie, 26, 93 wykonywanie bez nadzoru, 102 wyszukiwanie, 26

xargs, 220 xmail, 383 yum, 40 zabicie, 99 zakończenie, 99 zastępowanie, 34 zcat, 185 zewnętrzne, 34 zgrep, 185 zip, 204 złożone, 573 polityka bezpieczeństwa, 341 połączenia SSH, 350 położenie pliku wykonywalnego, 94 pomijanie aliasów i funkcji, 247 pomijanie nagłówka pliku, 64 popd, 492, 525 poprawność wykonania polecenia, 100 porównania, 147 dokumenty, 280, 596 posix, 543 POSIX, 23 POSIXLY_CORRECT, 529, 543 potoki, 27, 68, 509 powłoka, 21, 22 bash, 21, 23 C, 22 domyślna, 37 Korn, 22 systemowa, 21, 23 tryb interaktywny, 36 powtarzanie ostatniego polecenia, 493 powtórne wykorzystanie parametrów, 497 PPID, 529, 544 prawa dostępu, 334 prawa konta gościa, 338 prawa setgid, 336 prawa setuid, 318, 336 prawo wykonywania, 501 preprocesor danych wejściowych, 215 printf, 54, 55, 163, 173, 188, 514, 548, 552 specyfikatory formatu, 549 symbole specjalne, 551 znaczniki, 551 privileged, 544 problem wiersza #!, 358 procedura przetwarzania polecenia, 574 procesy, 99 sprawdzanie działania, 481 profile, 432, 438 progcomp, 535 programowanie w języku powłoki, 24 programowe uzupełnianie poleceń, 428

projekt GNU, 15 PROMPT_COMMAND, 396, 414, 529, 542 promptvars, 394, 542 przechodzenie do katalogów najniższego poziomu, 421 przechodzenie między kilkoma katalogami, 404 przechodzenie między określonymi katalogami, 492 przechwytywanie przerwań, 242 przeglądanie albumu ze zdjęciami, 268 przeglądanie danych wynikowych w formacie szesnastkowym, 370 przekazanie danych wyjściowych i komunikatów o błędach do różnych plików, 60 przekazanie danych wyjściowych i komunikatów o błędach do tego samego pliku, 61 przekazywanie danych wyjściowych do okna terminala, 52 przekazywanie przez wartość, 115 przekierowanie dane wejściowe, 68, 81 sieciowe, 371 strumienie, 546, 547 strumienie wyjściowe, 57, 58 wyjście na czas działania skryptu, 379 przekształcanie znacznik czasu w ciągi dat i czasu, 258 data i czas w znacznik czasu, 257 przenośne skrypty powłoki, 361 przenośność kodu, 357, 358 echo, 366 pętle, 365 przerwania, 242 przeszukiwanie danych przekazywanych w potoku, 179 przetwarzanie ciągi tekstowe, 133 dane znak po znaku, 296 nazwy plików zawierające niestandardowe znaki, 219 parametry skryptu powłoki, 283 parametry wiersza polecenia, 162 parametry z własnymi komunikatami o błędach, 286 pliki niezawierające znaków nowego wiersza, 311 pliki z danymi CSV, 314 pliki z repozytorium RCS, 594 polecenia, 573 pola o stałej długości, 309 przypisanie, 137 przywracanie przerwanych sesji, 453 ps, 335, 430 filtrowanie wyniku, 481

Skorowidz

|

617

PS1, 166, 390, 529, 542 PS2, 166, 390, 411, 529, 542 PS3, 166, 394, 411, 529, 542 PS4, 394, 411, 413, 529, 542 pułapki, 242 pushd, 492, 525 pwd, 26, 525 PWD, 529, 542

R race condition, 317 RANDOM, 330, 529, 545 rbash, 338 rc, 431 RCS, 591 rcsdiff, 594 rdist, 354 read, 86, 91, 156, 292, 293, 296, 484, 525 readline, 235, 389, 399, 408 plik konfiguracyjny, 555 README, 47 readonly, 525 Red Hat Enterprise Linux, 40 REDUCE, 276 rejestracja dane z całej sesji, 457 metadane plików, 460 zmiany, 597 remove_from_path, 403 rename, 451 REPLY, 86, 529, 545 repozytorium, 579 reset, 513 reset_internal_getopt, 424 restricted_shell, 535 return, 525 rm, 275, 467 –rf, 315, 317 root, 24 ROT13, 210, 344 ROT47, 344 rozgałęzianie kodu, 138 rozgałęzianie potoku, 69 rozłączanie nieaktywnych sesji, 355 rozmiar wolnej przestrzeni dyskowej, 276 rozpakowywanie archiwum, 206, 207, 599 wiele plików ZIP, 452 rozszerzenia plików archiwum, 207 rozszerzony mechanizm dopasowywania do wzorca, 149 równość dwóch wartości, 146 różnice między plikami, 465

618

|

Skorowidz

RPM, 206 rpm2cpio, 206 RPN, 167 rssh, 354 rsync, 354 R-wartość, 108

S samodzielne, przenośne pliki RC, 435 scp, 345, 354 screen, 445, 453 tryb dostępności dla wielu użytkowników, 456 script, 458 script-kiddies, 315 scripts, 566 sdiff, 476 SECONDS, 529, 539 sed, 289, 302, 468 sekwencje liczb, 487 sekwencyjne wykonanie kilku poleceń, 97 select, 90, 165, 412, 525 select_dir, 90 separator pól, 200 separator tysięcy, 489 seq, 159 set, 77, 116, 408, 436, 525, 530, 533 opcje, 530 -x, 517 setgid, 336, 337 setuid, 318, 336, 337 sftp, 354 sh, 21 SHELL, 529, 545 SHELLOPTS, 529, 535 shift, 163, 266, 525 shift_by, 479 shift_verbose, 542 SHLVL, 529, 537 shopt, 149, 531, 533 opcje, 531 SIGHUP, 102 SIGKILL, 244 SIGTERM, 102 składnia skryptu powłoki, 516 składnia tylko do zapisu, 109 skracanie nazw poleceń, 406 skrypty konfiguracyjne, 236 skrypty powłoki, 16, 22 parametry, 117 przetwarzanie parametrów, 283 slocate, 26, 27, 227 słowa kluczowe, 523

sort, 68, 197, 477 sortowanie adresy IP, 199 dane wyjściowe, 197 wartości liczbowe, 198 source, 229, 234, 235, 344, 406, 437 sourcepath, 535 sourcing, 235 spodziewana zmiana eksportowanych wartości, 506 spoofing, 318 Spotlight, 227 sprawdzanie docelowy katalog dla plików archiwum, 208 poprawne wykonanie polecenia, 95 równość dwóch wartości, 146 składnia skryptu powłoki, 516 stan procesu, 481 wiele warunków, 144 właściwości ciągów tekstowych, 145 właściwości plików, 141 zgodność wartości ze wzorcem, 148 ssh, 347, 350 SSH, 233, 344, 345, 350, 377 ograniczanie liczby poleceń, 352 pobieranie danych z innego komputera, 377 wykorzystanie usługi bez hasła, 345 SSH Communication Security, 345 SSH_ID, 378 SSH_USER, 378 SSH1, 345 SSH2, 345 ssh-add, 349 ssh-agent, 345, 351 ssh-keygen, 346 ssh-keygen2, 346 standardowy strumień błędów, 74 standardowy strumień wejściowy, 74, 81 standardowy strumień wyjściowy, 51, 74 startup-files, 565 stat, 28 state, 30 STDERR, 61, 74, 75, 234 STDIN, 74, 234 STDOUT, 61, 74, 75, 234 stos, 492 strefy czasowe, 262 strftime, 252, 552 stronicowanie, 215 strumienie, 61 wyjściowy, 81 stty, 512, 513 su, 474 Subversion, 585

sudo, 38, 342, 473, 474 grupa poleceń, 473 sudoers, 342 SUID, 455 suma logiczna, 144 sumowanie zbioru wartości, 189 SUSE, 40 suspend, 525 svn, 587 SVN, 297 svn status, 297 sygnały, 102 symbole specjalne ANSI, 522 symbole wieloznaczne, 519 synchronizacja historii poleceń wprowadzanych w różnych sesjach, 414 syslog, 185, 382 system kontroli wersji, 585 system operacyjny, 21 BSD, 42 Linux, 38 Mac OS X, 43 SVN, 297 Unix, 43 Windows, 44 sytuacja wyścigu, 317 szybkie wyszukiwanie plików, 227 szyfrowanie teksu, 210

Ś ścieżka dostępu, 266 śledzenie przebiegu skryptów, 516 śledzenie zmian, 597 środowisko chroot, 340

T tabela wartości ASCII, 561 tablice, 133, 290 asocjacyjne, 192 tail, 63, 64 tar, 205 tarball, 204 tee, 69, 457 telnet, 375 terminal, 512 tty, 233 test, 35, 108, 140, 337, 505, 525 test driver, 46 testowanie powłoka, 603 skrypty w środowisku VMware, 363

Skorowidz

|

619

Texinfo, 451 Text Utils, 45 then, 525 Thomson, 21 time, 525 TIMEFORMAT, 529, 542 times, 525 tkman, 452 TMOUT, 355, 529, 542 touch, 28 tr, 209, 210, 307 –d, 211 symbole specjalne, 212, 555 trap, 242, 244, 525 Tripwire, 318 true, 154 tryb emacs, 558 tryb interaktywny, 36 tryb vi, 559 tset, 513 tty, 233, 422 TTY, 423, 424 tworzenie baza danych MySQL, 298 bezpieczne skrypty powłoki, 315 indeks plików, 461 katalog i przejście do niego w jednym kroku, 419 menu, 165 przenośne skrypty powłoki, 361 samodzielne, przenośne pliki RC, 435 skrypty powłoki, 24 type, 26, 34, 525 typeset, 525

U Ubuntu, 40 UID, 529, 545 ujawnianie haseł na liście procesów, 335 ulimit, 322, 525 umask, 324, 525 UMASK, 324 umieszczenie danych w skrypcie, 82 unalias, 246, 526 unarne operatory wyrażeń warunkowych odnoszących się do cech plików, 143 uniq, 199 Unix, 21, 43 unset, 128, 526 until, 526 unzip, 452 UNZIP, 105

620

|

Skorowidz

uogólnianie nazw plików, 31 up2date, 40 uruchamianie plik wykonywalny, 93 polecenie w tle, 98 polecenie zbliżone do wykonywanego wcześniej, 494 powłoka z własną konfiguracją, 437 skrypt w tle, 233 skrypt w wybranym dniu, 263 wszystkie skrypty w katalogu, 105 usermod, 37 ustalanie wersji, 597 ustawianie interpreter bash jako domyślna powłoka, 37 wartości domyślne, 127 usuwanie aliasy, 246, 321 cudzysłowy drukarskie, 212 krańcowe znaki odstępu, 303 pliki, 275, 513 zduplikowane wiersze, 203 UTC, 257 uuencode, 383, 385 uzdrawianie terminala, 512 uzupełnianie poleceń, 426 użytkownik root, 24

V verbose, 537 VERBOSE, 125 vi, 472, 542, 559 visudo, 343 Vixie Cron, 263 VMware, 362 testowanie skryptów, 363

W wait, 526 walidacja wprowadzanych danych, 332 wartości domyślne, 127 pasujące do wzorca, 509 wc, 213 wcinanie osadzonych dokumentów, 85 wdiff, 465 wejściowe, 81 weryfikacja spójności systemu, 318 wget, 374 which, 26, 34 while, 135, 153, 484, 526

wiadomości e-mail, 383 wielokrotne przekierowania w jednym wierszu, 72 wielokrotne rozgałęzianie kodu, 160 wielokrotne wykorzystanie kodu, 234 wiersz #!, 358 Windows, 44 wirtualizacja, 362 właściwości ciągi tekstowe, 145 pliki, 141 wprowadzanie hasła, 91 kilka instrukcji w jednym wierszu, 66 odpowiedź typu tak-nie, 87 współdzielenie pojedynczej sesji powłoki, 456 wstawianie kodu źródłowego, 235 wybór opcji z listy, 90 wycinanie fragmentów listingu wynikowego, 202 wyczyszczenie drzewa kodu źródłowego w systemie SVN, 297 wyjście, 51 wykonywanie polecenia, 26, 93 polecenia bez nadzoru, 102 polecenia zapisane w zmiennych, 104 skrypty z prawami zwykłego użytkownika, 341 wykorzystanie danych wyjściowych jako wejściowych, 67 wymuszone polecenia, 353 wyodrębnianie określonych pól listingu danych, 299 wyrażenia arytmetyczne, 136 wyrażenia regularne, 150 numery NIP, 184 wyszukiwanie ciągów, 182 zgodne z wyrażeniami języka Perl, 301 wyrażenia warunkowe, 545 wysyłanie wiadomości e-mail, 383 wyszukiwanie ciąg niezależnie od wielkości liter, 178 ciąg w pliku tekstowym, 174 ciąg z użyciem bardziej rozbudowanych wzorców, 182 informacje w zarchiwizowanych plikach, 185 katalogi umożliwiające modyfikowanie zawartości w zmiennej $PATH , 325 numery NIP, 184 polecenia, 26 wiersze tylko jednego pliku, 475 wyszukiwanie plików, 217 bez względu na wielkość liter występujących w nazwach, 222 dowiązania symboliczne, 221

lista potencjalnych lokalizacji, 229 na podstawie daty, 223 określona treść, 226 określony rozmiar, 225 pliki MP3, 218 pliki określonego typu, 224 przetwarzanie nazw plików zawierających niestandardowe znaki, 219 szybkie wyszukiwanie, 227 zwiększanie szybkości przetwarzania wyszukanych plików, 221 wyświetlanie dane na ekranie, 52 dane w formie histogramu, 192 fragment tekstu występującego za wyszukaną frazą, 194 komunikaty o błędach, 103 myślniki, 265 nazwy plików, 29 nazwy plików zwierające poszukiwany ciąg tekstowy, 176 pliki ukryte, 30 separator tysięcy, 489 tekst z zachowaniem znaków spacji, 53 wartość wszystkich zmiennych, 116 wynik bez znaku nowego wiersza, 55 wywołanie funkcje, 239 powłoka bash, 521 wyznaczanie wartości wyrażenia arytmetycznego, 136 wzorzec, 31

X xargs, 220 xmail, 383 XML, 281 xpg_echo, 543 xtrace, 394, 517, 537

Y yum, 40

Z zabezpieczanie pliku przed przypadkowym nadpisaniem, 77 zabicie proces, 99 zachowanie części listingu wynikowego, 186 zachowanie fragmentu wiersza wynikowego, 187 zachowanie N ostatnich obiektów, 478

Skorowidz

|

621

zadania administracyjne, 449 wsadowe, 457 zakończenie procesu, 99 zakończenie wyszukiwania ciągu wynikiem typu „prawda — fałsz”, 177 załączniki pocztowe, 383 zamiana duże litery na małe, 210 strumienie STDERR i STDOUT, 75 znaki, 209 zapisywanie dane w odtwarzaczu MP3, 273 dane w tablicy, 290, 293 dane wyjściowe polecenia, 56 dane wyjściowe większej liczby poleceń, 66 dane wyjściowe, gdy wydaje się, że przekierowanie nie działa, 73 kopia danych wyjściowych mimo wykorzystywania ich jako danych wejściowych, 69 pliki danych w formacie CSV, 312 wynik polecenia ls, 59 wynik w plikach innych katalogów, 58 zapobieganie zrzutom pamięci, 322 zapominanie o ustawieniu praw wykonywania, 501 zarchiwizowane pliki, 185 zastępowanie poleceń, 34 zcat, 185 zdjęcia, 268 zewnętrzne pliki wykonywalne, 317 zgodność wartości ze wzorcem, 148 zgrep, 185 zip, 204 ZIP, 105, 281, 452 zliczanie iteracje, 158 parametry, 123 różnice między plikami, 465 wartości tekstowe, 190 wiersze, słowa i znaki, 213

622

|

Skorowidz

zmiana definicja polecenia, 245 domyślna powłoka, 37 nazwy plików zwierające znaki specjalne w nazwie, 467 nazwy poleceń, 406 nazwy wielu plików, 449 podziału wierszy, 214 sposób działania skryptu w zależności od rodzaju przekierowania danych, 152 znak zachęty w tekstowym menu, 166 zmienne, 107, 526 $-, 36 BASE, 130 BASH_REMATCH, 150 CDPATH, 404 eksportowanie, 114 HOME, 127 IFS, 230, 323 LESS, 215 nazwy, 113 PASSWD, 91 PATH, 94, 319 powłoka, 107 PS1, 166 tablice, 133 UMASK, 324 VERBOSE, 125 wyświetlanie wartości wszystkich zmiennych, 116 zmniejszenie liczby instrukcji if, 101 zmodem, 455 znacznik czasu, 257 znaczniki XML, 281 znak zachęty, 24, 390, 411, 522 znaki odstępu, 307 znaki spacji, 121 znaki wzorców dopasowywania, 554 zrzut pamięci, 322 zwiększenie szybkości przetwarzania wyszukanych plików, 221 zwracanie wartości z funkcji, 239

O autorach Carl Albing jest doświadczonym programistą posługującym się językami Java i C oraz językami skryptowymi powłoki. Pracuje z systemami Linux i Unix od połowy lat 70., czyli od czasu rozpoczęcia nauki w St. Olaf Collage. Jest autorem książek i wykładowcą. W swoim dorobku ma wiele prezentacji na konferencjach technicznych i korporacyjnych w Stanach Zjednoczonych, Kanadzie i Europie. Ma licencjat z matematyki i magisterium z zarządzania międzynarodowego i kontynuuje studia. Obecnie pracuje jako inżynier oprogramowania w firmie produkującej superkomputery Cray Inc. oraz jako niezależny konsultant w tej dziedzinie. Carl Albing jest współautorem książki Java Application Development on Linux (Prentice Hall PTR 2004). Można się z nim skontaktować, odwiedzając jego stronę http://www.carlalbing.com lub wprowadzając słowo Albing w wyszukiwarce na stronie http://www.oreilly.com. J.P. Vossen pracuje w przemyśle komputerowym od początku lat 80. Od początku lat 90. specjalizuje się w dziedzinie IT, a szczególnie w bezpieczeństwie informacji. Od czasu, gdy zrozumiał, do czego służy plik autoexec.bat, zainteresował się językami skryptowymi i mechanizmami automatyzacji zadań. Z zadowoleniem przyjął wszechstronność i elastyczność powłoki bash i narzędzi GNU udostępnionych w systemie Linux w połowie lat 90. Wcześniej pisał artykuły do takich czasopism, jak Information Security Magazine i http://SearchSecurity.com. W nielicznych chwilach, w których nie siedzi przed komputerem, rozkłada lub składa różne rzeczy bądź wykonuje jedną i drugą czynność naraz. Cameron Newham opracowuje różne rozwiązania z dziedziny technologii informacyjnych; mieszka w Wielkiej Brytanii. Pochodzi z Australii. Uzyskał dyplom inżyniera technologii informacyjnych i geografii na University of Western Australia. Wolny czas poświęca na pracę nad projektem cyfrowego odtworzenia budowli architektonicznych w Anglii. Interesuje się również filozofią, astronomią, cyfrowym przetwarzaniem obrazów, eklezjologią i historią architektury. Jest współautorem książki bash. Wprowadzenie (Helion 2006).

Kolofon Zwierzę przedstawione na okładce to żółw leśny (glyptemys insculpta). Swoją nazwę zawdzięcza ono strukturze skorupy, która wygląda, jakby składała się z plastrów pociętego drzewa. Żółwie leśne zamieszkują lasy Ameryki Północnej, szczególnie w okolicach Nowej Szkocji i rejonie Wielkich Jezior. Są zwierzętami wszystkożernymi. Zadawalają się wszystkim, co znajdzie się na ich drodze, włączając w to rośliny, robaki i ślimaki (ich ulubiony przysmak). Populacja żółwi leśnych jest zagrożona przez zajmowanie ich terytoriów przez człowieka. Z konieczności żółwie leśne osiedlają się na piaszczystych brzegach rzek, strumieni i stawów, czyli na terenach podatnych na erozję, na których buduje się tamy i na których można spotkać ludzi. Budowa dróg, zanieczyszczenie środowiska oraz handel zwierzętami dodatkowo przyczyniają się do zmniejszenia ich populacji. Z tego względu w wielu stanach i prowincjach są one uważane za gatunek zagrożony.