- Overview
- Text vs. Whatever
- Podwójne cudzysłowy wokół argumentów
- List Unique Species
- Rozwiązanie
- Why Record Commands in the History Before Running Them?
- Rozwiązanie
- Potok Nellego: Creating a Script
- Zmienne w skryptach powłoki
- Rozwiązanie
- Znajdź najdłuższy plik o podanym rozszerzeniu
- Rozwiązanie
- Scriptu Reading Comprehension
- Rozwiązania
- Debugowanie skryptów
- Rozwiązanie
- Kluczowe punkty
Overview
Nauczanie: 30 min
Ćwiczenia: 15 minPytania
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 plikmiddle.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 katalogmolecules
zawiera teraz plik o nazwiemiddle.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 jakhead
: 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, edytujmymiddle.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
itail
:$ 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, żewc
oznacza 'word count’, dodanie opcji-l
oznacza 'count lines’ zamiast tego), asort -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 wanimals.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łahistory
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 nazwiescript.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ć?
- Wszystkie wiersze pomiędzy pierwszym i ostatnim wierszem każdego pliku kończącego się na
.pdb
w katalogumolecules
- Pierwszy i ostatni wiersz każdego pliku kończącego się na
.pdb
w katalogumolecules
katalog- Pierwszy i ostatni wiersz każdego pliku w katalogu
molecules
- 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 przezhead
itail
.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żyjemytail-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
ibash 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 katalogunorth-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 programbash
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, żeecho
nic nie wypisuje. Zrobiliśmy literówkę w nazwie zmiennej pętli, a zmiennadatfile
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.
.