google web font

воскресенье, 13 февраля 2011 г.

alias против bash-функицй: rm -i

Итак, я решил на наглядном примере объяснить, чем принципиально отличается шелловский alias от шелловской функции. В качестве примера я решил использовать распространённый совет, который дают всем начинающим админам многочисленные авторы книг и статей про консоль: это совет прописать в ~/.bashrc такое:
alias rm='rm -i' 
Кстати, этот алиас демонстрирует хорошую особенность: если в теле алиаса есть команда, совпадающая по имени с именем алиаса, то он не впадает в бесконечную рекурсию, а нормально обрабатывает эту ситуацию.

Для тех, кто не понял: этот алиас заставляет команду rm запрашивать ввод подтверждения для каждого удаляемого файла. Напомню, к чему это ведёт, помимо того, что понижает вероятность случайно снести нужный файл.


Когда-то давно я сидел под виндой и удалял файлы с помощью кнопки Delete на клавиатуре. Вскоре мне надоело постоянно чистить корзину и я узнал про такое замечательное сочетание клавиш, как Shift+Delete. Теперь файлы удалялись сразу навсегда, поэтому винда всегда запрашивала подтверждение: показывала окно с вопросом "ты уверен?" и фокусом на кнопке "да, уверен". Поскольку фокус был всегда на нужной кнопке, сочетание клавиш довольно быстро превратилось из Shift+Delete в Shift+Delete+Enter, а подтверждение так и не выполняло своих функций, продолжая создавать нагрузку на техподдержку (по запросам "я удалил важный системный файл, а у меня всё сломалось"). Поэтому я считаю, что предложенный алиас будет полезен только в первый день, и поэтому лично я сделал бы по-другому.

Для начала определимся с целями. Кроме задачи продемонстрировать область применения алиасов, можно ещё дополнительно снизить вероятность случайно снести нужные файлы. Способы: 1) запросить подтверждение, 2) дать время передумать, 3) не удалять файлы даже в случае получения подтверждения.

1. Запросить подтверждение. Итак, я уже выяснил, что если запрашивать подтверждение постоянно, то получится банальная бюрократия, которую я все мои читатели нежно ненавидят. Зато у меня перед глазами есть пример, когда при подобном запросе подтверждения я всё-таки проверяю, действительно ли я сношу то, что надо. Это происходит не из-за того, что я осознаю важность момента, а из-за того, что подтверждение запрашивается только тогда, когда это действительно надо. Поэтому лично я считаю, что запрос подтверждения всё-таки полезен, если он происходит тогда, когда это действительно нужно. Пример может выглядеть так:
declare -a RESTRICTED=(/bin  /boot  /etc  /lib  /opt  /sbin  /usr  /var/www)

function rm_i {
  for FILE in "$@"; do
    RESTRICT=""
    for X in "${RESTRICTED[@]}"; do
      X="$(readlink -f "$X")"
      FILE="$(readlink -f "$FILE")"
      if [[ "$FILE" =~ ^"$X" ]]; then
        RESTRICT="-i"
        break
      fi
    done
    rm -v $RESTRICT "$FILE"
  done
}

alias rm='rm_i'
Этот пример проверяет, что файл входит в список каталогов, для которых нужно запрашивать подтверждение, и если не входит — то просто удаляет. Ключ -v добавлен, чтобы отсутствие запроса для части удаляемых файлов не поднимало в сознании вопросов о том, были ли удалены все файлы. Список каталогов вынесен за пределы функции для того, чтобы его можно было использовать в последующих примерах. Если захочется погрохать файлы без подтверждения, всегда можно использовать /bin/rm. В моём грубом примере есть одна ошибка — ему нельзя передавать ключи для rm. Эту проблему (решать которую здесь не имеет особого смысла) заинтересовавшимся предлагается решить самостоятельно, например, можно проверить все аргументы, самостоятельно запросить подтверждение и отдать аргументы без изменения команде rm.

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

2. Таймаут на размышления. Примерно как emerge при удалении пакетов запускает обратный отсчёт, прежде, чем приступить к работе. Пять вынужденных секунд ожидания заставят задуматься о необходимости удаления и дадут время проверить список удаляемых файлов. Чтобы не заставлять пользователя терпеть таймаут при каждом удалении (он начнёт его обходить постоянно и таймаут потеряет смысл), можно воспользоваться тем же списком важных директорий из предыдущего примера. Решать будем как-то так:
declare -a RESTRICTED=(/bin  /boot  /etc  /lib  /opt  /sbin  /usr  /var/www)

function count_doun {
  for X in $(seq 5 -1 1)
  do
    echo -n "$X "
    sleep 1
  done
  echo 0
}

function rm_i {
  for FILE in "$@"; do
    RESTRICT="0"
    for X in "${RESTRICTED[@]}"; do
      X="$(readlink -f "$X")"
      FILE="$(readlink -f "$FILE")"
      if [[ "$FILE" =~ ^"$X" ]]; then
        echo "You are going to delete some important files. Press Ctrl+C now to change your mind."
        count_doun
        break 2
      fi
    done
    rm -v "$FILE"
  done
}

alias rm='rm_i'
Теперь консоль будет в данной ситуации не запрашивать подтверждение на каждый файл, а просто предлагать подумать как следует, и, если передумал, нажать Ctrl+C. В данной ситуации мы вроде как не имеем проблемы с передачей аргументов команде rm, но всё ещё есть куда улучшаться: можно, например, обратный отсчёт сделать цветным, да ещё и разноцветным, чтобы "5" печаталось зелёным, а "1" — уже красным, можно дополнительно вывести список удаляемых файлов, чтобы было над чем подумать. Ещё раз напомню, что писать всю эту красоту нужно непосредственно в ~/.bashrc.

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

3. Не удалять. Речь внезапно пойдёт про корзину, спецификацию для которой успешно продвинули в проект free-desktop.org. Поскольку сам я KDE-шник, то и клиента для корзины буду использовать кэдэешного. Пример функции, которая вместо удаления будет кидать файлы в корзину будет очень мало отличаться от удаления файлов с запросом подтверждения:
declare -a RESTRICTED=(/bin  /boot  /etc  /lib  /opt  /sbin  /usr  /var/www)

function rm_i {
  for FILE in "$@"; do
    for X in "${RESTRICTED[@]}"; do
      X="$(readlink -f "$X")"
      FILE="$(readlink -f "$FILE")"
      if [[ "$FILE" =~ ^"$X" ]]; then
        kioclient move "$FILE" trash:/
        break
      fi
    done
    rm "$FILE"
  done
}

alias rm='rm_i'
Здесь у нас функция перебирает список файлов, и те, которые лежат в "охраняемых" каталогах, не удаляет, а отправляет в корзину, остальные просто удаляет. При таком подходе объективно нельзя передавать командам параметры, кроме списка файлов, зато ни один конфиг или другой нужный файл не будет удалён безвозвратно. Как управлять корзиной из консоли — я так и не понял, но можно делать kioclient exec trash:/ и управлять корзиной в Dolphin'е, можно изучить dbus-интерфейс этого чуда и написать несколько алиасов для быстрого просмотра списка фала и восстановления выбранных, но это всё мелочи, главное — это чувство уверенности в завтрашнем дне.

Итак, выводы. Совершенно очевидно, что alias годится только для того, чтобы сократить длинный список аргументов команды до некоторого короткого имени, предназначенного для конкретного действия. Функции имеют более широкие возможности: им можно передавать аргументы, как обычным командам, при чём, как было продемонстрировано выше, alias таки может ссылаться не только на команду/программу/скрипт, но и на функцию.

1 комментарий: