Добавить в избранное | Сделать стартовой страницей

Большая Linux библиотека для пользователей OS Linux и ПО для нее
Есть что сказать? Нужен совет? Посети наш форум.


При поддержке
Продвижение сайта
Продвижение сайта
Раскрутка сайта
Создание сайта
Оптимизация сайта
Интернет реклама
Аудит сайта
Администрирование сервера
настройка сервера
установка сервера
аренда сервера
Администрирование сервера
администрирование сервера
настройка сервера
аренда сервера
Rambler's Top100


Работаем с историей команд.

Работаем с историей команд. Часть I. Основы.

Как известно, лень человеческая - помимо слабости, является одним из основных двигателей прогресса. Не чужды ей и простые смертные линуксоиды и юниксоиды вместе взятые. Итак, как помочь человеку попавшему в дебри командного интерпретатора и облегчить его труд и усилить во сто крат силу его? Ответ на сей вопрос прост - надо дать ему в руки, и научить пользоваться таким полезным инструментом, как история команд. Сей инструмент в той или иной мере присутствует во многих программах и системах. В том же многострадальном MS-DOS, было жалкое подобие истории введенных команд, история также присутствовала в NC, FAR и других командных оболочках. Но там они ни в какое сравнение не идут с возможностями присутствующими в любом мало мальски распространенном shell под linux или unix. Далее я буду описывать работу c bash, хотя на сколько мне известно tcsh, csh и некоторые другие интерпретаторы имеют сходный набор команд для работы с историей.

Итак начав работу с командной строкой bash, я обнаружил что с помощью клавиш перемещения курсора можно перемещаться по списку ранее введенных команд. Так когда мне нужна была некоторая команда из ранее введенных я жал клавишу „стрелка вверх“ до тех пор пока нужная мне не появлялась в командной строке, потом я ее корректировал нужным мне образом и жал „Enter“ для ее выполнения. Это конечно значительно экономило время однако, как оказалось, не было наиболее эффективным способом работы.

Итак, все по порядку. Для просмотра списка ранее введенных команд в bash - имеется команда history. По умолчанию она выводит список команд хранящийся в истории. Размер данного списка определяется переменными окружения HISTSIZE - размер списка хранящегося в памяти интерпретатора, а HISTFILESIZE - максимальное количество команд хранящихся в файле истории. По умолчанию этоn файл ~/.bash_history, а его размер - 500 команд. Если вы желаете хранить историю в другом файле, то нужно в .bashrc, задать команду - HISTFILE=~/.vasya_history. Я для себя переопределяю только размер списка команд и размер файла истории, устанавливая их значения в 1000 команд. Итак введя:
        $ history

            1  history | less
            2  hg test
            3  test 333
            4  lynx www.yahoo.com
            5  cat /etc/profile.d/colorls.sh
            6  vim .screen
            7  vim .screenrc
                ..........................
          305  man bash
          306  man vfork
          307  hg lynx
          308  cd txt/everyday/
          309  vim history.txt 
          310  histroy

Отсюда видно, что в истории на данный момент находится 310 команд, конечно они все на экране не поместятся, посему если вам надо только последние 20 команд, то можно набрать:
    $ history 20

          295  vim lib/advhist.sh 
          296  getpg
          297  vim .bash_logout 
          298  vim .bashrc
          299  cat .lynxrc
          300  vim .lynxrc
          301  ls lib
          302  cat .bashrc
          303  getmail
          304  ls
          305  man bash
          306  man vfork
          307  hg lynx
          308  cd txt/everyday/
          309  vim history.txt 
          310  histroy
          311  history
          312  history 
          313  fg
          314  history 20 

Таким образом получим только последние 20 команд. Каждая команда имеет свой номер, с помощью которого к ней можно обратится. Если нам надо повторить 302 команду, то просто печатаем:
     $ !302
        cat .bashrc
        # .bashrc

        # User specific aliases and functions
        if [ "$PS1" -a -f ~/lib/advhist.sh ]; then
                . ~/lib/advhist.sh
        fi
        # Source global definitions
        if [ -f /etc/bashrc ]; then
                . /etc/bashrc
        fi
        umask 066

Здесь сначала печатается команда под номером 302 - cat .bashrc, а затем результат ее выполнения.

Приведу список команд для работы с историей:
  • !! - ссылается на предыдущую команду;
  • !n - ссылается на команду под номером n;
  • !-n - ссылается на команду по номером „текущая минус n“;
  • !string - ссылается на команду, начинающуюся с string;
  • !?string[?] - ссылается на команду, содержащую строку string;
  • ^string1^string2[^] - „быстрая замена“, заменяет первое вхождение строки string1 в предыдущей команде, на string2, после чего выполняет полученную команду.
Приведу несколько примеров по использованию вышеприведенных команд. Я часто выполняю приблизительно следующую последовательность:
       $ locate diald.conf
        /etc/diald.conf
        $ cat /etc/diald.conf
        .......

Первой командой я нахожу требуемый файл, а второй вывожу его содержимое. Сей короткий пример можно автоматизировать следующим образом:
    $ locate diald.conf
        /etc/diald.conf
        $ cat `!!`
        cat `locate diald.conf`
        mode ppp
        accounting-log /tmp/dialdlog
        .....

Таким образом я избегаю повторного ввода имени файла. `!!` означает - выполнить предыдущую команду и подставить ее результат в качестве параметра cat. Результирующая команда после подстановки из перечня печатается сразу за командной строкой: cat `locate diald.conf`.После чего выводится результат выполнения команды. На мой взгляд весьма удобно.

Идем дальше:
    $ history 10
        324  hg \'
        325  hg \`
        326  locate diald.c
        327  locate diald.conf
        328  cat `locate diald.conf`
        329  getmail
        330  history 10
        331  ls
        332  ps
        333  history 10
        $ !328
        cat `locate diald.conf`
        mode ppp
        accounting-log /tmp/dialdlog
        ..........

Данная команда выполняет 328 команду перечня. Другой способ обратится к этой же команде:
  $ !-6
        cat `locate diald.conf`
        mode ppp
        accounting-log /tmp/dialdlog
        ..........

Здесь используется обратная нумерация, то есть номер команды вычисляется как текущая минус 6.

Следующий способ, это когда я помню что команда начиналась со строки cat, чтобы ее повторить я печатаю следующее:
 $ !cat
        cat `locate diald.conf`
        mode ppp
        accounting-log /tmp/dialdlog
        ..........

А если я не помню названия начала команды, но помню ее середину diald, то тогда набираем:
      $ !?diald
        cat `locate diald.conf`
        mode ppp
        accounting-log /tmp/dialdlog
        ..........

Следующий интересный момент касается исправления неверно введенных команд или их корректировки:
   $ cta /etc/diald.conf
        bash: cta: command not found
        $ ^ta^at
        cat /etc/diald.conf
        mode ppp
        accounting-log /tmp/dialdlog
        ...............

В результате опечатки первая команда была введена с ошибкой, вместо cat, было введено cta, естественно интерпретатор не нашел такой команды, о чем и вывел предупреждение. Вторая команда ^ta^at^ - исправляет ошибку, делая замену ta на at. Это намного удобнее чем вызывать предыдущую команду на экран потом подводить курсор под нужный символ, а затем редактировать команду с помощью вставки/удаления символов. Данная возможность особенно удобна при удаленной работе с telnet на медленных линиях.

До настоящего момента при вызове команд из списка, мы ссылались на команду целиком. Но частенько надо повторить не всю команду, а только ее часть.

Пример:
     $ cat /etc/diald.conf
        mode ppp
        accounting-log /tmp/dialdlog
        .........
        $ vim !$

Первой командой cat мы выводим содержимое файла, например для того, чтобы удостоверится что это нужный нам файл. Удостоверившись я хочу его отредактировать, для того, чтобы повторно не набирать имя файла пишу vim !$ - что после подстановки преобразуется в vim /etc/disld.conf. Кратко опишу основные операторы обращения к различным частям предыдущей команды:
  • $ - последний аргумент командной строки;
  • ^ - первый аргумент командной строки;
  • 0 - нулевое слово, другими словами имя команды;
  • N - N-ый аргумент командной строки;
  • x-y - аргументы с x по у командной строки;
  • -y - сокращенное обращение к 0-y;
  • * - все аргументы, синоним 1-$;
  • x* - синоним x-$, другими словами аргументы с номера x до конца строки;
  • x- - синоним x*, но не включает последний аргумент.

Перед всеми описанными операторами, кроме ^ $ * %, необходимо ставить двоеточие.

Для ясности приведу несколько примеров:
        $ echo 1 2 3 4 5 6 7 8
        1 2 3 4 5 6 7 8
        $ echo !$
        echo 8
        8
        $ echo 1 2 3 4 5 6 7 8
        $ echo !*
        echo 1 2 3 4 5 6 7 8
        1 2 3 4 5 6 7 8
        $ echo !:3*
        echo 3 4 5 6 7 8
        3 4 5 6 7 8
        $ echo !^
        echo 3
        3
        $ echo !-2:-3
        echo echo 3 4 5 
        echo 3 4 5

Я наиболее часто использую операторы !^, !$, !* - которые ссылаются на первый аргумент, на последний аргумент и на все аргументы предыдущей команды соответственно.

Еще нужно упомянуть о так называемых модификаторах, подробно о них можно почитать на страницах руководства. Но об одном из них я скажу отдельно. Этот заслуживающий внимания модификатор - :p. Он говорит интерпретатору о том, что полученную в результате подстановки команду не надо выполнять, а только напечатать. Например:
   $ echo 1 2 3 4 5 6 7
        $ !!
        echo 1 2 3 4 5 6 7
        1 2 3 4 5 6 7 
        $ !!:p
        echo 1 2 3 4 5 6 7 
        $ echo !-2:p
        echo echo 1 2 3 4 5 6 7
        $

Как можно заметить - последняя и предпоследняя команды echo, не были выполнены, так как использовался модификатор :p. Вместо этого были выведены результаты подстановки.

Вот в общих чертах и все. Что я имел сказать - я сказал.

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

Работаем с историей команд. Часть II. Усовершенствования.

Разбираясь с функционированием перечня ранее введенных команд, я обнаружил что перечень обладает несколькими отрицательными свойствами. Так при вводе одной и той же команды, она каждый раз оседает в истории. Пример:
  $ echo 1
        1
        $ echo 1
        1
        $ echo 1
        1
        $ history 5
                26  PS1='$ '
                27  echo 1
                28  echo 1
                29  echo 1
                30  history 5

Как видно команда echo 1 была три раза введена и три раза была записана в историю. Для чего не понятно. Это можно устранить вставив строку HISTCONTROL=ignoredups в ваш файл ~/.bash_profile. Вышеприведенный пример будет выглядеть следующим образом:
  $ HISTCONTROL=ignoredups
        $ echo 1
        1
        $ echo 1
        1
        $ echo 1
        1
        $ echo 1
        1
        $ history 4
                42  history 4
                43  HISTCONTROL=ignoredups
                44  echo 1
                45  history 4

Как видно теперь повторяющаяся четыре раза команда echo 1, в перечне встречается только один раз. Но вот от следующей неприятности вышеприведенная опция не спасет:
    $ echo 1
        1
        $ echo 2
        2
        $ echo 1
        1
        $ history 5
                51  history 6
                52  echo 1
                53  echo 2
                54  echo 1
                55  history 5

Здесь команда echo 1, включена опять-таки два раза, что на мой взгляд, весьма неприятно.

Еще один неприятный момент, это то, что история засоряется короткими командами, смысла хранить которые нет. К таким командам можно отнести ls, ps, who, короче, те команды, длинна которых меньше 3-5 символов. У меня таких команд большинство. И что самое обидное, это то что из-за несколько сотен таких коротких команд, а также повторного включения одной и той же команды в перечень, перечень переполняется и из него выбрасываются действительно нужные команды.

Чтобы это исправить, пришлось написать парочку небольших функций облегчающих работу с историей. Скрипт реализующий их можно найти на странице Программы.

Если вкратце, то там реализованы такие функции для работы с историей:
  • hs - записывает историю в $HISTFILE, кроме того выбрасывает все повторяющиеся команды, а также команды длинна которых меньше $MINHISTITEMSIZE, таким образом перечень команд избавляется от мусора. Данная команда выполняется автоматически при выходе из интерпретатора;
  • hr [n] - название от History Reverse. Печатает n - строк истории, причем кроме прямой нумерации печатает обратную, что облегчает обращение к командам с помощью !-n, если n=0, то выводит весь перечень, если n опущено, то печатает только $DEFAULTPRINTSIZE строк истории;
  • hg [string [n]] - название от History Grep. Синоним hg n | egrep -i string, напечатать n - последних элементов истории, в которых встречается строка string, причем в строке могут применяться расширенные регулярные выражения, и она не чувствительна к регистру. Если n опущено, то поиск ведется по всему перечню.
Чтоб было более понятно о чем идет речь приведу пример:
$ hr         
    10    348  echo $HISTFILESIZE
     9    349  vim lib/advhist.sh
     8    350  ls
     7    351  PS!='$ '
     6    352  PS1='$ '
     5    353  hr
     4    354  ps
     3    355  PS1='$ '
     2    356  who
     1    357  hr
$ hr 5
     5    354  ps
     4    355  ws
     3    356  who
     2    357  hr
     1    358  hr 5
$ hs            # Выкидывает из перечня короткие и повторяющиеся команды
$ hr            # Проверяем результаты
    10    716  lynx sit/linux
     9    717  getpg
     8    718  lynx www.sit.kiev.ua/linux
     7    719  echo $HISTFILE
     6    720  echo $HISTSIZE
     5    721  echo $HISTFILESIZE
     4    722  vim lib/advhist.sh
     3    723  PS!='$ '
     2    724  PS1='$ '
     1    725  hr
$ hg ping       # Находим все команды в которых присутствует подстрока ping 
   116    613  ping www.yandex
    53    676  ping www.yandex.ru
     1    728  hg ping
$ !-53          # Выполняем найденный ping
ping www.yandex.ru
PING yandex.ru (62.118.249.254) from 195.230.153.157 : 56(84) bytes of data.
64 bytes from yandex.ru (62.118.249.254): icmp_seq=0 ttl=235 time=609.5 ms
64 bytes from yandex.ru (62.118.249.254): icmp_seq=1 ttl=235 time=600.0 ms
.....

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

Для использования этих функций, advhist.sh должен находится в ~/lib/advhist.sh, а в ваш .bashrc, необходимо добавить следующие строки:
if [ "$PS1" -a -f ~/lib/advhist.sh ]; then
        . ~/lib/advhist.sh
fi

Для настройки можно изменить следующие переменные окружения:
  • DEFAULTPRINTSIZE - количество печатаемых строк командой hg, по умолчанию равно 10;
  • MINHISTITEMSIZE - строки перечня короче этой величины будут исключены из него при выполнении команды hs, по умолчанию удаляются строки короче 5-ти символов.

Обсудить данную тему на нашем форуме "Все о Linux"