Журнал LinuxFormat - перейти на главную

LXF144:Bash

Материал из Linuxformat
Перейти к: навигация, поиск
Bash Соз­да­ем сис­те­му опо­ве­ще­ний и диа­ло­го­вые ок­на для скрип­тов

Содержание

Bash: Гра­фи­ка и оболочке

Вся мощь сис­те­мы кро­ет­ся в ко­манд­ной стро­ке, но не­ко­то­рым лю­дям нель­зя до­ве­рить на­бор ко­манд. Ник Вейч объ­е­ди­ня­ет пер­вое и вто­рое.


Как из­вест­но, гра­фи­че­­ский ин­тер­фейс по­мо­га­ет про­стым обы­ва­те­лям лег­ко и удоб­но ра­бо­тать с ком­пь­ю­те­ром. И, как из­вест­но, ре­шать оп­ре­де­лен­ные ком­пь­ю­тер­ные за­да­чи в обо­лоч­ке бы­ст­рее и безо­паснее, а иногда это во­об­ще един­ст­вен­ный спо­соб ре­шения. Ин­тер­фейс нелег­ко за­про­грам­ми­ро­вать с по­мо­щью скрип­тов, а вот в обо­лоч­ке скрип­ты как дома. Но и тут найде­тся по­вод для соз­дания ин­тер­фей­са ва­ших пре­крас­ных скрип­тов с че­ло­ве­че­­ст­вом: это до­пустит к скрип­там дру­гих смерт­ных или сде­ла­ет скрип­ты чуть бо­лее ав­то­ном­ны­ми, не тре­бую­щи­ми особо­го вме­ша­тель­ст­ва поль­зо­ва­те­ля.

Опо­ве­щения о со­бы­ти­ях

Пер­вич­ное ва­ше по­же­лание – к при­ме­ру, из­вес­тить ко­го-то, что скрипт или некая ко­ман­да от­ра­бо­та­ли. В со­вре­мен­ных ра­бо­чих сто­лах та­кой ме­ханизм пре­ду­смот­рен, и он на­зы­ва­ет­ся опо­вещением о со­бы­ти­ях [notification event]. Опо­ве­щения – это те ма­лень­кие окош­ки с со­об­щения­ми, что ка­кая-то про­грам­ма «упа­ла», что вы под­клю­чи­ли ка­ме­ру или что ваш же­ст­кий диск ра­бо­та­ет на пре­де­ле. В раз­лич­ных ра­бо­чих сто­лах они об­ра­ба­ты­ва­ют­ся по-сво­ему (на­при­мер, в KDE и Gnome об­ра­бот­чи­ки раз­ные), но, к сча­стью, freedesktop.org стан­дар­ти­зи­ро­вал ме­то­ды их ра­бо­ты, и об­ра­щать­ся к ним из команд­ной стро­ки мож­но оди­на­ко­вым об­ра­зом.

Еще бо­лее уп­ро­ща­ет си­туа­цию то, что все эти дей­ст­вия мож­но вы­полнить с по­мо­щью про­стой ко­ман­ды: notify-send. Она принима­ет несколь­ко па­ра­мет­ров-клю­чей, оп­ре­де­ляю­щих дли­тель­ность ото­бра­жения со­об­щения, и па­ру строк – за­го­ло­вок и, при необ­хо­ди­мо­сти, бо­лее под­роб­ное опи­са­ние. Вот при­мер ис­поль­зо­ва­ния ко­ман­ды:

notify-send -t 1000 ‘Hello’ ‘World’

Ко­неч­но, в Bash мы мо­жем сде­лать и по­хит­рей – на­при­мер, ис­поль­зо­вать пе­ре­мен­ные:

d=`date`;notify-send ‘The date is:’ “$d”

или от­прав­лять со­об­ще­ния по ус­ло­вию:

 if [$snd_msg-eq “1” ]
  then notify-send -t 2000 ‘yes’ ‘it is one’
 fi

или из­ме­нять со­об­ще­ние в за­ви­си­мо­сти от ре­зуль­та­та ра­бо­ты коман­ды:

 cp /tmp/bigfile /home/myname
 test $? = 0 && msg=’Большой файл ско­пи­ро­ван’ || msg=’Копирование рух­ну­ло’
 notify-send -t 4000 ‘File Copy’ “$msg

Пре­ж­де чем пе­рей­ти к но­во­му эта­пу, ска­жем па­ру слов о по­следних ко­ман­дах, ко­то­рые неко­то­рым чи­та­те­лям мо­гут по­ка­зать­ся необыч­ны­ми. Пер­вая ко­ман­да в дан­ном слу­чае про­сто ко­пи­ру­ет файл из од­но­го мес­та в дру­гое. Bash фор­ми­ру­ет воз­вра­щае­мое зна­чение – 0 (ниче­го не про­изош­ло) или 1 (про­изош­ла ошиб­ка). Во­об­ще-то ко­ман­да мо­жет вер­нуть сколь­ко угод­но зна­чений, но 0 всегда оз­на­ча­ет, что она сде­ла­ла то, что на нее воз­лагалось. Воз­вра­щае­мое зна­чение по­следней ко­ман­ды со­хра­ня­ет­ся в пе­ре­мен­ной ок­ру­жения, доступ к ко­то­рой мы по­лу­ча­ем с по­мо­щью $?.

В сле­дую­щей стро­ке кон­ст­рук­ци­ей test мы про­ве­ря­ем, яв­ля­eт­ся ли усло­вие (в дан­ном слу­чае, воз­вра­щае­мое зна­чение по­следней ко­ман­ды) ис­тин­ным (по­это­му за ним сле­ду­ет &&) или лож­ным (по­это­му за ним сле­ду­ет ||). В ре­зуль­та­те мы вы­во­дим раз­лич­ные со­об­щения в за­ви­си­мо­сти от то­го, сра­бо­та­ла или не сра­бо­та­ла ко­ман­да – вот вам ма­лень­кий при­мер умений Bash. И этот со­вет достал­ся вам да­ром! Ведь ста­тья-то про гра­фи­че­­ский ин­тер­фейс...

Ус­та­нов­ка на­по­ми­наний

Ис­поль­зо­вание Bash вме­сте с сис­те­мой опо­ве­щения мо­жет быть весь­ма пло­до­твор­ным. На­пример, пусть ка­ж­дые пять ми­нут вам вы­да­ют све­дения об ис­поль­зо­вании дис­ка:

while true; do msg=`df`; notify-send “disk usage” “$msg”; sleep 120 ; done

или да­же дер­жат вас в кур­се све­жих со­об­ще­ний яд­ра:

sudo tail -n0 -f /var/log/messages | while read msg; do notifysend “Но­вая за­пись в жур­на­ле” “$msg”; done

С виду ­кажет­ся, что осо­бо­го удоб­ст­ва здесь нет, но при по­мо­щи ря­да па­ра­мет­ров мож­но сде­лать ко­ман­ду notify-send бо­лее удоб­ной и да­же бо­лее ин­те­рак­тив­ной. Из­меним наш ис­ход­ный скрипт:

 #!/usr/bin/env bash
 winicon=/usr/share/icons/gnome/32x32/status/dialoginformation.png
 failicon=/usr/share/icons/gnome/32x32/status/dialogwarning.png
 dest=$2
 cp $1 $dest
 if [ $? -eq 0 ]
 then
   msg=”Ваш файл ско­пи­ро­ван и на­хо­дит­ся в \n<a href=’$dest>$dest</a>icon=$winicon
 else
   msg=”Что-то по­ло­ма­лось, но я тут ни при чем”
   icon=$failicon
 fi
 notify-send -t 5000 -i $icon ‘File Copy’ “$msg

Не тру­ди­тесь на­би­рать скрипт це­ли­ком: он имеется на на­шем DVD. Не то что мы счи­та­ем вас лен­тяя­ми – про­сто Bash неве­ро­ят­но возбудим на­счет ка­вы­чек, а на бу­ма­ге слож­но­ва­то за­ме­тить раз-ницу ме­ж­ду пря­мой ' и непря­мой ка­выч­ка­ми. В Bash поч­ти всегда упот­реб­ля­ют­ся пря­мые ка­выч­ки.

Мы внесли два из­менения в по­следний скрипт и из­менили про­вер­ку. Пе­ре­мен­ные в на­ча­ле со­дер­жат пу­ти к фай­лам изо­бра­жений – здесь они по­черп­ну­ты из биб­лио­те­ки изо­бра­жений Gnome, но вы мо­же­те взять и свои кар­тин­ки. За­тем они пе­ре­да­ют­ся ко­ман­де notify-send, что­бы в со­об­щении появи­лась икон­ка – этак будет кру­че!

С по­мо­щью спе­ци­аль­ных пе­ре­мен­ных Bash $1 и $2 мы по­лу­ча­ем пер­вый и вто­рой ар­гу­мен­ты скрип­та и ис­поль­зу­ем их как ис­точник и ме­сто на­зна­чения для ко­ман­ды copy, и опять же мо­жем про­ве­рить ее ре­зуль­тат с по­мо­щью $?.

Мы вер­ну­лись к кон­ст­рук­ции if; then; fi, по­то­му что по ней про­ще по­нять, что про­ис­хо­дит, и не нуж­на ог­ром­ная стро­ка, ко­то­рую дол­го на­би­рать. По су­ти ло­ги­ка оста­лась прежней – в за­ви­си­мо­сти от успе­ха или неуда­чи от­ра­бот­ки ко­ман­ды ото­бра­жа­ют­ся раз­ные икон­ки и со­об­щения.

Са­мо со­об­щение на сей раз со­дер­жит некий HTML-код. Он соз­да­ет ра­бо­таю­щую ссыл­ку на ка­та­лог, ку­да был ско­пи­ро­ван файл, и с этой ссыл­кой опо­ве­щения ста­но­вят­ся еще по­лезнее. Впро­чем, в опо­ве­щениях бу­дут ра­бо­тать не все HTML-тэ­ги (в кон­це кон­цов, это про­сто незатейливая сис­те­ма со­об­щений, без претензий), и ес­ли со­об­щение ока­зы­ва­ет­ся пустым, воз­мож­но, вы вклю­чи­ли в него ка­кие-то тэ­ги, ко­то­рые не бы­ли об­ра­бо­та­ны или ко­то­ры­ми нель­зя поль­зо­вать­ся.


Не­достат­ки сис­те­мы опо­ве­щения как сред­ст­ва взаи­мо­дей­ст­вия поль­зо­ва­те­лей оче­вид­ны. Хо­тя с ее по­мо­щью лег­ко от­пра­вить со­об­щение поль­зо­ва­те­лю ра­бо­че­го сто­ла, воз­мож­но­сти от­прав­ки от­ве­та вы­пол­няю­щей­ся про­грам­ме ог­раниче­ны.

Бла­го­да­ря ле­жа­щей в осно­ве Unix идее соз­дания от­дель­ных ути­лит для ре­шения уз­ких за­дач и их объ­е­динения для ре­шения за­дач бо­лее слож­ных, в Linux мож­но тво­рить чу­де­са. К со­жа­лению, боль­шин­ст­во лю­дей это­го не осоз­на­ют – пре­ж­де все­го по­то­му, что бо­ят­ся команд­ных строк.

Поль­зо­ва­тель­ский ввод в скрип­тах от­нюдь не нов. В Bash это де­ла­ет­ся со­всем про­сто:

read -p “Прав­да, здо­ро­во?” -e input
echo Со­гла­сен, здо­ро­во $input

Про­бле­ма здесь в том, что все еще не обой­тись без команд­ной стро­ки. Тра­ди­ци­он­ный спо­соб ре­шения этой про­бле­мы – Ncurses. Из­на­чаль­но Curses пред­став­ля­ла со­бой биб­лио­те­ку, раз­ра­бо­тан­ную эн­ту­зиа­ста­ми из BSD для иг­ры на тер­ми­на­лах, но идея уш­ла го­раз­до даль­ше. В Linux ис­поль­зу­ют­ся биб­лио­те­ки Ncurses, раз­ра­бо­тан­ные в се­ре­дине 1990‑х и зна­ко­мые ка­ж­до­му, кто пы­тал­ся уста­нав­ли­вать BSD или Linux при­мер­но до 2001 го­да. Ка­ким-то хит­рым об­ра­зом она соз­да­ет в тер­ми­на­ле тек­сто­вый эк­ран с несколь­ки­ми точ­ка­ми фо­ку­са, пе­ре­ме­щать­ся ме­ж­ду ко­то­ры­ми мож­но кла­ви­шей Tab. Эти точ­ки мо­гут пред­став­лять со­бой прак­ти­че­­ски лю­бые эле­мен­ты стан­дарт­но­го гра­фи­че­­ско­­го ок­на: на­при­мер, га­лоч­ки или тек­сто­вые по­ля.

Да­вай­те по­про­бу­ем од­ну та­кую.

 src=$1
 dest=`dialog --fselect ~ 15 25 3>&2 2>&1 1>&3`
 cp $src $dest
 test $? = 0 && msg=’Файл ско­пи­ро­ван’ || msg=’Файл рух­нул’
 dialog --msgbox$msg6 20

Боль­шую часть скрип­та мы уже рас­смот­ре­ли пре­ж­де, но во вто­рой стро­ке есть некая хит­рость. Пе­ре­мен­ная на­зна­чения уста­нав­ли­ва­ет­ся с по­мо­щью вы­ра­жения в об­рат­ных апо­ст­ро­фах – это зна­чит, что Bash вы­полнит ко­ман­ду внут­ри апо­ст­ро­фов (в анг­лий­ской рас­клад­ке клавиатуры та­кой апо­ст­роф обыч­но на­хо­дит­ся над кла­ви­шей Tab) и за­менит вы­ра­жение вы­во­дом этой коман­ды. По­нять это бы­ло бы лег­че, не будь в ко­ман­де осложнений.

Пре­ж­де все­го, знай­те, что dialog – это ко­ман­да. За ней всегда сле­ду­ет ключ, обо­зна­чаю­щий тип диа­ло­го­во­го ок­на и па­ра­мет­ры ок­на. В дан­ном слу­чае это диа­лог вы­бо­ра фай­ла, ко­то­рый принима­ет ис­ход­ный ка­та­лог (причем Bash за­ме­ня­ет ~ на ваш до­машний ка­та­лог), ши­ри­ну и вы­со­ту ок­на. А что за аб­ра­ка­даб­ра идет даль­ше? От боль­шо­го ума ав­то­ры ре­ши­ли от­прав­лять вы­вод в по­ток STDERR вме­сто STDOUT, то есть вам не уда­ст­ся его уви­деть. Мно­же­ст­во скрип­тов, ис­поль­зую­щих Dialog, пе­ре­на­прав­ля­ют вы­вод в файл и за­тем его вы­во­дят. Этот фраг­мент ис­поль­зу­ет стан­дарт­ные пе­ре­на­прав­ления, что­бы по­ме­нять мес­та­ми STDOUT (ка­нал 1) и STDERR (ка­нал 2) че­рез до­полнитель­ный ка­нал (3). Все, что он на са­мом де­ле де­ла­ет – по­лу­ча­ет зна­чение из диа­ло­го­во­го ок­на, соз­дан­но­го ко­ман­дой dialog, и при­сваи­ва­ет его пе­ре­мен­ной.

Ос­таль­ная часть скрип­та долж­на быть оче­вид­ной. В кон­це мы вы­зы­ва­ем dialog сно­ва, на этот раз с про­стым окош­ком со­об­щения, что­бы по­ка­зать ре­зуль­тат опе­ра­ции.

Ис­поль­зо­вание Xdialog

Су­ще­ст­ву­ет мно­же­ст­во дру­гих ти­пов диа­ло­го­вых окон, но пре­ж­де чем нас ув­ле­кут сим­па­тич­ные, но не вполне со­вре­мен­ные Ncurses, са­мое вре­мя рас­ска­зать об Xdialog. Те­перь читай­те внима­тель­но, по­то­му что сле­дую­щее пред­ло­жение – ложь. Xdialog – удоб­ная за­ме­на Dialog, в ко­то­рой вме­сто жал­кой ими­та­ции ис­поль­зу­ют­ся на­стоя­щие ок­на GTK.

За­ме­ти­ли ложь? Xdialog – за­ме­на толь­ко поч­ти. Син­так­сис по боль­шей час­ти иден­ти­чен. В Xdialog есть па­ра лишних оп­ций для вы­бо­ра шриф­тов и про­ри­сов­ки диа­ло­га. В це­лях со­вмес­ти­мо­сти Dialog принима­ет мно­же­ст­во этих оп­ций и про­сто иг­но­ри­ру­ет их, по­это­му по крайней ме­ре для про­стых скрип­тов мож­но поль­зо­вать­ся и тем, и дру­гим, су­ще­ст­вен­но не из­ме­няя код. (Со­вет: за­ве­ди­те в на­ча­ле скрип­та пе­ре­мен­ную, ко­то­рая бу­дет со­дер­жать фак­ти­че­­ски ис­поль­зуе­мую ко­ман­ду.)

И наш код те­перь вы­гля­дит так:


 src=$1
 dest=`Xdialog --fselect ~ 15 25 3>&2 2>&1 1>&3`
 cp $src $dest
 test $? = 0 && msg=’Файл ско­пи­ро­ван’ || msg=’Файл рух­нул’
 Xdialog --msgbox$msg6 20

Как ви­ди­те, Xdialog оста­ет­ся со­вмес­ти­мым, и для воз­вра­щения ре­зуль­та­та скрип­ту ему нуж­но про­де­лы­вать те же опе­ра­ции с ка­на­ла­ми. По­это­му, хо­тя с ним мы из­ба­ви­лись от ста­ро­мод­ных око­шек Ncurses, он все еще до­воль­но неудо­бен с точ­ки зрения на­пи­сания скрип­та. По­это­му не бу­дем тра­тить на него вре­мя и зай­мем­ся Zenity.

Опе­ра­ция Zenity

Хо­тя Xdialog и пе­ре­но­сит нас в XXI век с точ­ки зрения сти­льности, с ним все еще не обойтись без за­трат немалых уси­лий на на­пи­сание скрип­та. Манипу­ля­ция с ка­на­ла­ми то­же бу­дет ра­бо­тать не всегда, по­то­му что иногда вам нуж­ны зна­чения из STDOUT вме­сто STDERR, а иногда – из обо­их по­то­ков.

Zenity несо­вмес­тим с Dialog или Xdialog, хо­тя в общем-то по­хож на них. Кое в чем он уп­ро­щен и не де­ла­ет все в точ­ности так же, как Xdialog, но пре­достав­ля­ет симпатич­ный ин­тер­фейс GTK, и им будет сравнительно нетруд­но восполь­зо­вать­ся в скрип­тах.

На­пи­шем про­стой скрипт для ути­ли­ты FFmpeg и скон­вер­ти­ру­ем с ее по­мо­щью файл, при­няв от поль­зо­ва­те­ля ряд на­стро­ек.

Пер­вое, что мы сде­ла­ем в этом слу­чае – про­ве­рим, был ли за­пу­щен скрипт с именем фай­ла в ка­че­­ст­ве па­ра­мет­ра, а ес­ли нет – за­ста­вим поль­зо­ва­те­ля вы­брать файл.

[ “$#” -eq 1 ] && src=$1 \
|| src=$(zenity --title “Вы­бе­ри­те файл” --file-selection)

Здесь про­ве­ря­ет­ся количество пе­ре­дан­ных па­ра­мет­ров (в квад­рат­ных скоб­ках), и ли­бо пер­вый па­ра­метр за­пи­сы­ва­ет­ся в пе­ре­мен­ную src, ли­бо мы по­лу­ча­ем путь к фай­лу от Zenity.

По­ме­щение вы­ра­жения в $(…) в Bash ана­ло­гич­но об­рат­ным апо­ст­ро­фам. Со­дер­жи­мое ско­бок вы­пол­ня­ет­ся как ко­ман­ды, и их ре­зуль­тат под­став­ля­ет­ся на их ме­сто.

Син­так­сис очень прост – Zenity не ста­ра­ет­ся выглядеть сложнее, чем он есть. У него есть несколь­ко оп­ций, ко­то­рые мож­но ука­зать, но зна­чения по умол­чанию пре­крас­но под­хо­дят для боль­шин­ст­ва си­туа­ций, и вам не нуж­но во­зить­ся с пе­ре­на­прав­ления­ми ка­на­лов.

Те­перь по­смот­рим на спи­сок:

outsize=$(zenity --list --text “ За­дай­те вы­ход­ное раз­ре­ше­ние” \
--height 280 --radiolist \
--column “Pick one “ --column “size” --column “format” \
FALSE 352x288 CIF \
FALSE 320x240 QVGA \
FALSE 640x480 VGA \
FALSE 800x600 SVGA \
TRUE 1024x768 XGA \
FALSE 1280x720 HD720 \
)

На этот раз мы пе­ре­да­ли па­ра­метр list, что­бы соз­дать спи­сок зна­чений для вы­бо­ра. Так как мы ука­за­ли radiolist, в пер­вом столб­це долж­но быть зна­чение TRUE для ра­дио­кноп­ки по умол­чанию и FALSE для осталь­ных. Есть еще два столб­ца: в од­ном помещаются са­ми раз­ре­шения, в дру­гом – про­чая ин­фор­ма­ция; в дан­ном слу­чае – аб­бре­виа­ту­ра фор­ма­та. За­тем следуют собст­венно зна­чения. Фра­зы из несколь­ких слов, ес­ли таковые имеются, необходимо взять в ка­выч­ки; удоб­но так­же восполь­зо­вать­ся пе­ре­но­са­ми строк, что­бы ка­ж­дый эле­мент спи­ска на­хо­дил­ся на но­вой стро­ке.

Зна­чение из вто­ро­го столб­ца, ко­то­рое предупредительно со­от­вет­ст­ву­ет фор­ма­ту, ожи­дае­мо­му FFmpeg, передается обратно в нашу переменную. При необ­хо­ди­мо­сти оп­ций мож­но до­ба­вить, и вот лишь неболь­шой при­мер то­го, что мож­но сде­лать:

 [$#-eq 1 ] && src=$1 \
 || src=$(zenity --title “Вы­бе­ри­те файл” --file-selection)
 echo $src
 outsize=$(zenity --list --text “За­дай­те вы­ход­ное раз­ре­ше­ние” \
 --height 260 --radiolist \
 --column “Pick one “ --columnsize--column “format” \
 FALSE 640x480 VGA \
 FALSE 800x600 SVGA \
 TRUE 1024x768 XGA \
 FALSE 1280x720 HD720 \
 )
 bit=$(zenity --scale --text “Max бит­рейт?” --min-value=50 --max-value=64000 --value=200 –step 1)
 dest=$(zenity --title “Ука­жи­те, где со­хра­нить” --save –file-selection)
 ffmpeg -i $src -b $bit -s $outsize $dest 2>&1 | zenity --progress --pulsate

Оп­ция scale соз­да­ет «пол­зу­нок» с по­нят­ны­ми па­ра­мет­ра­ми, за­даю­щи­ми пре­де­лы зна­чений, и мы мо­жем восполь­зо­вать­ся дру­гим окош­ком вы­бо­ра фай­лов, на этот раз с па­ра­мет­ром –save, ука­зав, что нам нуж­но но­вое имя фай­ла.

На­конец, когда в кон­це скрип­та мы за­пуска­ем са­му ко­ман­ду ffmpeg, мы мо­жем по­ме­нять мес­та­ми ка­на­лы и на­пра­вить ре­зуль­тат в ин­ди­ка­тор про­грес­са.

Идем даль­ше

Мы рас­ши­рим этот про­ект, улуч­шив ра­бо­ту ин­ди­ка­то­ра про­грес­са так, что­бы он от­ра­жал те­ку­щее состояние. Ути­ли­та FFmpeg не воз­вра­ща­ет те­ку­щий про­гресс в про­цен­тах, но мо­жет со­об­щить вам, который кадр пе­ре­ко­ди­ру­ет в дан­ный мо­мент. Ес­ли оп­ре­де­лить чис­ло кад­ров в ис­ход­ном фай­ле, мож­но оп­ре­де­лить про­гресс, до­быв те­ку­щий кадр (по­лез­ная под­сказ­ка: при­мените sed) и вы­полнив неслож­ный рас­чет.

Итак, хо­тя мы не мо­жем ут­вер­ждать, что по­роднить команд­ную стро­ку и ра­бо­чий стол про­сто, это оп­ре­де­лен­но по­лез­но и ве­се­ло. Ес­ли у вас есть ин­те­рес­ные при­ме­ры, свя­зан­ные с обо­лоч­кой или гра­фи­че­­ским ин­тер­фей­сом, рас­ска­жи­те нам о них или при­шли­те свои пред­ло­же­ния на фо­ру­мы Linux Format по ад­ре­су http://www.linuxformat.ru.

Персональные инструменты
купить
подписаться
Яндекс.Метрика