Shell Scripts

paź 29, 2021

Overview

Nauczanie: 30 min
Ćwiczenia: 15 min
Pytania

  • Jak mogę zapisywać i ponownie używać poleceń?

Cele

  • Napisz skrypt powłoki, który uruchamia polecenie lub serię poleceń dla ustalonego zestawu plików.

  • Uruchom skrypt powłoki z wiersza poleceń.

  • Napisanie skryptu powłoki, który działa na zestawie plików zdefiniowanych przez użytkownika w wierszu poleceń.

  • Tworzenie potoków, które zawierają skrypty powłoki napisane przez Ciebie i innych.

Jesteśmy wreszcie gotowi, aby zobaczyć, co sprawia, że powłoka jest tak potężnym środowiskiem programowania.Zamierzamy wziąć polecenia, które często powtarzamy i zapisać je w plikach, tak byśmy mogli później ponownie wykonać wszystkie te operacje, wpisując jedno polecenie.Z powodów historycznych, zestaw poleceń zapisanych w pliku jest zwykle nazywany skryptem powłoki, ale nie popełnij błędu: są to w rzeczywistości małe programy.

Zacznijmy od powrotu do molecules/ i utworzenia nowego pliku, middle.sh, który będzie naszym skryptem powłoki:

$ cd molecules$ nano middle.sh

Polecenie nano middle.sh otwiera plik middle.sh w edytorze tekstowym 'nano’ (który działa w powłoce).Jeśli plik nie istnieje, zostanie utworzony. Możemy użyć edytora tekstowego do bezpośredniej edycji pliku – po prostu wstawimy następującą linię:

head -n 15 octane.pdb | tail -n 5

Jest to wariacja na temat potoku, który skonstruowaliśmy wcześniej: wybiera on linie 11-15 pliku octane.pdb.Pamiętaj, że nie uruchamiamy go jeszcze jako polecenia: umieszczamy polecenia w pliku.

Następnie zapisujemy plik (Ctrl-O w nano) i wychodzimy z edytora tekstu (Ctrl-X w nano).Sprawdź, czy katalog molecules zawiera teraz plik o nazwie middle.sh.

Po zapisaniu pliku możemy poprosić powłokę o wykonanie zawartych w nim poleceń.Nasza powłoka nazywa się bash, więc wykonujemy następujące polecenie:

$ bash middle.sh
ATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00

Wystarczy, że dane wyjściowe naszego skryptu są dokładnie takie same, jakie otrzymalibyśmy, uruchamiając ten potok bezpośrednio.

Text vs. Whatever

Zazwyczaj nazywamy programy takie jak Microsoft Word lub LibreOffice Writer „edytorami tekstu”, ale musimy być nieco bardziej ostrożni, gdy chodzi o programowanie. Domyślnie Microsoft Word używa plików .docx do przechowywania nie tylko tekstu, ale także informacji o formatowaniu, takich jak czcionki, nagłówki, a wkrótce. Te dodatkowe informacje nie są przechowywane jako znaki i nic nie znaczą dla narzędzi takich jak head: oczekują one, że pliki wejściowe będą zawierały tylko litery, cyfry i znaki interpunkcyjne występujące na standardowej klawiaturze komputerowej. Podczas edycji programów należy więc albo używać edytora tekstowego, albo uważać, by zapisywać pliki jako tekst jawny.

A co, jeśli chcemy wybrać wiersze z dowolnego pliku? Moglibyśmy edytować middle.sh za każdym razem, by zmienić nazwę pliku, ale zajęłoby to prawdopodobnie więcej czasu niż ponowne wpisanie polecenia w powłoce i wykonanie go z nową nazwą pliku.Zamiast tego, edytujmy middle.sh i uczyńmy go bardziej uniwersalnym:

$ nano middle.sh

Teraz, w „nano”, zastąp tekst octane.pdb specjalną zmienną zwaną :

head -n 15 "" | tail -n 5

Wewnątrz skryptu powłoki, oznacza „pierwszą nazwę pliku (lub inny argument) w wierszu poleceń”.Możemy teraz uruchomić nasz skrypt w ten sposób:

$ bash middle.sh octane.pdb
ATOM 9 H 1 -4.502 0.681 0.785 1.00 0.00ATOM 10 H 1 -5.254 -0.243 -0.537 1.00 0.00ATOM 11 H 1 -4.357 1.252 -0.895 1.00 0.00ATOM 12 H 1 -3.009 -0.741 -1.467 1.00 0.00ATOM 13 H 1 -3.172 -1.337 0.206 1.00 0.00

lub na innym pliku w ten sposób:

$ bash middle.sh pentane.pdb
ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00

Podwójne cudzysłowy wokół argumentów

Z tego samego powodu, dla którego umieszczamy zmienną pętli w cudzysłowie, na wypadek gdyby nazwa pliku zawierała spacje, otaczamy cudzysłowem.

Obecnie, musimy edytować middle.sh za każdym razem, gdy chcemy dostosować zakres zwracanych linii. Naprawmy to, konfigurując nasz skrypt tak, aby zamiast tego używał trzech argumentów wiersza poleceń. Po pierwszym argumencie wiersza poleceń (), każdy dodatkowy argument, który podamy, będzie dostępny przez specjalne zmienne , , , które odnoszą się odpowiednio do pierwszego, drugiego i trzeciego argumentu wiersza poleceń.

Wiedząc o tym, możemy użyć dodatkowych argumentów, aby określić zakres wierszy, które mają być przekazane odpowiednio do head i tail:

$ nano middle.sh
head -n "" "" | tail -n ""

Możemy teraz uruchomić:

$ bash middle.sh pentane.pdb 15 5
ATOM 9 H 1 1.324 0.350 -1.332 1.00 0.00ATOM 10 H 1 1.271 1.378 0.122 1.00 0.00ATOM 11 H 1 -0.074 -0.384 1.288 1.00 0.00ATOM 12 H 1 -0.048 -1.362 -0.205 1.00 0.00ATOM 13 H 1 -1.183 0.500 -1.412 1.00 0.00

Zmieniając argumenty do naszego polecenia możemy zmienić zachowanie naszego skryptu:

$ bash middle.sh pentane.pdb 20 5
ATOM 14 H 1 -1.259 1.420 0.112 1.00 0.00ATOM 15 H 1 -2.608 -0.407 1.130 1.00 0.00ATOM 16 H 1 -2.540 -1.303 -0.404 1.00 0.00ATOM 17 H 1 -3.393 0.254 -0.321 1.00 0.00TER 18 1

To działa, ale następnej osobie, która przeczyta middle.sh, może zająć chwilę zorientowanie się, co to robi.Możemy ulepszyć nasz skrypt, dodając na górze kilka komentarzy:

$ nano middle.sh
# Select lines from the middle of a file.# Usage: bash middle.sh filename end_line num_lineshead -n "" "" | tail -n ""

Komentarz zaczyna się od znaku # i biegnie do końca wiersza.Komputer ignoruje komentarze, ale są one nieocenione, aby pomóc ludziom (w tym Twojemu przyszłemu ja) zrozumieć i używać skryptów.Jedynym zastrzeżeniem jest to, że za każdym razem, gdy modyfikujesz skrypt, powinieneś sprawdzić, czy komentarz jest nadal dokładny: wyjaśnienie, które wysyła czytelnika w złym kierunku, jest gorsze niż jego brak.

A co, jeśli chcemy przetworzyć wiele plików w jednym potoku?Na przykład, jeśli chcemy posortować nasze .pdb pliki według długości, wpisalibyśmy:

$ wc -l *.pdb | sort -n

ponieważ wc -l podaje liczbę linii w plikach (przypomnijmy, że wc oznacza 'word count’, dodanie opcji -l oznacza 'count lines’ zamiast tego), a sort -n sortuje rzeczy numerycznie.Moglibyśmy umieścić to w pliku, ale wtedy sortowałoby to tylko listę .pdb plików w bieżącym katalogu.Jeśli chcemy być w stanie uzyskać posortowaną listę innych rodzajów plików, potrzebujemy sposobu na wprowadzenie tych wszystkich nazw do skryptu. Nie możemy użyć , i tak dalej, ponieważ nie wiemy ile jest plików. Zamiast tego, używamy specjalnej zmiennej $@, która oznacza, 'Wszystkie argumenty wiersza poleceń do skryptu powłoki’.Powinniśmy również umieścić $@ wewnątrz cudzysłowów, by obsłużyć przypadek argumentów zawierających spacje ("$@" jest specjalną składnią i odpowiada "" "" …).

Oto przykład:

$ nano sorted.sh
# Sort files by their length.# Usage: bash sorted.sh one_or_more_filenameswc -l "$@" | sort -n
$ bash sorted.sh *.pdb ../creatures/*.dat
9 methane.pdb12 ethane.pdb15 propane.pdb20 cubane.pdb21 pentane.pdb30 octane.pdb163 ../creatures/basilisk.dat163 ../creatures/minotaur.dat163 ../creatures/unicorn.dat596 total

List Unique Species

Leah ma kilkaset plików danych, z których każdy jest sformatowany w ten sposób:

2013-11-05,deer,52013-11-05,rabbit,222013-11-05,raccoon,72013-11-06,rabbit,192013-11-06,deer,22013-11-06,fox,12013-11-07,rabbit,182013-11-07,bear,1

Przykład tego typu pliku jest podany w data-shell/data/animal-counts/animals.txt.

Możemy użyć polecenia cut -d , -f 2 animals.txt | sort | uniq do uzyskania unikalnych gatunków w animals.txt. Aby uniknąć konieczności każdorazowego wpisywania tej serii poleceń, naukowiec może zamiast tego napisać skrypt powłoki.

Napisz skrypt powłoki o nazwie species.sh, który przyjmuje dowolną liczbę nazw roślin jako argumenty wiersza poleceń i używa wariantu powyższego polecenia do wypisania listy unikalnych gatunków występujących w każdym z tych plików osobno.

Rozwiązanie

# Script to find unique species in csv files where species is the second data field# This script accepts any number of file names as command line arguments# Loop over all filesfor file in $@doecho "Unique species in $file:"# Extract species namescut -d , -f 2 $file | sort | uniqdone

Załóżmy, że właśnie wykonaliśmy serię poleceń, które zrobiły coś pożytecznego – na przykład utworzyły wykres, który chcielibyśmy wykorzystać w pracy. Chcielibyśmy móc odtworzyć ten wykres później, jeśli zajdzie taka potrzeba, więc chcemy zapisać polecenia w pliku.Zamiast wpisywać je ponownie (i potencjalnie się mylić) możemy zrobić tak:

$ history | tail -n 5 > redo-figure-3.sh

Plik redo-figure-3.sh zawiera teraz:

297 bash goostats NENE01729B.txt stats-NENE01729B.txt298 bash goodiff stats-NENE01729B.txt /data/validated/01729.txt > 01729-differences.txt299 cut -d ',' -f 2-3 01729-differences.txt > 01729-time-series.txt300 ygraph --format scatter --color bw --borders none 01729-time-series.txt figure-3.png301 history | tail -n 5 > redo-figure-3.sh

Po chwili pracy w edytorze nad usunięciem numerów seryjnych na komendach,oraz usunięciu ostatniej linii,w której wywołaliśmy komendę history,mamy całkowicie dokładny zapis tego,jak stworzyliśmy tę figurę.

Why Record Commands in the History Before Running Them?

Jeśli uruchomisz polecenie:

$ history | tail -n 5 > recent.sh

ostatnim poleceniem w pliku jest samo polecenie history, tzn,powłoka dodała history do dziennika poleceń przed jego faktycznym wykonaniem. W rzeczywistości, powłoka zawsze dodaje polecenia do dziennika przed ich uruchomieniem. Jak sądzisz, dlaczego to robi?

Rozwiązanie

Jeśli polecenie powoduje awarię lub zawieszenie się czegoś, może być użyteczne wiedzieć, jakie to było polecenie, w celu zbadania problemu.Gdyby polecenie było zapisywane tylko po jego uruchomieniu, nie mielibyśmy zapisu ostatniego uruchomionego polecenia w przypadku awarii.

W praktyce, większość ludzi tworzy skrypty powłoki, uruchamiając polecenia w znaku zachęty powłoki kilka razy, aby upewnić się, że robią to dobrze, a następnie zapisując je w pliku do ponownego użycia.Ten styl pracy pozwala ludziom na recykling tego, co odkrywają o swoich danych i przepływie pracy za pomocą jednego wywołania history i odrobiny edycji, aby oczyścić dane wyjściowe i zapisać je jako skrypt powłoki.

Potok Nellego: Creating a Script

Przełożony Nelle nalegał, aby wszystkie jej analizy były powtarzalne. Najłatwiejszym sposobem na uchwycenie wszystkich kroków jest skrypt.

Najpierw wracamy do katalogu z danymi Nelle:

$ cd ../north-pacific-gyre/2012-07-03/

Uruchamia edytor i pisze następującą treść:

# Calculate stats for data files.for datafile in "$@"do echo $datafile bash goostats $datafile stats-$datafiledone

Zapisuje to w pliku o nazwie do-stats.sh, dzięki czemu może teraz powtórzyć pierwszy etap analizy, wpisując:

$ bash do-stats.sh NENE*.txt

Może też zrobić tak:

$ bash do-stats.sh NENE*.txt | wc -l

żeby na wyjściu była tylko liczba przetworzonych plików, a nie nazwy plików, które zostały przetworzone.

Jedną z rzeczy, na które należy zwrócić uwagę w skrypcie Nelle, jest to, że pozwala on osobie go uruchamiającej zdecydować, jakie pliki mają być przetwarzane.Mogła go napisać jako:

# Calculate stats for Site A and Site B data files.for datafile in NENE*.txtdo echo $datafile bash goostats $datafile stats-$datafiledone

Zaletą jest to, że zawsze wybiera właściwe pliki: nie musi pamiętać o wykluczeniu plików 'Z’. Wadą jest to, że zawsze wybiera tylko te pliki – nie może go uruchomić na wszystkich plikach (łącznie z 'Z’), ani na plikach 'G’ lub 'H’, które produkują jej koledzy na Antarktydzie, bez edycji skryptu.Jeśli chciałaby być bardziej odważna, mogłaby zmodyfikować skrypt tak, aby sprawdzał argumenty wiersza poleceń i używał NENE*.txt, jeśli żadne nie zostały podane.Oczywiście, wprowadza to kolejny kompromis między elastycznością a złożonością.

Zmienne w skryptach powłoki

W katalogu molecules wyobraź sobie, że masz skrypt powłoki o nazwie script.sh zawierający następujące polecenia:

head -n  tail -n  

Przebywając w katalogu molecules, wpisujemy następujące polecenie:

bash script.sh '*.pdb' 1 1

Którego z następujących wyjść można się spodziewać?

  1. Wszystkie wiersze pomiędzy pierwszym i ostatnim wierszem każdego pliku kończącego się na .pdb w katalogu molecules
  2. Pierwszy i ostatni wiersz każdego pliku kończącego się na .pdb w katalogu molecules katalog
  3. Pierwszy i ostatni wiersz każdego pliku w katalogu molecules
  4. Błąd z powodu cudzysłowów wokół *.pdb

Rozwiązanie

Prawidłowa odpowiedź to 2.

Zmienne specjalne $1, $2 i $3 reprezentują argumenty wiersza poleceń przekazane skryptowi, tak że uruchamiane są następujące polecenia:

$ head -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb$ tail -n 1 cubane.pdb ethane.pdb octane.pdb pentane.pdb propane.pdb

Powłoka nie rozwija argumentu '*.pdb', ponieważ jest on ujęty w cudzysłów. W związku z tym pierwszym argumentem skryptu jest '*.pdb', który zostaje rozwinięty w skrypcie przez head i tail.

Znajdź najdłuższy plik o podanym rozszerzeniu

Napisz skrypt powłoki o nazwie longest.sh, który jako argumenty przyjmuje nazwę katalogu i rozszerzenie nazwy pliku, a następnie wypisuje nazwę pliku z największą liczbą wierszy w tym katalogu o tym rozszerzeniu. Na przykład:

$ bash longest.sh /tmp/data pdb

wypisuje nazwę pliku .pdb w katalogu /tmp/data, który ma najwięcej linii.

Rozwiązanie

# Shell script which takes two arguments:# 1. a directory name# 2. a file extension# and prints the name of the file in that directory# with the most lines which matches the file extension.wc -l /*. | sort -n | tail -n 2 | head -n 1

Pierwsza część potoku, wc -l /*. | sort -n, liczy wiersze w każdym pliku i sortuje je numerycznie (największe jako ostatnie). Gdy jest więcej niż jeden plik, wc wypisuje również końcowy wiersz podsumowujący, podający całkowitą liczbę linii we wszystkich plikach. Użyjemy tail-n 2 | head -n 1, aby wyrzucić ten ostatni wiersz.

Za pomocą wc -l /*. | sort -n | tail -n 1 zobaczymy końcowy wiersz podsumowujący: możemy budować nasz potok po kawałku, aby mieć pewność, że rozumiemy dane wyjściowe.

Scriptu Reading Comprehension

Dla tego pytania rozważ jeszcze raz katalog data-shell/molecules.Zawiera on pewną liczbę plików .pdb oprócz wszelkich innych plików, które mogłeś utworzyć.Wyjaśnij, co zrobiłby każdy z trzech poniższych skryptów po uruchomieniu odpowiednio jakobash script1.sh *.pdb, bash script2.sh *.pdb i bash script3.sh *.pdb.

# Script 1echo *.*
# Script 2for filename in   do cat $filenamedone
# Script 3echo [email protected]

Rozwiązania

W każdym przypadku powłoka interpretuje symbol wieloznaczny w *.pdb przed przekazaniem wynikowej listy nazw plików jako argumentów skryptu.

Skrypt 1 wypisałby listę wszystkich plików zawierających w swojej nazwie kropkę.Argumenty przekazane do skryptu nie są w rzeczywistości nigdzie w nim użyte.

Skrypt 2 wypisałby zawartość pierwszych 3 plików z rozszerzeniem .pdb., i odnoszą się odpowiednio do pierwszego, drugiego i trzeciego argumentu.

Skrypt 3 wypisałby wszystkie argumenty skryptu (tj. wszystkie pliki .pdb), po których następuje .pdb.$@ odnosi się do wszystkich argumentów przekazanych skryptowi powłoki.

cubane.pdb ethane.pdb methane.pdb octane.pdb pentane.pdb propane.pdb.pdb

Debugowanie skryptów

Załóżmy, że zapisałeś następujący skrypt w pliku o nazwie do-errors.sh w katalogu north-pacific-gyre/2012-07-03 Nelle:

# Calculate stats for data files.for datafile in "$@"do echo $datfile bash goostats $datafile stats-$datafiledone

Po jego uruchomieniu:

$ bash do-errors.sh NENE*.txt

wynik jest pusty.Aby dowiedzieć się dlaczego, uruchom ponownie skrypt, używając opcji -x:

bash -x do-errors.sh NENE*.txt

Co pokazuje wyjście? Który wiersz jest odpowiedzialny za błąd?

Rozwiązanie

Opcja -x powoduje, że program bash działa w trybie debugowania. Powoduje to wypisanie każdego polecenia w trakcie jego wykonywania, co pomaga zlokalizować błędy. W tym przykładzie widzimy, że echo nic nie wypisuje. Zrobiliśmy literówkę w nazwie zmiennej pętli, a zmienna datfile nie istnieje, dlatego zwraca pusty łańcuch.

Kluczowe punkty

  • Zapisywanie poleceń w plikach (zwykle nazywanych skryptami powłoki) w celu ponownego użycia.

  • bash uruchamia polecenia zapisane w pliku.

  • $@ odnosi się do wszystkich argumentów wiersza poleceń skryptu powłoki.

  • , , itd, odnoszą się do pierwszego argumentu wiersza poleceń, drugiego argumentu wiersza poleceń itd.

  • Umieszczaj zmienne w cudzysłowach, jeśli wartości mogą zawierać spacje.

  • Pozwalanie użytkownikom na decydowanie, jakie pliki mają być przetwarzane, jest bardziej elastyczne i spójne z wbudowanymi poleceniami uniksowymi.

.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.