Вопрос

Каковы были бы ваши предложения по хорошему шаблону скрипта bash / ksh для использования в качестве стандарта для всех вновь создаваемых скриптов?

Обычно я начинаю (после #! строка) с закомментированным заголовком с именем файла, кратким описанием, использованием, возвращаемыми значениями, автором (ами), списком изменений и поместился бы в строки из 80 символов.

Все строки документации я начинаю с символов двойного хэша ## таким образом, я могу легко использовать grep для них, и имена локальных переменных начинаются с "__".

Есть какие-нибудь другие рекомендации?Чаевые?Соглашения об именовании?А как насчет кодов возврата?

Комментарии к управлению версиями :мы хорошо используем SVN, но у другого подразделения предприятия есть отдельное репозиторий, и это их скрипт.Как я узнаю, к кому обратиться через Q's, если там нет информации @author?Использование записей, похожих на javadocs, имеет некоторые преимущества даже в контексте оболочки, ИМХО, но я могу ошибаться.

Это было полезно?

Решение

Я бы расширил ответ Нормана до 6 строк, и последняя из них пустая:

#!/bin/ksh
#
# @(#)$Id$
#
# Purpose
 

Третья строка - это строка идентификации системы управления версиями - на самом деле это гибрид с маркером SCCS '@(#)"это может быть идентифицировано программой (SCCS) what и строка версии RCS, которая расширяется, когда файл помещается в RCS, VCS по умолчанию, который я использую для личного использования.Программа RCS ident принимает развернутую форму $Id$, который может выглядеть как $Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $.Пятая строка напоминает мне, что в верхней части скрипта должно быть описание его назначения;Я заменяю это слово фактическим описанием скрипта (вот почему, например, после него нет двоеточия).

После этого, по сути, нет ничего стандартного для сценария оболочки.Есть стандартные фрагменты, которые появляются, но нет стандартного фрагмента, который появлялся бы в каждом скрипте.(В моем обсуждении предполагается, что скрипты написаны в нотациях оболочки Bourne, Korn или POSIX (Bash).Существует целое отдельное обсуждение того, почему кто-то ставит производную оболочки C после #! символ - это жизнь во грехе.)

Например, этот код появляется в той или иной форме всякий раз, когда скрипт создает промежуточные (временные) файлы:

tmp=${TMPDIR:-/tmp}/prog.$$
trap "rm -f $tmp.?; exit 1" 0 1 2 3 13 15

...real work that creates temp files $tmp.1, $tmp.2, ...

rm -f $tmp.?
trap 0
exit 0

В первой строке выбирается временный каталог, по умолчанию /tmp, если пользователь не указал альтернативу ($TMPDIR очень широко известен и стандартизирован POSIX).Затем он создает префикс имени файла, включающий идентификатор процесса.Это не является мерой безопасности;это простая мера параллелизма, предотвращающая использование несколькими экземплярами скрипта данных друг друга.(В целях безопасности используйте непредсказуемые имена файлов в закрытом каталоге.) Вторая строка гарантирует, что 'rm" и "exit команды выполняются, если оболочка получает любой из сигналов SIGHUP (1), SIGINT (2), SIGQUIT (3), SIGPIPE (13) или SIGTERM (15).Тот самый 'rm' команда удаляет все промежуточные файлы, соответствующие шаблону;тот самый exit команда гарантирует, что статус отличен от нуля, что указывает на какую-то ошибку.Тот самый 'trap' из 0 означает, что код также выполняется, если оболочка завершает работу по какой-либо причине - это касается небрежности в разделе с пометкой "реальная работа".Затем приведенный в конце код удаляет все сохранившиеся временные файлы, до того, как снятие ловушки при выходе и, наконец, выход с нулевым статусом (успех).Очевидно, что если вы хотите завершить работу с другим статусом, вы можете - просто убедитесь, что вы установили его в переменной перед запуском rm и trap строки, а затем используйте exit $exitval.

Обычно я использую следующее, чтобы удалить путь и суффикс из скрипта, чтобы я мог использовать $arg0 при сообщении об ошибках:

arg0=$(basename $0 .sh)

Я часто использую функцию оболочки для сообщения об ошибках:

error()
{
    echo "$arg0: $*" 1>&2
    exit 1
}

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

Другим довольно стандартным фрагментом является цикл синтаксического анализа опций, использующий getopts встроенная оболочка:

vflag=0
out=
file=
Dflag=
while getopts hvVf:o:D: flag
do
    case "$flag" in
    (h) help; exit 0;;
    (V) echo "$arg0: version $Revision$ ($Date$)"; exit 0;;
    (v) vflag=1;;
    (f) file="$OPTARG";;
    (o) out="$OPTARG";;
    (D) Dflag="$Dflag $OPTARG";;
    (*) usage;;
    esac
done
shift $(expr $OPTIND - 1)

или:

shift $(($OPTIND - 1))

Кавычки вокруг "$OPTARG" обрабатывают пробелы в аргументах.Dflag является кумулятивным, но используемая здесь нотация не учитывает пробелы в аргументах.Существуют (нестандартные) способы обойти и эту проблему.

Обозначение первого сдвига работает с любой оболочкой (или было бы, если бы я использовал обратные тики вместо '$(...)'.Второй работает в современных оболочках;возможно, даже существует альтернатива с квадратными скобками вместо круглых, но это работает, поэтому я не потрудился разобраться, что это такое.

Последний трюк на данный момент заключается в том, что у меня часто есть как GNU, так и не GNU-версии программ, и я хочу иметь возможность выбирать, какие из них я использую.Поэтому многие из моих скриптов используют такие переменные, как:

: ${PERL:=perl}
: ${SED:=sed}

И затем, когда мне нужно вызвать Perl или sed, скрипт использует $PERL или $SED.Это помогает мне, когда что-то ведет себя по-другому - я могу выбрать рабочую версию - или при разработке сценария (я могу добавить дополнительные параметры только для отладки в команду без изменения сценария).(См . Расширение параметров оболочки для получения информации о ${VAR:=value} и связанные с ними обозначения.)

Другие советы

Я использую первый набор строк ## для документации по использованию. Я не могу вспомнить, где я впервые увидел это.

#!/bin/sh
## Usage: myscript [options] ARG1
##
## Options:
##   -h, --help    Display this message.
##   -n            Dry-run; only show what would be done.
##

usage() {
  [ "$*" ] && echo "$0: $*"
  sed -n '/^##/,/^$/s/^## \{0,1\}//p' "$0"
  exit 2
} 2>/dev/null

main() {
  while [ $# -gt 0 ]; do
    case $1 in
    (-n) DRY_RUN=1;;
    (-h|--help) usage 2>&1;;
    (--) shift; break;;
    (-*) usage "$1: unknown option";;
    (*) break;;
    esac
  done
  : do stuff.
}

Любой код, который будет выпущен в дикой природе, должен иметь следующий короткий заголовок:

# Script to turn lead into gold
# Copyright (C) 2009 Joe Q Hacker - All Rights Reserved
# Permission to copy and modify is granted under the foo license
# Last revised 1/1/2009

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

Если вы собираетесь полагаться на bashisms, используйте #! / bin / bash, а не / bin / sh, так как sh - это вызов POSIX любой оболочки. Даже если / bin / sh указывает на bash, многие функции будут отключены, если вы запустите его через / bin / sh. Большинство дистрибутивов Linux не принимают сценарии, которые основаны на bashisms, попробуйте быть переносимыми.

Для меня комментарии в сценариях оболочки глупы, если они не читают что-то вроде:

# I am not crazy, this really is the only way to do this

Сценарии оболочки настолько просты, что (если вы не пишете демонстрацию, чтобы научить кого-то, как это делать), код почти всегда устраняет сам себя.

Некоторым оболочкам не нравится, когда им дают типизированные локальные переменные. Я считаю, что по сей день Busybox (обычная спасательная оболочка) является одним из них. Вместо этого сделайте GLOBALS_OBVIOUS, его будет намного легче читать, особенно при отладке через / bin / sh -x ./script.sh.

Мое личное предпочтение - позволить логике говорить за себя и минимизировать работу для анализатора. Например, многие могут написать:

if [ $i = 1 ]; then
    ... some code 
fi

Где бы я просто:

[ $i = 1 ] && {
    ... some code
}

Кроме того, кто-то может написать:

if [ $i -ne 1 ]; then
   ... some code
fi

... где я бы:

[ $i = 1 ] || {
   ... some code 
}

Единственный раз, когда я использую обычный if / then / else, это если есть else-if, чтобы добавить микс.

Ужасно безумный пример очень хорошего переносимого кода оболочки можно изучить, просто просмотрев скрипт 'configure' в большинстве бесплатных программных пакетов, использующих autoconf. Я говорю «безумие», потому что его 6300 строк кода обслуживают каждую известную человеку систему, имеющую UNIX-подобную оболочку. Вы не хотите такого раздувания, но интересно изучить некоторые из различных хаков переносимости внутри ... например, хорошо относиться к тем, кто может указать / bin / sh на zsh:)

Единственный другой совет, который я могу дать, - следить за расширением в отсюда, т.е.

cat << EOF > foo.sh
   printf "%s was here" "$name"
EOF

... собирается расширить $ name, когда вы, вероятно, захотите оставить переменную на месте. Решите это через:

  printf "%s was here" "\$name"

который оставит $ name в качестве переменной вместо ее расширения.

Я также настоятельно рекомендую узнать, как использовать ловушку для перехвата сигналов ... и использовать эти обработчики в качестве стандартного кода. Говорить, что работающий скрипт замедляется с помощью простого SIGUSR1, очень удобно:)

Большинство новых программ, которые я пишу (ориентированных на инструмент / командную строку), начинаются как сценарии оболочки, это отличный способ для прототипирования инструментов UNIX.

Вам также может понравиться компилятор сценариев оболочки SHC, посмотрите его здесь .

Это заголовок, который я использую для своей оболочки сценария (bash или ksh). Это man похоже и используется для отображения use ().

#!/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/%d@%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/%d@%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

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

Включение обнаружения ошибок значительно упрощает раннее обнаружение проблем в сценарии:

set -o errexit

Выйти из скрипта при первой ошибке. Таким образом, вы не будете продолжать делать что-то, что зависело от чего-то ранее в скрипте, возможно, заканчивая каким-то странным состоянием системы.

set -o nounset

Обрабатывать ссылки на неустановленные переменные как ошибки. Очень важно избегать запуска таких вещей, как rm -you_know_what "$var/" с незаданным $var. Если вы знаете, что переменная может быть неустановлена, и это безопасная ситуация, вы можете использовать ${var-value}, чтобы использовать другое значение, если оно не установлено, или ${var:-value}, чтобы использовать другое значение, если оно не установлено, или пусто.

set -o noclobber

Легко сделать ошибку, вставив > туда, где вы хотели вставить <, и перезаписать какой-нибудь файл, который вы хотели прочитать. Если вам нужно закрыть файл в вашем скрипте, вы можете отключить это перед соответствующей строкой и включить снова после этого.

set -o pipefail

Используйте первый ненулевой код выхода (если он есть) из набора переданных команд в качестве кода завершения полного набора команд. Это облегчает отладку команд по конвейеру.

shopt -s nullglob

Избегайте, чтобы ваш /foo/* глобус интерпретировался буквально , если нет файлов, соответствующих этому выражению.

Вы можете объединить все это в две строки:

set -o errexit -o nounset -o noclobber -o pipefail
shopt -s nullglob

Мой шаблон bash описан ниже (установлен в моей конфигурации vim ):

#!/bin/bash

## DESCRIPTION: 

## AUTHOR: $USER_FULLNAME

declare -r SCRIPT_NAME=$(basename "$BASH_SOURCE" .sh)

## exit the shell(default status code: 1) after printing the message to stderr
bail() {
    echo -ne "$1" >&2
    exit ${2-1}
} 

## help message
declare -r HELP_MSG="Usage: $SCRIPT_NAME [OPTION]... [ARG]...
  -h    display this help and exit
"

## print the usage and exit the shell(default status code: 2)
usage() {
    declare status=2
    if [[ "$1" =~ ^[0-9]+$ ]]; then
        status=$1
        shift
    fi
    bail "${1}$HELP_MSG" $status
}

while getopts ":h" opt; do
    case $opt in
        h)
            usage 0
            ;;
        \?)
            usage "Invalid option: -$OPTARG \n"
            ;;
    esac
done

shift $(($OPTIND - 1))
[[ "$#" -lt 1 ]] && usage "Too few arguments\n"

#==========MAIN CODE BELOW==========

Я бы предложил

#!/bin/ksh

и это все.Тяжеловесные комментарии к блокам для сценариев оболочки?У меня мурашки по коже.

Предложения:

  1. Документация должна представлять собой данные или код, а не комментарии.По крайней мере, usage() функция.Взгляните на то, как ksh и другие инструменты AST документируют себя с помощью параметров --man для каждой команды.(Не могу перейти по ссылке, потому что веб-сайт не работает.)

  2. Объявляйте локальные переменные с помощью typeset.Вот для чего это нужно.Нет необходимости в неприятных подчеркиваниях.

Что вы можете сделать, это создать скрипт, который создает заголовок для скрипта & amp; и автоматически открыть его в вашем любимом редакторе. Я видел, как парень делал это на этом сайте:

http://code.activestate.com/recipes/577862-bash-script-to-create-a-header-for-bash-scripts/?in=lang-bash

#!/bin/bash -       
#title           :mkscript.sh
#description     :This script will make a header for a bash script.
#author          :your_name_here
#date            :20110831
#version         :0.3    
#usage           :bash mkscript.sh
#notes           :Vim and Emacs are needed to use this script.
#bash_version    :4.1.5(1)-release
#===============================================================================

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

Я запускаю каждый скрипт со своим заголовком,

#!/bin/bash
# [ID LINE]
##
## FILE: [Filename]
##
## DESCRIPTION: [Description]
##
## AUTHOR: [Author]
##
## DATE: [XX_XX_XXXX.XX_XX_XX]
## 
## VERSION: [Version]
##
## USAGE: [Usage]
##

Я использую этот формат даты для упрощения поиска / поиска. Я использую скобки '[', чтобы указать текст, который люди должны ввести сами. если они появляются вне комментария, я пытаюсь запустить их с '# ['. Таким образом, если кто-то вставит их как есть, его не примут за ввод или за тестовую команду. Проверьте раздел использования на странице руководства, чтобы увидеть этот стиль в качестве примера.

Когда я хочу закомментировать строку кода, я использую один '#'. Когда я делаю комментарий как примечание, я использую двойное «##». /etc/nanorc также использует это соглашение. Я считаю полезным, чтобы дифференцировать комментарий, который был выбран, чтобы не выполнять; стихи комментарий, который был создан как заметка.

Все мои переменные оболочки я предпочитаю делать в CAPS. Я стараюсь держать от 4 до 8 символов, если не требуется иное. Имена как можно лучше связаны с их использованием.

Я также всегда выхожу с 0 в случае успеха или 1 для ошибок. Если в скрипте есть много разных типов ошибок (и он на самом деле кому-то поможет, или может каким-то образом использоваться в каком-то коде), я бы выбрал документированную последовательность вместо 1. В общем, коды выхода не так строго соблюдаются в мире * nix. К сожалению, я никогда не нашел хорошую схему общих чисел.

Мне нравится обрабатывать аргументы стандартным способом. Я всегда предпочитаю getopts, чтобы getopt. Я никогда не делаю взлома с помощью команд read и if. Мне также нравится использовать case, чтобы избежать вложенных ifs. Я использую скрипт перевода для длинных опций, поэтому --help означает -h для getopts. Я пишу все сценарии либо на bash (если это приемлемо), либо на обобщенном sh.

Я НИКОГДА не использую интерпретируемые bash символы (или любые интерпретируемые символы) в именах файлов или любое другое имя в этом отношении. в частности ... & '`$ & amp; * # () {} [] - я использую _ для пробелов.

Помните, это всего лишь условности. Лучшая практика, конечно, но иногда вы вынуждены выходить за рамки. Самое главное - быть последовательными во всех ваших проектах.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top