Разработки проекта PuppyRus > Программирование и компиляция

Основы BASH

(1/1)

Zloy_T:
[Работа с потоками данных в bash]

Статья посвящена работой с потоками данных в bash. Я постарался написать ее наиболее доступным и простым языком, чтобы было понятно даже новичкам в Linux.


В одной из моих статей мы рассматривали запись звука в файл с помощью команды:

cat /dev/audio > /tmp/my.sound

Эта команда читает файл (устройство) /dev/audio с помощью команды cat и перенаправляет информацию из него в файл /tmp/my.sound (с помощью оператора >).

У каждой программы существует 3 системных потока: stdout, stderr, stdin.

stdout

Стандартный поток вывода данных для программ. Например, когда мы пишем команду ls, то список папок и файлов она выводит именно в этот поток, который отображается у нас в консоли:


--- Код ---
$ ls
bin incoming pub usr
--- Конец кода ---

stderr

Поток вывода ошибок. Если программа не смогла сделать все как надо — она пишет именно в этот поток. Например, когда rm пытается удалить несуществующий файл:


--- Код ---
$ rm example.txt
rm: example.txt: No such file or directory
--- Конец кода ---

stdin

Поток ввода данных. А вот это довольно интересный и удобный поток. Например, его использует вэб-сервер, когда просит интерпретаторы выполнить скрипты через CGI. Мы тоже можем попробовать:


--- Код ---
$ echo '<?php echo «Hello world»; ?>' | php
Hello world
--- Конец кода ---

В этом примере мы встретили оператор перенаправления потока вывода. Мы остановимся на нем позже.

Подробнее:
http://ru.wikipedia.org/wiki/Стандартные_потоки


Перенаправление потоков

Для начала рассмотрим перенаправление потоков в файлы, устройства и другие потоки.


--- Код ---
$ ls >1.txt
--- Конец кода ---

В этом примере мы направили stdout команды ls в файл 1.txt. Читаем его:


--- Код ---
$ cat 1.txt
bin incoming pub usr
--- Конец кода ---

Да, все успешно записалось.

Теперь попробуем направить stderr команды rm:

--- Код ---
$ rm example.txt 2>1.txt
--- Конец кода ---

Здесь мы использовали номер потока stderr (2). По умолчанию оператор > перенаправляет поток stdout, который имеет номер 1. Чтобы направить другой поток, надо перед оператором > поставить его номер.

Мы можем направлять одни потоки в направлении других:


--- Код ---
$ rm exmple.txt >1.txt 2>&1
--- Конец кода ---

В этом примере мы направили поток stdout в файл 1.txt, а затем направили stderr туда же, куда направлен stdout с помощью оператора & перед номером потока.

Теперь давайте поиграем с потоком stdin. Например, я хочу найти все папки ".svn" в некотором проекте и удалить:


--- Код ---
cd myproject
find .
--- Конец кода ---

Команда find с параметром. выводит в stdout все вложенные папки и файлы, которые находит в данной папке и во всех вложенных.

Теперь нам надо выбрать только папки с именем ".svn":


--- Код ---
find . | grep -e '/.svn$'
--- Конец кода ---

Оператор | перенаправляет stdout одного приложения в stdin следующего. То есть все строки найденные с помощью find пошли в команду grep, которая выбирает строки по определенным условиям и выводит их. Здесь условие — это регулярное выражение, которое говорит о том, что строка должна заканчиваться на "/.svn".

Нужные папки мы выбрали, осталось их удалить.


--- Код ---
rm -Rf `find . | grep -e '/.svn$'`
--- Конец кода ---

И снова новый оператор: `. Он забирает stdout из команды, которую он окружает и вставляет в данное место как строку.

Получается, что мы запросили все файлы, выбрали из них папки с именем ".svn" и отдали результат как аргументы команде rm. В этом случае у нас будут проблемы если имена файлов и папок содержат пробелы. Исправляем ситуацию:


--- Код ---
find . | grep -e '/.svn$' | xargs rm -Rf
--- Конец кода ---

Теперь мы отдаем нужные файлы команде xargs, которая вызывает rm -Rf и в качестве параметров использует свой stdin построчно. Задача решена.

цэ http://habrahabr.ru/blogs/linux/55136/

Zloy_T:
Извиняюсь за такую большую задержку между статьями, но сессия дает о себе знать в самый неподходящий момент :)
Всем спасибо за замечания, критику и дополнения, которые были озвучены в комментариях к прошлой статье.
Эта часть, как и обещал, будет посвящена циклам, математическим операциям и использованию внешних команд.
Начнем.


Циклы. Цикл for-in.

Оператор for-in предназначен для поочередного обращения к значениям перечисленным в списке. Каждое значение поочередно в списке присваивается переменной.
Синтаксис следующий:
for переменная in список_значений
do
команды
done

Рассмотрим небольшой пример:

--- Код ---
#!/bin/bash
for i in 0 1 2 3 4 #переменной $i будем поочередно присваивать значения от 0 до 4 включительно
do
echo "Console number is $i" >> /dev/pts/$i #Пишем в файл /dev/pts/$i(файл виртуального терминала) строку "Console number is $i"
done #цикл окончен
exit 0

--- Конец кода ---
После выполнения примера в первых 5 виртуальных консолях(терминалах) появится строка с её номером. В переменную $i поочередно подставляются значения из списка и в цикле идет работа со значением этой переменной

Циклы. Цикл while.

Цикл while сложнее цикла for-in и используется для повторения команд, пока какое-то выражение истинно( код возврата = 0).
Синтаксис оператора следующий:
while выражение или команда возвращающая код возврата
do
команды
done

Пример работы цикла рассмотрим на следующем примере:

--- Код ---
#!/bin/bash
again=yes #присваиваем значение "yes" переменной again
while [ "$again" = "yes" ] #Будем выполнять цикл, пока $again будет равно "yes"
do
echo "Please enter a name:"
read name
echo "The name you entered is $name"

echo "Do you wish to continue?"
read again
done
echo "Bye-Bye"
--- Конец кода ---

А теперь результат работы скрипта:

--- Код ---
ite@ite-desktop:~$ ./bash2_primer1.sh
Please enter a name:
ite
The name you entered is ite
Do you wish to continue?
yes
Please enter a name:
mihail
The name you entered is mihail
Do you wish to continue?
no
Bye-Bye
--- Конец кода ---


Как видим цикл выполняется до тех пор, пока мы не введем что-то отличное от «yes». Между do и done можно описывать любые структуры, операторы и т.п., все они будут выполнятся в цикле.Но следует быть осторожным с этим циклом, если вы запустите на выполнение в нём какую-либо команду, без изменения переменной выражения, вы можете попасть в бесконечный цикл.
Теперь об условии истинности. После while, как и в условном операторе if-then-else можно вставлять любое выражение или команду, которая возвращает код возврата, и цикл будет исполнятся до тех пор, пока код возврата = 0! Оператор "[" аналог команды test, которая проверяет истинность условия, которое ей передали.

Рассмотрим еще один пример, я взял его из книги Advanced Bash scripting. Уж очень он мне понравился :), но я его немного упростил. В этом примере мы познакомимся с еще одним типом циклов UNTIL-DO. Эта практически полный аналог цикла WHILE-DO, только выполняется пока какое-то выражение ложно.
Вот пример:

--- Код ---
#!/bin/bash
echo "Введите числитель: "
read dividend
echo "Введите знаменатель: "
read divisor

dnd=$dividend #мы будем изменять переменные dividend и divisor,
#сохраним их знания в других переменных, т.к. они нам
#понадобятся
dvs=$divisor
remainder=1

until [ "$remainder" -eq 0 ]
do
let "remainder = dividend % divisor"
dividend=$divisor
divisor=$remainder
done

echo "НОД чисел $dnd и $dvs = $dividend"
--- Конец кода ---


Результат выполнения скрипта:

--- Код ---
ite@ite-desktop:~$ ./bash2_primer3.sh
Введите числитель:
100
Введите знаменатель:
90
НОД чисел 100 и 90 = 10
--- Конец кода ---


Математические операции

Команда let.
Команда let производит арифметические операции над числами и переменными.
Рассмотрим небольшой пример, в котором мы производим некоторые вычисления над введенными числами:

--- Код ---
#!/bin/bash
echo "Введите a: "
read a
echo "Введите b: "
read b

let "c = a + b" #сложение
echo "a+b= $c"
let "c = a / b" #деление
echo "a/b= $c"
let "c <<= 2" #сдвигает c на 2 разряда влево
echo "c после сдвига на 2 разряда: $c"
let "c = a % b" # находит остаток от деления a на b
echo "$a / $b. остаток: $c "
--- Конец кода ---

Результат выполнения:

--- Код ---
ite@ite-desktop:~$ ./bash2_primer2.sh
Введите a:
123
Введите b:
12
a+b= 135
a/b= 10
c после сдвига на 2 разряда: 40
123 / 12. остаток: 3
--- Конец кода ---

Ну вот, как видите ничего сложного, список математических операций стандартный:
+ — сложение
— — вычитание
* — умножение
/ — деление
** — возведение в степень
% — модуль(деление по модулю), остаток от деления
let позволяет использовать сокращения арифметических команд, тем самым сокращая кол-во используемых переменных. Например: a = a+b эквивалентно a +=b и т.д
Работа с внешними программами при написании shell-скриптов

Для начала немного полезной теории.
Перенаправление потоков.

В bash(как и многих других оболочках) есть встроенные файловые дескрипторы: 0 (stdin), 1 (stdout), 2 (stderr).
stdout — Стандартный вывод. Сюда попадает все что выводят программы
stdin — Стандартный ввод. Это все что набирает юзер в консоли
stderr — Стандартный вывод ошибок.
Для операций с этими дескрипторами, существуют специальные символы: > (перенаправление вывода), < (перенаправление ввода). Оперировать ими не сложно. Например:

--- Код ---
cat /dev/random > /dev/null
--- Конец кода ---
перенаправить вывод команды cat /dev/random в /dev/null (абсолютно бесполезная операция :)) ) или

--- Код ---
ls -la > listing
--- Конец кода ---
записать в файл listing содержание текущего каталога (уже полезней)
Если есть необходимость дописывать в файл(при использовании ">" он заменятеся), необходимо вместо ">" использовать ">>"

--- Код ---
sudo < my_password
--- Конец кода ---
после просьбы sudo ввести пароль, он возьмется из файла my_password, как будто вы его ввели с клавиатуры.
Если необходимо записать в файл только ошибки, которые могли возникнуть при работе программы, то можно использовать:

--- Код ---
./program_with_error 2> error_file
--- Конец кода ---
цифра 2 перед ">" означает что нужно перенаправлять все что попадет в дескриптор 2(stderr).
Если необходимо заставить stderr писать в stdout, то это можно можно след. образом:

--- Код ---
./program_with_error 2>&1
--- Конец кода ---
символ "&" означает указатель на дескриптор 1(stdout)
(Поумолчанию stderr пишет на ту консоль, в котрой работает пользователь(вренее пишет на дисплей)).

2.Конвееры.

Конвеер — очень мощный инструмент для работы с консолью Bash. Синтаксис простой:
команда1 | команда 2 — означает, что вывод команды 1 передастся на ввод команде 2
Конвееры можно группировать в цепочки и выводить с помощью перенаправления в файл, например:

--- Код ---
ls -la | grep «hash» |sort > sortilg_list
--- Конец кода ---
вывод команды ls -la передается команде grep, которая отбирает все строки, в которых встретится слово hash, и передает команде сортировке sort, которая пишет результат в файл sorting_list. Все довольно понятно и просто.

Чаще всего скрипты на Bash используются в качестве автоматизации каких-то рутинных операций в консоли, отсюда иногда возникает необходимость в обработке stdout одной команды и передача на stdin другой команде, при этом результат выполнения одной команды должен быть неким образом обработан. В этом разделе я постораюсь объяснить основные принципы работы с внешними командами внутри скрипта. Думаю что примеров я привел достаточно и можно теперь писать только основные моменты.

1. Передача вывода в переменную.

Для того чтобы записать в переменную вывод какой-либо команды, достаточно заключить команду в `` ковычки, например

--- Код ---
a = `echo "qwerty"`
echo $a
--- Конец кода ---

Результат работы: qwerty

Однако если вы захотите записать в переменную список директорий, то необходимо, должным образом обработать результат для помещения данных в переменную. Рассмотрим небольшой, пример:

--- Код ---
LIST=`find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '`
for ONE_OF_LIST in $LIST
do
svnadmin hotcopy /svn/$ONE_OF_LIST /svn/temp4backup/$ONE_OF_LIST
done
--- Конец кода ---

Здесь мы используем цикл for-do-done для архивирование всех директорий в папке /svn/ с помощью команды svnadmin hotcopy(что в нашем случае не имеет никого значения, просто как пример). Наибольшй интерес вызывает строка: LIST=`find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '` В ней переменной LIST присваивается выполнение команды find, обработанной командами awk, sort, uniq,tr(все эти команды мы рассматривать не будем, ибо это отдельная статья). В переменной LIST будут имена всех каталогов в папке /svn/ пгомещенных в одну строку(для того чтобы её стравить циклу.

Как видно, все не сложно, достаточно понять принцип и написать пару своих скриптов. В заключении статьи хочу пожелать удачи в изучении BASH и Linux в целом. Критика, как водится приветствуется. Следующая статья возможно будет посвещена использованию таких программ как sed, awk.

Zloy_T:
Сегодня я хотел бы рассказать о возможных подводных камнях и их обходе при написании скриптов на bash.

while;do: ;done
обычно я использую цикл while в случае когда требуется обработка строк разделенных пробелами (в отличии от for, см. ниже)
но это не единственное различие for и while в bash. приведу простой пример


--- Код ---
echo -e 'test test\ntest2\ntest3 test3'|
while read a ;do
echo ==$a
exit
done
echo "the script finished $a"
--- Конец кода ---


Здесь я хотел бы обратить внимание на то, как распределятся процессы при выполнении кода, ветки основного процесса я помечу курсивом, а ветки потомка выделю жирным


--- Код ---
echo -e 'test test\ntest2\ntest3 test3'|
while read a ;do
echo ==$a
exit
done
echo "the script finished $a"
--- Конец кода ---

таким образом становится понятно, что цикл выйдет напечатав

--- Код ---
test test
--- Конец кода ---
, но переменная $a останется необновленной, к тому же exit в конце выйдет всего-лишь из цикла. поэтому последняя строка вывода будет

--- Код ---
the script finished
--- Конец кода ---
попробуем написать скрипт основаный на while, который напечатает все строки а в конце получит в управление переменную заданную в теле цикла., так-же выведет было ли выполнение цикла вообще.

все гораздо проще чем кажется мы просто перенесем echo в дочерний процесс избавившись от перенаправления через pipe:

--- Код ---
while read a ;do
echo ==$a
flag=1
done < <(echo -e 'test test\ntest2\ntest3 test3')
if [ $flag ] ;then echo "the script finished, loop completed $a" else
echo "the script finished, loop didn't started";
fi
exit 0
--- Конец кода ---

2. for a in $list ;do: ;done
часто for удобнее чем while но только в случае когда $list (обычно вывод комманды) не содержит пробелов, или, если нам на это плевать.

тут есть свои приколы, но чет я устал и не могу найти пример, кроме vzctl exec для этого случая.

Статья понравилась, но автор не я.
Источник http://welinux.ru/post/262/

Навигация

[0] Главная страница сообщений

Перейти к полной версии