Общий шаблон оболочки script

Миллионы разработчиков пишут сценарии оболочки для решения различных задач. Я использую shell-скрипты для упрощения развертывания, управления жизненным циклом, установки или просто как язык клея.

Что я заметил, никто не заботится о стиле и качестве скриптов оболочки. Многие команды тратят много часов на исправление проблем стиля Java, С++,..., но полностью игнорируют проблемы в своих сценариях оболочки. Кстати, обычно нет стандартного способа реализации оболочки script в рамках конкретного проекта, поэтому можно найти десятки разных, уродливых и багги-скриптов, распространяемых вокруг кодовой базы.

Чтобы преодолеть эту проблему в моих проектах, я решил создать шаблон оболочки script, универсальный и достаточно хороший. Я предоставлю свои шаблоны, чтобы сделать этот вопрос более полезным. Из этих шаблонов вы можете:

  • Обработка аргументов командной строки
  • синхронизация
  • Основная помощь

Обработка аргументов: getopts (последняя версия: shell- script -template @github)

#!/bin/bash
# ------------------------------------------------------------------
# [Author] Title
#          Description
# ------------------------------------------------------------------

VERSION=0.1.0
SUBJECT=some-unique-id
USAGE="Usage: command -ihv args"

# --- Options processing -------------------------------------------
if [ $# == 0 ] ; then
    echo $USAGE
    exit 1;
fi

while getopts ":i:vh" optname
  do
    case "$optname" in
      "v")
        echo "Version $VERSION"
        exit 0;
        ;;
      "i")
        echo "-i argument: $OPTARG"
        ;;
      "h")
        echo $USAGE
        exit 0;
        ;;
      "?")
        echo "Unknown option $OPTARG"
        exit 0;
        ;;
      ":")
        echo "No argument value for option $OPTARG"
        exit 0;
        ;;
      *)
        echo "Unknown error while processing options"
        exit 0;
        ;;
    esac
  done

shift $(($OPTIND - 1))

param1=$1
param2=$2

# --- Locks -------------------------------------------------------
LOCK_FILE=/tmp/$SUBJECT.lock
if [ -f "$LOCK_FILE" ]; then
   echo "Script is already running"
   exit
fi

trap "rm -f $LOCK_FILE" EXIT
touch $LOCK_FILE

# --- Body --------------------------------------------------------
#  SCRIPT LOGIC GOES HERE
echo $param1
echo $param2
# -----------------------------------------------------------------

Флаги Shell (shFlags) позволяет упростить аргументы командной строки, так что в какой-то момент я решил не игнорировать такую ​​возможность.

Обработка аргументов: shflags (последняя версия: shell- script -template @github)

#!/bin/bash
# ------------------------------------------------------------------
# [Author] Title
#          Description
#
#          This script uses shFlags -- Advanced command-line flag
#          library for Unix shell scripts.
#          http://code.google.com/p/shflags/
#
# Dependency:
#     http://shflags.googlecode.com/svn/trunk/source/1.0/src/shflags
# ------------------------------------------------------------------
VERSION=0.1.0
SUBJECT=some-unique-id
USAGE="Usage: command -hv args"

# --- Option processing --------------------------------------------
if [ $# == 0 ] ; then
    echo $USAGE
    exit 1;
fi

. ./shflags

DEFINE_string 'aparam' 'adefault' 'First parameter'
DEFINE_string 'bparam' 'bdefault' 'Second parameter'

# parse command line
FLAGS "[email protected]" || exit 1
eval set -- "${FLAGS_ARGV}"

shift $(($OPTIND - 1))

param1=$1
param2=$2

# --- Locks -------------------------------------------------------
LOCK_FILE=/tmp/${SUBJECT}.lock

if [ -f "$LOCK_FILE" ]; then
echo "Script is already running"
exit
fi

trap "rm -f $LOCK_FILE" EXIT
touch $LOCK_FILE

# -- Body ---------------------------------------------------------
#  SCRIPT LOGIC GOES HERE
echo "Param A: $FLAGS_aparam"
echo "Param B: $FLAGS_bparam"
echo $param1
echo $param2
# -----------------------------------------------------------------

Я думаю, что эти шаблоны можно улучшить, чтобы еще больше упростить жизнь разработчика.

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

  • встроенный журнал
  • улучшенная обработка ошибок
  • улучшенная переносимость
  • меньший размер
  • встроенное отслеживание времени выполнения

Ответ 1

Я бы не стал полагаться на bash в качестве оболочки и смоделировал ваше решение поверх синтаксиса оболочки , определенного в POSIX, и использовал бы /bin/sh на шебанге. Недавно у нас было несколько сюрпризов, когда Ubuntu сменил /bin/sh на dash.

Еще одна пандемия в мире оболочек - это общее неправильное понимание кодов состояния выхода. Выход с понятным кодом - это то, что позволяет другим сценариям оболочки программно реагировать на конкретные сбои. К сожалению, есть не много рекомендаций по этому вопросу, кроме заголовочного файла "sysexits.h".

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

Лично я не нашел большого применения для шаблонов оболочки. К сожалению, большинство инженеров просто скопируют и вставят ваш шаблон и продолжат писать такой же небрежный код оболочки. Лучше всего создать библиотеку функций оболочки с четко определенной семантикой, а затем убедить других использовать их. Этот подход также поможет с контролем изменений. Например, если вы обнаружите дефект в шаблоне, то каждый скрипт, основанный на нем, будет поврежден и потребует изменений. Использование библиотеки позволяет исправить дефекты в одном месте.

Добро пожаловать в мир сценариев оболочки. Написание сценариев оболочки - это немного утраченное искусство, которое, похоже, вступает в эпоху Возрождения. В конце 90-х годов было написано несколько хороших книг на эту тему - Программирование на оболочке UNIX Бернса и Артура приходит на ум, хотя рецензии на книгу от Amazon делают ее ужасной. ИМХО, эффективный код оболочки охватывает философию UNIX, описанную Эриком С. Раймондом в статье Искусство программирования Unix.

Ответ 2

Это заголовок моего шаблона оболочки script (который можно найти здесь: http://www.uxora.com/unix/shell-script/18-shell-script-template).

Это внешний вид man, который используется при использовании() для справки diplsay.

#!/bin/ksh
#================================================================
# HEADER
#================================================================
#% SYNOPSIS
#+    ${SCRIPT_NAME} [-hv] [-o[file]] args ...
#%
#% DESCRIPTION
#%    This is a script template
#%    to start any good shell script.
#%
#% OPTIONS
#%    -o [file], --output=[file]    Set log file (default=/dev/null)
#%                                  use DEFAULT keyword to autoname file
#%                                  The default value is /dev/null.
#%    -t, --timelog                 Add timestamp to log ("+%y/%m/%[email protected]%H:%M:%S")
#%    -x, --ignorelock              Ignore if lock file exists
#%    -h, --help                    Print this help
#%    -v, --version                 Print script information
#%
#% EXAMPLES
#%    ${SCRIPT_NAME} -o DEFAULT arg1 arg2
#%
#================================================================
#- IMPLEMENTATION
#-    version         ${SCRIPT_NAME} (www.uxora.com) 0.0.4
#-    author          Michel VONGVILAY
#-    copyright       Copyright (c) http://www.uxora.com
#-    license         GNU General Public License
#-    script_id       12345
#-
#================================================================
#  HISTORY
#     2015/03/01 : mvongvilay : Script creation
#     2015/04/01 : mvongvilay : Add long options and improvements
# 
#================================================================
#  DEBUG OPTION
#    set -n  # Uncomment to check your syntax, without execution.
#    set -x  # Uncomment to debug this shell script
#
#================================================================
# END_OF_HEADER
#================================================================

И вот функции использования:

  #== needed variables ==#
SCRIPT_HEADSIZE=$(head -200 ${0} |grep -n "^# END_OF_HEADER" | cut -f1 -d:)
SCRIPT_NAME="$(basename ${0})"

  #== usage functions ==#
usage() { printf "Usage: "; head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#+" | sed -e "s/^#+[ ]*//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }
usagefull() { head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#[%+-]" | sed -e "s/^#[%+-]//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }
scriptinfo() { head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#-" | sed -e "s/^#-//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g"; }

Вот что вы должны получить:

# Display help
$ ./template.sh --help

    SYNOPSIS
    template.sh [-hv] [-o[file]] args ...

    DESCRIPTION
    This is a script template
    to start any good shell script.

    OPTIONS
    -o [file], --output=[file]    Set log file (default=/dev/null)
                                  use DEFAULT keyword to autoname file
                                  The default value is /dev/null.
    -t, --timelog                 Add timestamp to log ("+%y/%m/%[email protected]%H:%M:%S")
    -x, --ignorelock              Ignore if lock file exists
    -h, --help                    Print this help
    -v, --version                 Print script information

    EXAMPLES
    template.sh -o DEFAULT arg1 arg2

    IMPLEMENTATION
    version         template.sh (www.uxora.com) 0.0.4
    author          Michel VONGVILAY
    copyright       Copyright (c) http://www.uxora.com
    license         GNU General Public License
    script_id       12345

# Display version info
$ ./template.sh -v

    IMPLEMENTATION
    version         template.sh (www.uxora.com) 0.0.4
    author          Michel VONGVILAY
    copyright       Copyright (c) http://www.uxora.com
    license         GNU General Public License
    script_id       12345

Вы можете получить полный шаблон script здесь: http://www.uxora.com/unix/shell-script/18-shell-script-template

Ответ 3

Если вы обеспокоены переносимостью, не используйте == в тестах. Вместо этого используйте =. Не проверяйте явно, если $# равно 0. Вместо этого используйте ${n?error message} при первом обращении к требуемому аргументу (например, ${3?error message}). Это предотвращает крайне раздражающую практику испускания оператора использования вместо сообщения об ошибке. И самое главное, всегда помещайте сообщения об ошибках в правый поток и выходите с правильным статусом. Например:

echo "Unknown error while processing options" >&2
exit 1;

Часто бывает удобно делать что-то вроде:

die() { echo "$*"; exit 1; } >&2

Ответ 4

Я также расскажу о своих результатах. Идея всех этих примеров заключается в том, чтобы стимулировать общее качество. Также важно убедиться, что конечный результат достаточно безопасен.

Logging

Очень важно иметь правильное ведение журнала с одного и того же начала. Я просто пытаюсь думать об использовании продукции.

TAG="foo"
LOG_FILE="example.log"

function log() {
    if [ $HIDE_LOG ]; then
        echo -e "[$TAG] [email protected]" >> $LOG_FILE
    else
        echo "[`date +"%Y/%m/%d:%H:%M:%S %z"`] [$TAG] [email protected]" | tee -a $LOG_FILE
    fi
}

log "[I] service start"
log "[D] debug message"

Командный тест

Это касается безопасности, реальной жизни и правильной обработки ошибок. Может быть необязательным.

function is_command () {
    log "[I] check if commad $1 exists"
    type "$1" &> /dev/null ;
}

CMD=zip

if is_command ${CMD} ; then
   log "[I] '${CMD}' command found"
else
   log "[E] '${CMD}' command not found"
fi

Обработка шаблонов

Могло быть только мое субъективное мнение, но в любом случае. Я использовал несколько разных способов для создания некоторой конфигурации /etc прямо из script. Perl, sed и другие выполняют эту работу, но выглядят немного страшно.

Недавно я заметил лучший способ:

function process_template() {
    source $1 > $2

    result=$?
    if [ $result -ne 0 ]; then
        log "[E] Error during template processing: '$1' > '$2'"
    fi
    return $result
}

VALUE1="tmpl-value-1"
VALUE2="tmpl-value-2"
VALUE3="tmpl-value-3"

process_template template.tmpl template.result

Пример шаблона

echo "Line1: ${VALUE1}
Line2: ${VALUE2}
Line3: ${VALUE3}"

Пример результата

Line1: tmpl-value-1
Line2: tmpl-value-2
Line3: tmpl-value-3

Ответ 5

В оболочке script нет более полезной вещи, чем хорошо документированное поведение с примерами использования и список известных ошибок. Я думаю, что ни одна программа не может быть названа пуленепробиваемой, и ошибки могут появляться в каждый момент (особенно, когда ваш script используется другими людьми), поэтому единственное, о чем я забочусь, это хороший стиль кодирования и использовать только те вещи, которые действительно нужны script. Youre стоит на пути агрегации, и он всегда станет большой системой, которая поставляется с множеством неиспользуемых модулей, которые трудно переносить и трудно поддерживать. И чем больше система пытается быть портативной, тем больше она растет. Серьезно, скрипты оболочки не нуждаются в том, чтобы они были реализованы таким образом. Они должны быть как можно меньше, чтобы упростить дальнейшее использование.

Если системе действительно нужно что-то большое и пуленепробиваемое, ее время подумать о C99 или даже С++.

Ответ 6

Здесь мой bash шаблон с некоторыми здравомыслящими вариантами, объясненными в комментариях

#!/usr/bin/env bash

set -e  # Abort script at first error, when a command exits with non-zero status (except in until or while loops, if-tests, list constructs)
set -u  # Attempt to use undefined variable outputs error message, and forces an exit
set -x  # Similar to verbose mode (-v), but expands commands
set -o pipefail  # Causes a pipeline to return the exit status of the last command in the pipe that returned a non-zero return value.