Во-первых, хочу всем напомнить, что на самом деле alias не обязательно прописывать в конфигурационном файле ~/.bashrc. Более того, весь этот файл не более конфигурационный, чем скрипты из /etc/init.d/: bashrc — это тупо скрипт на языке bash, который выполняется при запуске интерпретатора. В нём можно написать что угодно, всё то же самое можно вбить и в командную строку — эффект будет точно такой же. Поэтому некоторые прописывают в ~/.bashrc команду fortune, а некоторые — команду df -h. Так вот, про что я: alias можно не прописывать в bashrc, а задать в интерактивном режиме: после выхода из сеанса он исчезнет, оставив в память о себе только строчки в ~/.bash_history. Так что можно временно вбить alias tt='/home/name_no/src/program-svn/build/bin/program' и использовать его по назначению.
Но бывает, что одним алиасом не обойдёшься. Например, у меня часто бывает, что приходится несколько раз метнуться из одного каталога в другой, убить логи и какой-то мусор, исправить какую-то закорючку в одном файле и запустить какую-то прогу. Я решил, что мне нужна функция повтора.
Сначала я хотел найти какой нибудь простой способ, вроде !1-5. Как известно, суперкомбо !!, например, повторяет предыдущую команду. Самый типичный пример:
$ rm ./file bash: rm: Невозможно удалить «./file»: Отказано в доступе $ sudo !! sudo rm ./file password: $Ну так вот, я искал что-то вроде этого и не нашёл. То есть оно есть, но не в таком виде, чтобы я мог бы его использовать.
Есть вариант !-5 — этот набор символов запускает пятую с конца истории команду. Можно было бы сделать функцию, которая принимала бы в качестве аргумента число и в цикле запускала бы вот этим образом команды из истории. Но оказалось, что так просто не получится, потому что bash пытался раскрыть !-5 ещё в момент объявления функции, а если объявить alias с таким содержимым, то в нём это вообще не будет раскрываться.
Значит, мне нужны манипуляции со встроенными средствами для работы с историей команд. Сначала я думал взяться за файл истории ~/.bash_history и grep'ом его, sed'ом его, awk'ом его! Но это, конечно, локальный маразм, поэтому я начал искать более другие способы. Дальше я осознал, что для bash и zsh это будет делаться очень по-разному, поэтому сделал только для bash, которым я пользуюсь на рабочем сервере. Для настольного компа с zsh сделаю по мере необходимости и, если что, сразу отпишусь.
Для работы с историей команд в баше надо использовать встроенную команду history. Эта команда принимает среди прочего параметры, заставляющие её принудительно записать историю в файл, причём записать можно два варианта: всю историю и часть истории с последнего момента записи. Негусто, отсчитывать количество команд не позволяет. А может, это и к лучшему? Концепция меняется: ставим метку "начало записи", ставим метку "конец записи", а потом запускаем "повтор" от метки до метки. Получается даже более логичный вариант, который позволяет делать разнообразные промежуточные действия и производить повтор после выхода и нового входа. Технически, конечно, никакие метки никуда не ставятся, на самом деле метка "начало" — это принудительная запись истории в оригинальный ~/.bash_history, а метка "конца" — это запись истории с момента последней синхронизации в отдельный файл — ~/.bash_replay. Поначалу получилось как-то так:
# SET FILENAME RECORD test -z "$REPLAYFILE" && export REPLAYFILE="$HOME/.bash_replay" # BEGIN RECORDING function mark_start { rm -f "$REPLAYFILE" history -a } # STOP RECORDING function mark_stop { history -d $[$HISTCMD-1] history -a "$REPLAYFILE" } # PLAY RECORD function replay { test -f "$REPLAYFILE" || mark_stop set -v source "$REPLAYFILE" set +v }Оказалось, что это работает только для bash версии >4.1. Часть истории команд записывается в отдельный файл, ~/.bash_replay, потом этот файл отдаётся на растерзание интерпретатору текущей сессии с помощью команды source. Но, как выяснилось позже, на 3.2 оно работает некорректно: в replay-файл попадает команда mark_start. Пришлось немного подкорректировать логику, и вся конструкция стала даже ещё логичнее.
Итак, вот результат, который работает у меня на bash версий 3.2 и 4.1:
[[ -z "$REPLAYFILE" ]] && export REPLAYFILE=" $HOME/.bash_replay" function rec { history -a history -c OLD_HISTCONTROL="$HISTCONTROL" OLD_HISTFILE="$HISTFILE" OLD_PS1="$PS1" PS1="\e[31m [•] \e[0m$PS1" HISTCONTROL="ignorespace" HISTFILE="$REPLAYFILE" rm -f "$REPLAYFILE" } function rec_stop { history -d $[$HISTCMD-1] history -w "$REPLAYFILE" HISTCONTROL="$OLD_HISTCONTROL" HISTFILE="$OLD_HISTFILE" history -r } function replay { test -f "$REPLAYFILE" || rec_stop local PS4="\e[31m [▸] \e[0m$PS1" set -x source "$REPLAYFILE" set +x }Этот хитрый набор команд работает немного умнее предыдущего: после сохранения, история очищается, консоль как бы переходит в другой режим — режим записи. Вся история из этого "режима" сохраняется в отдельный файл — ~/.bash_replay, из которого она потом "воспроизводится". Работает и выглядит это примерно так:
user@localhost /pwd $ user@localhost /pwd $ rec [•] user@localhost /pwd $ ls functions [•] user@localhost /pwd $ ls -ls functions 4 -rw-r--r-- 1 user users 706 Фев 14 22:38 functions [•] user@localhost /pwd $ pwd /pwd [•] user@localhost /pwd $ less functions [•] user@localhost /pwd $ rec_stop user@localhost /pwd $ user@localhost /pwd $ user@localhost /pwd $ replay [▸] user@localhost /pwd $ source /home/user/.bash_replay [▸] user@localhost /pwd $ ls --color=auto functions [▸] user@localhost /pwd $ ls --color=auto -ls functions 4 -rw-r--r-- 1 user users 706 Фев 14 22:40 functions [▸] user@localhost /pwd $ pwd /pwd [▸] user@localhost /pwd $ less functions [▸] user@localhost /pwd $ set +x user@localhost /pwd $ user@localhost /pwd $ user@localhost /pwd $ cat ~/.bash_replay ls ls -ls functions pwd less functions user@localhost /pwd $Тут надо отметить некоторые вещи. Во-первых: спички детям не игрушка. Не забывайте вернуться в исходный каталог, не удаляйте важных файлов в режиме записи, следи за собой и будь осторожен ©, короче. Иначе, я не виноват. Конечно, для любителей прострелить себе ногу можно добавить защиту от дурака, заставить функции самостоятельно за всем следить и обеспечивать безопасность, но дуракам это не поможет, а мне будет только мешать.
Во-вторых, при воспроизведении появились команды, которые вручную не вводились, а те, которые вводились — как-то не так выглядят. Это потому что наглядность обеспечивается с помощью башевской опции -x, которая дублирует каждую исполняемую команду на вывод уже после её "разворачивания", то есть после того, как раскроет все алиасы, переменные и спецсимволы. Лишние команды в начале и в конце жить не мешают, поэтому пусть будут. Кстати, важно вместо rec_stop не нажать по привычке Ctrl+D. У меня с этим беда, надо что-то придумывать.
Комментариев нет:
Отправить комментарий