Немного про Bash и смежные науки. Часть 1

    Если вы активно используете Linux для администраторских задач, то наверняка заглядываете время от времени в консоль (или живёте в ней). Несмотря на активное вытеснение текстового интерфейса графическим, а тыкать галочки и нажимать на кнопочки всё-таки интуитивнее, что породило целое поколение эникейщиков, консоль была, есть и будет эффективным средством общения с компьютером. Данная статья рассчитана на тех, кто уже как бы знаком с Bash (Bourne-again Shell), самой популярной реализацией командной оболочки. Этот терминал уже много лет используется по умолчанию чуть ли не в каждом дистрибутиве Linux, так что новички даже не догадываются, что бывают и другие оболочки. Bash пронизан мудростью наших UNIX-предков и всячески рекомендуется для освоения. Сейчас вы увидите, что консоль бывает полезна не только для команд вида «sudo /etc/rc.d/network restart» :)

    Псевдонимы


    Трёхэтажные команды выглядят очень круто, но набирать их каждый раз утомительно. Так вот Bash позволяет создавать краткие произвольные псевдонимы любому набору команд. К примеру, можно создать команду-псевдоним «la», которая будет запускать «ls -a».

    alias la='ls -a'

    А чтобы при перезагрузке ваши новые настройки не потерялись, запишите псевдонимы в файл ~/.bash_aliases.

    Условные операторы


    Всем, кто знаком хотя бы с основами программирования, знакомо понятие условных операторов типа if then else. Bash хоть и не является языком программирования, но допускает использование таких операторов, что невероятно удобно и позволяет избежать использования языков вроде Perl или Python в простых задачах. Вот например:

    if uname -a | grep "GNU/Linux" > /dev/null; then echo "Вы работаете в Linux"; fi

    Работает это следующим образом: сначала выполняется команда «uname -a», которая получает информацию о вашей системе. Полученная информация просеивается через фильтр «grep», который ищет в ней строку GNU/Linux. Вывод направлен в «dev/null» (чёрная дыра Линукса), потому как нас интересует факт наличия строки, а не её вывод на экран. If, then и fi, как вы уже догадались, и есть условный оператор Bash. «If» проверяет условие, «then» описывает действие, которое соответствует условию, а «fi» просто означает конец логической структуры. Условие может быть любой сложности.

    Давайте рассмотрим более полезный пример и проверим запущен ли где-то в системе Firefox. Для этого используем команду «ps aux», а вывод информации перенаправим на фильтр grep. Grep сам является программой, поэтому есть в списке процессов. Поскольку «firefox» содержится в «grep firefox», то окажется, что Firefox якобы есть в процессах, хотя мы запустили всего лишь поиск этой строки. Чтобы обойти неувязочку, используем grep дважды, для фильтрации самого себя :) Запустим его с ключом -v для инвертирования условия:

    if ps aux | grep firefox | grep -v grep > /dev/null; then echo "Firefox запущен"; fi

    Постоянно набирать такую большую команду неудобно, поэтому сделаем из неё краткий псевдоним:

    alias isff='if ps aux | grep firefox | grep -v grep > /dev/null; then echo "Firefox запущен"; fi'

    Теперь для проверки нужно лишь выполнить команду isff.

    Циклы


    Истинная мощь Bash даже не в условных операторах, а в циклах. Циклы используются для многократного повторения некой операции. Рассмотрим на примере:

    while (true); do sleep 1; date; done

    Команда ежесекундно выводит дату и время на экран, пока не нажмут Ctrl+C. Но это как-то тупо, давайте сделаем что-то более полезное.

    Как, например, распаковать несколько архивов tar.bz2 одной командой? А вот так: командой «ls» получаем список bz2-файлов в каталоге и с помощью цикла обрабатываем каждый файл по отдельности. Разумеется, в автоматическом режиме.

    for i in `ls *.tar.bz2`; do tar xjf "$i"; done

    Обратите внимание на символ обратного апострофа (он расположен на клавиатуре слева от цифры 1). Это оператор, который подставляет результат команды «ls *.tar.bz2» в соответствующее место цикла.

    Недооценённый Grep


    Обычно Grep используют для решения дебильных задач, вроде таких:

    somecommand | grep "fuuu"

    То есть для грубой фильтрации вереницы иформации (звучит прям как light amplification by stimulated emission of radiation). Вообще у команды grep огромное количество параметров, но вот самые основные и их стоит запомнить:

    -c Подсчитывает число вхождений искомой строки.
    -i Поиск, не зависящий от регистра символов.
    -l Выводит на экран имена совпавших файлов (это строчная L).
    -n Отображает номер соответствующей запросу строки.
    -r Выполняет рекурсивный поиск по каталогам.
    -v Инвертирует условие поиска (строки, не содержащие цель поиска).


    Параметры можно объединять, а grep можно направлять на самого себя. Например, если необходимо подсчитать количество файлов, которые содержат слово «paul» (или «PAUL», «PaUl» и т.д.), кроме тех, которые имеют расширение .txt, вам следует сделать что-то вроде такого:

    grep -ilr paul * | grep -cv "\.txt$"

    Здесь мы используем важнейшие свойства grep. Комбинация параметров -ilr означает, что выполняется поиск по файлам слов «PAUL», «PaUl» и т.д., поиск проходит рекурсивно по вашей файловой системе, начиная с текущего каталога и возвращает имена подходящих файлов. Всё это поступает на ещё один экземпляр grep, который выполнятся с параметрами -cv, которые, в свою очередь, включают режим подсчёта и поиска файлов, которые не соответствуют регулярному выражению "\.txt$". Обратный слеш здесь экранирует точку, без него мы получили бы специальный символ. А знак доллара означает, что .txt должно находится в конце имени файла. То есть «fuuu.txt.boo» не будет соответствовать выражению.

    Не очевидный Find


    Команда find не подчиняется законам здравого смысла. То есть не думайте, что можете использовать её в виде «find <иголка> <стог_сена>». В отличие от этого, find работает как фильтр.

    find .

    Точка в конце означает, что будет возвращён список всех файлов в текущем каталоге. Но команда «ls -a» делает, в принципе, то же самое. Поэтому усложним задачу. Найдём файлы в каталоге, в имени которых есть «arr».

    find . -name "*arr*"

    А ещё можно добавить параметр "-size" и найти только те файлы, которые больше 1-го мегабайта.

    find . -name "*bar*" -size +1M

    Параметр "-user" возвращает файлы, принадлежащие указанному пользователю. Например:

    find . -name "*bar*" -user veles

    Это также полезно использовать с параметром "-not", который, как вы уже догадались, инвертирует поиск:

    # все файлы, кроме myfile.txt и тех, которыми владеет veles
    find . -not -name "myfile.txt" -not -user veles


    Ещё один прикольный фильтр — "-newer". Он возвращает все файлы, которые новее, чем указанный файл. Это очень удобно для скриптов резервного копирования: когда делаете копию, просто укажите произвольный файл, и он будет служить временной меткой, которую можно использовать в команде «find -newer». Таким образом вы получите список всех файлов, которые изменились с момента создания этого файла.

    find . -newer /path/to/myfile

    Пока достаточно. Разумеется, тонны деталей упущены и остаются вам на самостоятельное изучение. «Маны» ещё никто не отменял, а цель данной статьи — привлечь ваше внимание к великому и могучему UNIX-way.

    В следующий раз мы познакомимся с управлением многозадачностью (вы же не забыли, что консоль Линукса многозадачна?), а также подружимся с планировщиком cron, который может стать вашим любимым средством автоматизации рутинной работы.

    P.S. Юниксоиды со стажем, скорее всего, давно всё это знают. Но эта статья написана больше для хорошего аппетита интересующихся.
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 38

      +5
      RTFM и ваши волосы будут мягкие и шелковистые… :)
      • UFO just landed and posted this here
          +1
          O_o
          man bash, man find, man grep/egrep/fgrep/rgrep
          • UFO just landed and posted this here
            • UFO just landed and posted this here
                0
                Вообще-то вопрос интересный. Это я в защиту автора, просто чтоб пояснить. Вопрос в том, что считать расширением… после последней точки, или после первой. Почему вопрос, поясню — есть же расширения типа *.tar.gz.
                то есть, если мы считаем расширение после последней точки, тогда:
                file=1.tar.gz
                filename=${file%.*}
                ext=${file##*.}

                если же наоборот, мы считаем расширение после первой точки, то:
                file=1.tar.gz
                filename=${file%%.*}
                ext=${file#*.}

                Вот так всё просто.
                • UFO just landed and posted this here
                  0
                  ну «for i in» буквально означает перебирать то в чем ищите, а искать надо в списке, "*.txt" это не список файлов это просто строка. я обычно делаю как то так

                  #!/bin/sh
                  ls -la | while read tfile
                  do
                  echo "$tfile"
                  done

                  самый простой пример цикла по списку файлов, можете дальше его усложнять как будет угодно…

                  вот пример цикла с for

                  for i in `seq 1 100`; do
                  echo "$i"
                  done
                  • UFO just landed and posted this here
                    +1
                    Есть такое дело, называется регулярные выражения (соб-но отличие grep от egrep, к примеру), так вот, если их освоить, все становиться просто волшебно… Но они сложны.
                    По Вашему вопросу:
                    [code]
                    for foo in `find some_file_with_some_expressions`; do grep/xargs/some_thing_else $foo; done;
                    [/code]

                    прекрасно выполняет что хотите с каким угодно кол-вом файлов, отобранных по определенным признакам.
                  0
                  bash scripting >> google сразу же на учебник попадаем.

                  p.s.
                  я жу говорил, что тут дружно маны читают…

                  >Кстати, раз уж вы позиционируете себя как гуру баша, может расскажите как найти полное описание, как делать цикл for по файлам? Вроде, for i in *.txt; do smth i; done. Полное описание синтаксиса, как использовать результаты (например только имя файла, без расширения) и т.д. Заодно расскажете, как нашли. Вот и проверим действенность совета.

                  for file in *.txt
                  name="${file%%.*}"
                  ext="${file##*.}"
                  echo $name
                  echo $ext
                  done

                  жаже вот вам больше:
                  if ping -c 2 -q ya.ru;then if [ -e /tmp/down];then echo «inet podnyalsa»;rm /tmp/down; fi; else echo «inet upal»;touch /tmp/down; fi
                  • UFO just landed and posted this here
                      0
                      also, вбейте в гугл bash for и попадаете на мою любимую хавту по циклам for в баше.
                      • UFO just landed and posted this here
                          0
                          grab.by/3rhH -> www.cyberciti.biz/faq/bash-for-loop/ сам ей надавно пользовался так как надо было после долго неписание скриптов написать добавление метаданных к >200 серий аниме :)
                          • UFO just landed and posted this here
                              0
                              а это по вашему что?
                              Following shell script will go though all files stored in /etc directory. The for loop will be abandon when /etc/resolv.conf file found.

                              #!/bin/bash
                              for file in /etc/*
                              do
                              if [ "${file}" == "/etc/resolv.conf" ]
                              then
                              countNameservers=$(grep -c nameserver /etc/resolv.conf)
                              echo «Total ${countNameservers} nameservers defined in ${file}»
                              break
                              fi
                              done

                              #!/bin/bash
                              FILES="$@"
                              for f in $FILES
                              do
                              # if .bak backup file exists, read next file
                              if [ -f ${f}.bak ]
                              then
                              echo «Skiping $f file...»
                              continue # read next file and skip cp command
                              fi
                              # we are hear means no backup file exists, just use cp command to copy file
                              /bin/cp $f $f.bak
                              done
                              • UFO just landed and posted this here
                                  0
                                  тут все построчно для удобства чтения, переписать в одну строчку не сложно. Пишите статья про баш и не знаете, что $@ массив аргументов? похвально…
                                  • UFO just landed and posted this here
                                      0
                                      извиняюсь, но все равно похвально. такие вещи как $0 $1 $$ $! $? $@ надо бы знать.
                                      • UFO just landed and posted this here
                                          0
                                          в прошлой жизне я был гуру баш скриптинга. помню написал на нем свой интерактивный шелл для простенького администрирования.
                                          p.s.
                                          на opennet'e есть толстый учебник на русском по башу.
              • UFO just landed and posted this here
                  +3
                  жаль, по заголовку ожидал каких-то трюков, вкусностей, а тут все до ужаса банально. Переименуйте что ли в «обычный такой bash».
                    +2
                    Смущает что начали с bash, и не закончив про него, сразу прыгнули на отдельные комманды, grep и find.
                    Начинающим будет полезно. Но всё-таки попробуйте быть последовательным, не перескакивая.
                    За статью спасибо!
                      0
                      Всё настолько плотно друг с другом связывается, что я без лишнего угрызения совести поставил утилиты GNU под флаг Bash, что концептуально неправильно, но доступно для понимая новичками. И верно, я подчёркиваю, что статья для начинающих. Для них (а я сам таким был) это нетривиально. Я видел как ограниченно многие Линуксоиды используют консоль — ssh и для ребута сервисов, разве что. А на Хабре есть люди разного уровня подготовки. Кому-то да пригодится.

                      Спасибо за замечание, постараюсь быть более последовательным. Хочется о многом рассказать, а раздувать статью наоборот, не хочется.
                        0
                        Не увидел нетривиального bash-а, хотя статья вроде называется «Нетривиальный BASH.» но про баш почему то ни слова…
                          0
                          Я чуть выше уже объяснился почему «нетривиальный». Для вас «банальный», и это хорошо. Всё ведь зависит от уровня подготовленности читателя, согласны?

                          Примечание для себя: впредь быть осторожнее с заголовками, они вводят в заблуждение.
                            0
                            Так может быть, стоит поправить заголовок? Сам в баше не очень разбираюсь, но после прочтения всё равно возникает вопрос: а что же тогда тривиально, если не это? echo «Hello, World!» что ли?
                          0
                          Количество озадаченных комментаторов ясно указало мне на желтуху заголовка. Сменил на другой. Надеюсь так лучше и никого не смущает.
                            0
                            alias isff='if ps aux | grep firefox | grep -v grep > /dev/null; then echo «Firefox запущен»; fi'

                            тут не хватает грепа имени пользователя. Обычно это нужно (т.к. мне может быть пофег, что фаерфокс запущен у какого-то др пользователя):
                            grep `id -un`
                              0
                              if ps aux | grep firefox | grep -v grep
                              Откройте для себя pgrep

                              И вот не в обиду, но вы немножко не за тот шелл взялись. Честно.
                              Я вам как бы намекну, что на данный момент действительно пронизано и рекомендуется:
                              Сравните и скажите, что проще:
                              bash:
                              1. find. '*.c' -print | xargs grep 'hello' /dev/null
                              2. for i in `ls *.tar.bz2`; do tar xjf "$i"; done

                              zsh:
                              1. grep hello $(ls **/*.c)
                              2. for i in *.tar.bz2;tar xjf "$i"
                                0
                                Холиварно :) Незаменимых вещей нет и Bash легко меняется на тот же zsh. Всё равно не может существовать единственно универсального решения, но я хотел бы, что бы в Линуксе, с которым я плотно работаю, хоть что-то было постоянным от дистрибутива к дистрибутиву. Пусть это будет GNU и пусть это будет Bash.
                                  0
                                  >
                                  >Сравните и скажите, что проще:
                                  >bash:
                                  >1. find. '*.c' -print | xargs grep 'hello' /dev/null
                                  >2. for i in `ls *.tar.bz2`; do tar xjf "$i"; done
                                  >
                                  >zsh:
                                  >1. grep hello $(ls **/*.c)
                                  >2. for i in *.tar.bz2;tar xjf "$i"

                                  ненене дэвид блэйн, одно дело пользоваться zsh для своего удобства, второе писать скрипты для общих случаев, в скриптах не место не bash-измам не zsh-измам, мое сугубое имхо.
                                    0
                                    Вы серьёзно думаете что
                                    if ps aux | grep firefox | grep -v grep
                                    используется в некоей гетерогенной среде где требуется универсальность и совместимость?
                                    У вас на серверах тоже крутится firefox?
                                      0
                                      я не понимаю вашего вопроса, я не использую firefox на серверах и не помню чтобы такое говорил. Второе я считаю что если пишешь что то что дальше будет кем то использоваться то надо это делать так чтобы было меньше привязок к чему то узкоспециальному.

                                Only users with full accounts can post comments. Log in, please.