Пользователь
0,0
рейтинг
25 февраля 2009 в 17:08

Администрирование → Основы BASH. Часть 2 tutorial

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


Циклы. Цикл 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.

Статьи на unix-admin.su
Михаил @ite
карма
52,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Администрирование

Комментарии (51)

  • +5
    В последнем скрипте допущена типичная ошибка — он неправильно обрабатывает директории с пробелом в имени.
    • +10
      На мой взгляд типичная ошибка это создавать директории с пробелами в имени.
      • +4
        И чего в этом плохого? Я всегда даю названия директориям в виде human readable. Уж programs to do some stuff сморится гораздо лучше prg_staff. Не в досе же живём, чессово.
        • +1
          Вот как раз programs to do some stuff смотрится фиговей чем programs_to_do_some_stuff. Причём речь даже не в скриптах: в обычной переписке выделить двойным кликом programs_to_do_some_stuff гораздо легче чем выделять programs to do some stuff и пытаться понять почему каталог programs to do не найден…
          • +2
            Может быть для этого нужны какие-то настройки, но у меня при этом выделяется тольок часть имени между подчёркиваниями ;)
      • +1
        это вы тем, кто выкладывает торренты расскажите :)
    • 0
      после отказа от виндовс(а в последствии и работы с ним по необходимости), заменил все пробелы на "_", это гораздо удобней при работе в консоли(на работе работаю 90% в ней, а отрывок скрипта, кстати имепнно с рабочего компа), tab`ом удобней пользоваться.
      • +1
        Какой-то у вас не правильный tab. Нормально он дополняет файлы с пробелами в имени.

        Человек, утверждающий, что пробелы в имени не нужны, глубоко ошибается на мой взгляд.

        Да, и еще, sudo не будет читать пароль из файла а попросит его с терминала. Попробуйте проверять утверждения, прежде чем публиковать статью — отсутствие документации порой лучше, чем ложная документация.
      • +2
        УЖОС:

        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


        $ echo "/1/2/3/4/5/6" | awk '{FS="/"} {print $4}'
        почему-то ничего не выводит…

        Если это linux, может быть лучше:
        find /svn/ -mindepth 4 -maxdepth 4 -type d | while read ONE_OF_LIST; do
        ...
        done
  • –1
    Вместо echo, лучше всегда использовать printf
  • 0
    к первому примеру можно рассказать про seq
    • 0
      Да. тоже об этом подумал. ЛУчше переделать первый пример так:

      for i in `seq 0 4`

      Ибо, если тебе надо, например, не 5 значений перебрать, а тысячи — не будешь же руками перечислять…
  • +2
    недавно пришлось писать скрипты на bash, и возникла ошибка с переносом строки. Хочу предупредить тех, кто будет создавать скрипты на bash из под FAR или из PSPad: перенос строки в dos и unix две РАЗНЫЕ вещи!

    в PSPpad это лечится просто, Format->Unix (LF). В FAR`e не знаю.
    Коммент конечно мой туп, но для новичком может стать крайне полезной информацией, особенно когда создаются файлы с символом переноса строки в конце имени ^M
    • +10
      писать под баш в фаре — это сильно))
    • +3
      в FAR'e Shift + F2
    • 0
      я обычно это лечу так:
      в виме ":%s,\r,,g"
      или можно прямо sed'ом
  • +2
    там где упоминается for i in 0 1 2 3 4, стоит добавить коментарий, что удобнее делать так:
    for i in {0..4}
    • 0
      Для чистого sh, в freebsd можно использовать комманду jot(for item in `jot 50 1 50`), в линухе, к сожалению не помню.
  • 0
    В bash(как и многих других оболочках) есть встроенные файловые дескрипторы: 0 (stdin), 1 (stdin), 2 (stderr).


    Опечатка — stdin 2 раза, а stdout отсутствует…
    • –1
      Опередили:)
    • 0
      Спасибо, исправил
  • +2
    Спасибо, особенно за «&», не знал и долго искал:)
    Буду ждать продолжения.
  • 0
    И еще для более сложной и продвинутой математики можно использовать перенаправление на bc
  • 0
    спасибо,
    надеюсь будете продолжать цикл статей про баш
    просьба — не забывайте в статье вставлять ссылку на предыдущие части, так как некоторые моменты неплохо бы освежить, и нужно лезть искать…
    • 0
      хм… а я вроде бы вставлял.., ну да ладно :). Исправил.
    • 0
      Небольшая просьба тем, кто будет копировать статьи себе на сайт: Господа, ставьте ХОТЯ БЫ ссылку источник откуда берете. Уважайте себя и труд других.
    • 0
      простите, предыдущий коментарий был адресован всему топику, а не вам конкретно)
  • 0
    Расставьте отступы плз. И раскрасьте код с помощью s-c.me/
    А топик хорош.
    • 0
      Не думаю что такие маленькие кусочки кода сложно разобрать без подсветки. Однако, я приму ваше замечание.
      • 0
        Нет-нет, ведь речи о том, что «сложно разобрать» не было же. Подсветка была бы приятным дополнением.
  • +2
    let "c = a / b" #умножение
    Больше похоже на деление.

    По умолчанию stderr не должен выводится в на консоль(писаться в stderr)
    Мне кажется, что это неверно, да ещё и с опечаткой. stderr вроде как в консоль и пишется по умолчанию, но отдельно от stdout.

    LIST=`find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | tr '\n' ' '`
    for ONE_OF_LIST in $LIST
    do
    ...
    done

    Это, я думаю, более правильно делать как-то так:
    find /svn/ -type d 2>/dev/null| awk '{FS="/"} {print $4}'| sort|uniq | while read ONE_OF_LIST
    do
    ...
    done


    Вообще, статья оформлена несколько неряшливо. Спешили куда-то что ли?
    • 0
      Исправил ваши замечания. По поводу оформления, статья писалась обрывками 2,5 месяца, поэтому возможна некоторая разница в изложении материала + некоторые орфографические ошибки далеко не всеглда заметны(и чем больше перечитываешь статью, тем меньше они заметны). А по поводу примера, ну я это привел лишь как пример :), а так каждый сам решит как улчше это реализовать.
      • 0
        блин..., обещаю заняться постановкой синхронизации рук для печатания на клавиатуре :)
  • +1
    Еще немного иллюстраций

    while :; do… done

    myprogram <<< something_into_stdin
    (см. раздел Here Strings, бывает удобно)

    Это не запускайте!
    :(){ :|:& };:
  • 0
    Вопрос к гуру. Как православней записывать в переменную вывод какой-либо команды?

    a = `echo «qwerty»`
    или
    a = $(echo «qwerty»)
    ?

    Или по барабану?
    • 0
      Второй вариант.
    • 0
      Если баш — то второй вариант, хотя бы из-за того, что вложенность поддерживает.
  • 0
    Мои комментарии:
    1) В примере с for — лучше вариант:
    for i in {0..4}
    или
    for i in $(seq 0 4)
    2) Часто использую бесконечный цикл:
    while:; do чего-нибудь; done
    (упс… узрел в комментах)
    3) Арифметические действия:
    echo $[2+2]
    Полезность — вывести случайное число в диапазоне 1-100
    echo $[ $RANDOM % 100 +1 ]
    При необходимости использования дробей — команда bc:
    (упс… узрел в комментах)
    # echo 3/2 | bc -l
    1.50000000000000000000
    # echo $[3/2]
    1
    4) stdout и stderr в один файл:
    ./program_with_error >& file_with_out-and-err
    5) Командная подстановка — кавычки ``:
    удобнее пользоватся $() — так как удобнее можно делать вложения(без экранирования внутрених кавычек), например:
    rm $(cat /tmp/list-$(whoami))
    — удалит файлы по списку с имением /tmp/list-ИмяВыполневшегоКоманду
    6) Про пробелы в именах файлов спорить не надо — скрипт нужно подстраивать под «сложные условия»:
    лишний раз не ленится аргументы с использованием переменных в каваычки брать,
    где можно у find, например, использовать опцию -print0, а у xargs — --null
  • 0
    А можно ли передать stderr на конвейер (pipe)?
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        Смешается со stdout. Его можно закрыть:
        foo 2>&1 >&- | bar
  • 0
    Помню, несколько лет (!) не мог писать полноценные баш скрипты, потому что про программу let не знал, а знал про expr.

    А у этого expr есть особенность, о которой нигде толком не написано. А особенность в том, что на результат вычисления влияют пробелы.

    #expr 1+3
    1+3

    #expr 1 + 3
    4

    Я писал выражения по первому варианту, и все никак не мог понять — как же делать математические вычисления? В доке написано, что вычисления возможны. На практике — возвращается исходная строка. В голове не укладывалось, что на результат могут влиять непечатные символы.

    И вот только один раз скопипастив откуда-то скрипт, обнаружил, что в нем почему-то expr работает. Стал разбираться почему — чуть не свихнулся, думал уже, что какие-то переменные среды на результат влияют. Через пол-часа стало ясно почему. С новыми знаниями пересмотрел man expr — действительно, про правила форматирования математических выражений пробелами ни слова.
    • 0
      А всё потому, что не разучили вовремя, как шел передаёт аргументы :)
      В отличии от ДОСа, например, где «cd\» сработает. Благодаря этому бремя парсинга выражения на expr не падает, делая его тривиальной программой — все аргументы раздельно переданы в ARGV().
      • 0
        А как вы узнали, что expr для нормальной работы нужно каждый элемент формулы как отдельный аргумент передавать? Вы выучили язык C, скачали исходники и разобрались, как там парсинг работает?

        Я вот например думал, что такая прога, по нормальному-то, все аргументы должна в одну строку склеивать, а потом ее парсить. Конечно, если прогу вменяемый чел писал.
        • 0
          Да, я выучил язык C много лет назад, но это к делу не относится.
          А к делу относится всего лишь man команды expr:
          
          ARG1 | ARG2
                        ARG1 if it is neither null nor 0, otherwise ARG2
          ARG1 < ARG2
                        ARG1 is less than ARG2
          ARG1 <= ARG2
                        ARG1 is less than or equal to ARG2
          STRING : REGEXP
                        anchored pattern match of REGEXP in STRING
          
          

          Обратите внимание на пробелы вокруг операторов. Если бы их не было, неясно как бы он парсил, например, строчные аргументы, как в последнем случе с оператором «двоеточие».
          • 0
            > Обратите внимание на пробелы вокруг операторов.

            Это я обратил внимание. Однако, в любом нормальном разгребателе выражений непечатные символы между токенами пропускаются, не такли? Это аксиома настолько стандартна, что отступая от нее, в документации об этом надо явно написать как минимум.

            > Если бы их не было, неясно как бы он парсил, например, строчные аргументы, как в последнем случе с оператором «двоеточие».

            А как бы он парсил строку с пробелами и двоеточиями вообще страшно подумать.
  • 0
    3й части нету? (

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.