Skrypty Bash – Część 4 – Wejście i wyjście

Mikhail Raevskiy
Michał Raevskiy

Follow

Jul 28, 2020 – 11 min read

Ostatnim razem, w części 3 tej serii skryptów bash, rozmawialiśmy o parametrach linii poleceń i przełącznikach. Naszym dzisiejszym tematem jest wejście, wyjście i wszystko, co z tym związane.

Photo by Ben Kolde on Unsplash

Znasz już dwie metody pracy z tym, co wypisują skrypty linii komend.skryptów linii poleceń:

  • Wyświetlić dane wyjściowe na ekranie.
  • Przekierowanie danych wyjściowych do pliku.

Czasami coś musi być wyświetlone na ekranie, a coś musi być zapisane do pliku, więc musisz zrozumieć, jak dane wejściowe i wyjściowe są obsługiwane w Linuksie, co oznacza, że musisz się nauczyć, jak wysyłać wyniki działania skryptów tam, gdzie trzeba. Zacznijmy od omówienia standardowych deskryptorów plików.

Wszystko w Linuksie to pliki, w tym dane wejściowe i wyjściowe. System operacyjny identyfikuje pliki za pomocą deskryptorów.

Każdy proces może mieć do dziewięciu otwartych deskryptorów plików. Powłoka bash rezerwuje pierwsze trzy deskryptory o identyfikatorach 0, 1 i 2. Oto co one oznaczają.

  • 0STDIN– standardowy strumień wejściowy.
  • 1STDOUT– standardowy strumień wyjściowy.
  • 2STDERR– standardowy strumień błędów.

Te trzy specjalne deskryptory obsługują wejście i wyjście skryptu.
Trzeba dobrze zrozumieć standardowe strumienie. Można je porównać do fundamentu, na którym zbudowana jest interakcja skryptów ze światem zewnętrznym. Rozważmy szczegóły na ich temat.

STDIN

STDIN– jest to standardowe wejście powłoki. Dla terminala, standardowym wejściem jest klawiatura. Gdy skrypty używają znaku przekierowania wejścia – <, Linux zastępuje deskryptor pliku standardowego wejścia tym podanym w poleceniu. System odczytuje plik i przetwarza dane tak, jakby były wprowadzane z klawiatury.

Wiele poleceń basha pobiera dane wejściowe z STDIN chyba że w wierszu poleceń podano plik, z którego mają być pobierane dane. Na przykład, tak jest w przypadku zespołu cat.

Gdy wprowadzisz polecenie cat w wierszu poleceń bez podania parametrów, przyjmuje ono dane wejściowe z STDIN. Po wprowadzeniu kolejnego wiersza, catpo prostu wypisuje go na ekran.

STDOUT

STDOUT– standardowe wyjście powłoki. Domyślnie, jest to ekran. Większość poleceń bash wyprowadza dane STDOUTdo konsoli, co powoduje ich wyświetlenie w konsoli. Dane te można przekierować do pliku, dołączając je do jego zawartości za pomocą polecenia >>.

Mamy więc pewien plik z danymi, do którego możemy dodać inne dane za pomocą tego polecenia:

pwd >> myfile

To, co wypisze pwd zostanie dodane do pliku myfile, natomiast dane, które już się w nim znajdują nigdzie nie trafią.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ pwd >> myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
this is old file content
/home/raevskym/Desktop

Do tej pory tak dobrze, ale co jeśli spróbujesz zrobić coś takiego, jak pokazano poniżej, uzyskując dostęp do nieistniejącego plikuxfile, myśląc o tym wszystkim tak, abymyfile komunikat o błędzie trafił do pliku.

ls –l xfile > myfile

Po wykonaniu tego polecenia na ekranie zobaczymy komunikaty o błędach.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l xfile > myfile
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory

Próba dostępu do nieistniejącego pliku generuje błąd, ale powłoka nie przekierowała komunikatów o błędach do pliku, wyświetlając je. My jednak chcieliśmy, aby komunikaty o błędach trafiały do pliku. Co zrobić? Odpowiedź jest prosta – użyć trzeciego standardowego deskryptora.

STDERR

STDERR Jest to standardowy strumień błędów powłoki. Domyślnie, uchwyt ten wskazuje na to samo, na co wskazuje STDOUT, dlatego gdy wystąpi błąd, widzimy komunikat na ekranie.

Załóżmy więc, że chcemy przekierować komunikaty o błędach do, powiedzmy, pliku dziennika lub gdzie indziej, zamiast drukować je na ekranie.

Przekierowanie strumienia błędów

Jak już wiesz, deskryptor pliku dlaSTDERR to 2. Możemy przekierować błędy, umieszczając ten deskryptor przed poleceniem przekierowania:

ls -l xfile 2>myfile
cat ./myfile

Komunikat o błędzie trafi teraz do pliku myfile.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l xfile 2> myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
ls: cannot access 'xfile': No such flie or directory

Przekierowanie strumieni błędów i wyjścia

Podczas pisania skryptów wiersza poleceń może dojść do sytuacji, w której trzeba zorganizować zarówno przekierowanie komunikatu o błędzie, jak i przekierowanie standardowego wyjścia. Aby to osiągnąć, należy użyć poleceń przekierowania dla odpowiednich deskryptorów, wskazujących pliki, do których powinny trafić błędy i standardowe wyjście:

ls –l myfile xfile anotherfile 2> errorcontent 1> correctcontent

Zobaczmy bliżej:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l myfile xfile anotherfile 2> errorcontent 1> correctcontent
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./correctcontent
myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./errorcontent
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory
ls: cannot access 'anotherfile': No such file or directory

Powłoka przekieruje to, co polecenielsnormalnie wysyła doSTDOUTplikucorrectcontentze względu na jego konstrukcję1>. Komunikaty o błędach, które skończyłyby sięSTDERRup w pliku, sąerrorcontentpowodowane poleceniem przekierowania2>.

Jeśli to konieczne, iSTDERR, iSTDOUT mogą zostać przekierowane do tego samego pliku za pomocą polecenia&>:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l myfile xfile anotherfile &> content
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./content
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory
ls: cannot access 'anotherfile': No such file or directory
myfile

Po wykonaniu polecenia, to co jest przeznaczoneSTDERR iSTDOUT znajduje się w plikucontent.

Przekierowanie wyjścia w skryptach

Istnieją dwie metody przekierowania wyjścia w skryptach wiersza poleceń:

  • Przekierowanie tymczasowe, czyli przekierowanie wyjścia jednej linii.
  • Permanentne przekierowanie, czyli przekierowanie całego lub części wyjścia w skrypcie.

Tymczasowe przekierowanie wyjścia

W skrypcie możesz przekierować wyjście jednej linii do STDERR. Aby to zrobić, wystarczy użyć polecenia przekierowania, podając deskryptor STDERR, a przed numerem deskryptora umieścić znak ampersandu (&):

#!/bin/bash
echo "This is an error" >&2
echo "This is normal output"

Jeśli uruchomisz skrypt, obie linie trafią na ekran, ponieważ, jak już wiesz, domyślnie błędy są wyświetlane w tym samym miejscu, co normalne dane.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This is an error
This is normal output

Uruchommy skrypt tak, aby dane wyjścioweSTDERR trafiały do pliku.

./myscript 2> myfile

Jak widać, teraz zwykłe dane wyjściowe trafiają do konsoli, a komunikaty o błędach do pliku.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript 2> content
This is normal output
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./content
This is an error

Stałe przekierowanie wyjścia

Jeśli skrypt musi przekierować wiele wyświetlanych danych, to echoniewygodne jest dodawanie odpowiedniej komendy do każdego wywołania. Zamiast tego można ustawić, aby dane wyjściowe były przekierowywane do określonego deskryptora na czas działania skryptu, używając polecenia exec:

#!/bin/bash
exec 1>outfile
echo "This is a test of redirecting all output"
echo "from a shell script to another file."
echo "without having to redirect every line"

Uruchommy skrypt:

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat outfile
This is a test of redirecting all output
from a shell script to another file.
without having to redirect every line

Jeśli spojrzymy na plik podany w poleceniu przekierowania wyjścia, okaże się, że wszystko, co wyszło z poleceniaecho trafiło do tego pliku.

Komendaexec może być użyta nie tylko na początku skryptu, ale również w innych miejscach:

#!/bin/bash
exec 2>myerror
echo "This is the start of the script"
echo "now redirecting all output to another location"
exec 1>myfile
echo "This should go to the myfile file"
echo "and this should go to the myerror file" >&2

Tak wygląda sytuacja po uruchomieniu skryptu i obejrzeniu plików, do których przekierowaliśmy dane wyjściowe.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This is the start of the script
now redirecting all output to another location
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myerror
and this should go to the myerror file
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
This should go to the myfile file

Najpierw komendaexec ustawia przekierowanie wyjścia zSTDERR do plikumyerror. Następnie dane wyjściowe kilku poleceń sąechosyłane doSTDOUTi wyświetlane na ekranie. Następnie komendaexec ustawia wysyłanie tego, co trafi doSTDOUT plikumyfile, a na koniec, używamy polecenia przekierowaniaSTDERR w komendzieecho, co prowadzi do zapisania odpowiedniej linii do pliku.myerror.

Opanowawszy to, możesz przekierować dane wyjściowe tam, gdzie chcesz. Teraz porozmawiajmy o przekierowaniu danych wejściowych.

Przekierowanie danych wejściowych w skryptach

Aby przekierować dane wejściowe, możesz użyć tej samej techniki, której użyliśmy do przekierowania danych wyjściowych. Na przykład, polecenie exec pozwala na stworzenie źródła danych dla STDIN pliku:

exec 0< myfile

Ta komenda mówi powłoce, aby jej dane wejściowe pochodziły z pliku myfile, a nie zwykłego STDIN. Zobaczmy przekierowanie danych wejściowych w akcji:

#!/bin/bash
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$(( $count + 1 ))
done

To jest to, co pojawi się na ekranie po uruchomieniu skryptu.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: this is the first line
Line #2: this is the second line
Line #3: this is the third line

W jednym z poprzednich artykułów dowiedziałeś się, jak używać komendyread do odczytywania danych wejściowych użytkownika z klawiatury. Jeśli przekierujesz dane wejściowe, czyniąc źródłem danych plik, to polecenieread, próbując odczytać dane zSTDIN, odczyta je z pliku, a nie z klawiatury.

Niektórzy administratorzy Linuksa używają tego podejścia do odczytywania, a następnie przetwarzania plików logów.

Tworzenie własnych przekierowań wyjścia

Przekierowując wejście i wyjście w skryptach, nie jesteś ograniczony do trzech standardowych deskryptorów plików. Jak wspomniano, możesz mieć do dziewięciu otwartych deskryptorów. Pozostałe sześć, oznaczone numerami od 3 do 8, może być użyte do przekierowania wejścia lub wyjścia. Każdy z nich może być przypisany do pliku i użyty w kodzie skryptu.

Do przypisania deskryptora dla danych wyjściowych można użyć polecenia exec:

#!/bin/bash
exec 3>myfile
echo "This should display on the screen"
echo "and this should be stored in the file" >&3
echo "And this should be back on the screen"

Po uruchomieniu skryptu część danych wyjściowych trafi na ekran, a część do pliku z deskryptorem 3.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This should display on the screen
And this should be back on the screen
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
and this should be stored in the file

Tworzenie deskryptorów plików dla danych wejściowych

Możesz przekierować dane wejściowe w skrypcie w taki sam sposób jak dane wyjściowe. Przechowuj STDIN w innym deskryptorze przed przekierowaniem danych wejściowych.

Po zakończeniu czytania pliku, możesz przywrócić STDIN i używać go jak zwykle:

#!/bin/bash
exec 6<&0
exec 0< myfile
count=1
while read line
do
echo "Line #$count: $line"
count=$(( $count + 1 ))
done
exec 0<&6
read -p "Are you done now? " answer
case $answer in
y) echo "Goodbye";;
n) echo "Sorry, this is the end.";;
esac

Testujmy skrypt:

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: First line
Line #2: Second line
Line #3: Third line
Are you done now? y
Goodbye
raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: First line
Line #2: Second line
Line #3: Third line
Are you done now? n
Sorry, this is the end.

W tym przykładzie deskryptor pliku 6 został użyty do przechowywania referencji doSTDIN. Następnie wykonano przekierowanie danych wejściowych, źródłem danych dla plikuSTDIN stało się. Po tym, dane wejściowe dla poleceniaread pochodziły z przekierowanegoSTDIN, czyli z pliku.

Po odczytaniu pliku, przywracamySTDIN jego pierwotny stan, przekierowując go do deskryptora6. Teraz, w celu sprawdzenia, czy wszystko działa poprawnie, skrypt zadaje użytkownikowi pytanie, czeka na dane wejściowe z klawiatury i przetwarza to, co zostało wprowadzone.

Zamykanie deskryptorów plików

Powłoka automatycznie zamyka deskryptory plików po zakończeniu skryptu. Jednakże, w niektórych przypadkach, konieczne jest ręczne zamknięcie deskryptorów zanim skrypt zakończy pracę. Aby zamknąć uchwyt, należy go przekierować do &-. Wygląda to tak:

#!/bin/bash
exec 3> myfile
echo "This is a test line of data" >&3
exec 3>&-
echo "This won't work" >&3

Po wykonaniu skryptu otrzymamy komunikat o błędzie.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
./myscript: line 5: 3: Bad file descriptor

Chodzi o to, że próbowaliśmy uzyskać dostęp do nieistniejącego deskryptora pliku.

Bądź ostrożny przy zamykaniu deskryptorów plików w skryptach. Jeśli wysłałeś dane do pliku, następnie zamknąłeś uchwyt, a następnie otworzyłeś go ponownie, powłoka zastąpi istniejący plik nowym. To znaczy, wszystko, co zostało wcześniej zapisane do tego pliku, zostanie utracone.

Pobieranie informacji o otwartych uchwytach

Aby uzyskać listę wszystkich otwartych deskryptorów w Linuksie, możesz użyć polecenia lsof. Wiele dystrybucji, jak Fedora, ma narzędzie lsof w /usr/sbin. To polecenie jest bardzo przydatne, ponieważ wyświetla informacje o każdym uchwycie otwartym w systemie. Dotyczy to zarówno tego, co jest otwierane przez procesy działające w tle, jak i tego, co jest otwierane przez zalogowanych użytkowników.

Polecenie to posiada wiele klawiszy, przyjrzyjmy się najważniejszym z nich.

  • -pPozwala na określenie IDprocesu.
  • -dPozwala na określenie numeru deskryptora, o którym chcemy uzyskać informacje.

W celu poznania PID bieżącego procesu, można użyć specjalnej zmiennej środowiskowej $$, do której powłoka zapisuje bieżący PID.

Klucz ten jest -awykorzystywany do wykonania operacji boolean ANDna wynikach zwróconych przez użycie pozostałych dwóch kluczy:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
myscript 24 raevskym 0u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 1u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 2u CHR 4,1 25332 /dev/tty1

Typ plików związanych zSTDINSTDOUTiSTDERR — CHR (tryb znakowy). Ponieważ wszystkie one wskazują na terminal, nazwa pliku odpowiada nazwie urządzenia przypisanego do terminala. Wszystkie trzy standardowe pliki są dostępne do odczytu i zapisu.

Przyjrzyjrzyjmy się wywołaniu polecenialsof ze skryptu, w którym oprócz standardowego, otwarte są inne deskryptory:

#!/bin/bash
exec 3> myfile1
exec 6> myfile2
exec 7< myfile3
lsof -a -p $$ -d 0,1,2,3,6,7

Tak się stanie, jeśli uruchomimy ten skrypt.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
myscript 24 raevskym 0u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 1u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 2u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 3w REG 0,14 0 13510 ~/bash_course/myfile1
myscript 24 raevskym 6w REG 0,14 0 13229 ~/bash_course/myfile2

Skryptor otworzył dwa deskryptory dla wyjścia (3 i6) i jeden dla wejścia (7). Podane są tu również ścieżki do plików używanych do konfiguracji deskryptorów.

Tłumienie danych wyjściowych

Czasami trzeba się upewnić, że polecenia w skrypcie, które na przykład mogą być wykonywane jako proces w tle, nie wyświetlają niczego na ekranie. Aby to zrobić, można przekierować wyjście do /dev/null. Jest to coś w rodzaju „czarnej dziury”.

Na przykład, oto jak stłumić komunikaty o błędach:

ls -al badfile anotherfile 2> /dev/null

Tego samego podejścia używa się, gdy na przykład trzeba wyczyścić plik bez usuwania go:

cat /dev/null > myfile

Wyniki

Dzisiaj dowiedziałeś się, jak działa wejście i wyjście w skryptach wiersza poleceń. Teraz wiesz jak obsługiwać deskryptory plików, tworzyć, przeglądać i zamykać je, wiesz o przekierowywaniu strumieni wejściowych, wyjściowych i błędów. To wszystko jest bardzo ważne w tworzeniu skryptów basha.

Następnym razem porozmawiamy o sygnałach linuksowych, jak je obsługiwać w skryptach, jak uruchamiać zaplanowane zadania i zadania w tle.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *