Подтвердить что ты не робот

Bash утилита script библиотека

Существует ли обычно используемая (или несправедливо используемая) утилита "библиотека" функций bash? Что-то вроде Apache commons-lang для Java. bash настолько вездесущ, что кажется странным пренебречь в области библиотек расширений.

4b9b3361

Ответ 1

Библиотеки для bash находятся там, но не являются общими. Одна из причин нехватки библиотек bash объясняется ограничением функций. Я считаю, что эти ограничения лучше всего объясняются на "Greg bash Wiki":

Функции. bash "функции" имеют несколько проблем:

  • Функции повторного использования кода: bash ничего не возвращают; они производят только выходные потоки. Каждый разумный метод захвата этого потока и либо назначение его переменной, либо передача ее в качестве аргумента требует SubShell, который разбивает все назначения на внешние области. (См. Также BashFAQ/084 для трюков для получения результатов от функции.) Таким образом, библиотеки функций многократного использования невозможны, так как вы не можете запрашивать функцию для хранения ее результатов в переменной, имя которой передается в качестве аргумента (за исключением путем выполнения eval backflips).

  • Область: bash имеет простую систему локального масштаба, которая примерно напоминает "динамическую область" (например, Javascript, elisp). Функции видят локали своих вызывающих абонентов (например, ключевое слово "нелокальное" Python), но не могут получить доступ к позиционным параметрам вызывающего абонента (кроме BASH_ARGV, если включен extdebug). Функции многократного использования не могут быть гарантированы без коллизий пространства имен, если вы не прибегаете к странным правилам именования, чтобы сделать конфликты достаточно маловероятными. Это особенно проблема при реализации функций, которые ожидают, что они будут воздействовать на имена переменных из фрейма n-3, которые могут быть перезаписаны вашей многоразовой функцией в n-2. Ksh93 может использовать более распространенные лексические правила области, объявляя функции с синтаксисом "имя функции {...}" (Bash не может, но поддерживает этот синтаксис в любом случае).

  • Закрытие. В Bash сами функции всегда являются глобальными (имеют "область файлов" ), поэтому никаких закрытий. Определения функций могут быть вложенными, но это не закрытие, хотя они выглядят очень одинаково. Функции не являются "проходимыми" (первоклассными), а анонимных функций (лямбда) нет. На самом деле ничто не "проходимо", особенно не массивы. bash использует строгую семантику посылок (исключение магии псевдонима).

  • Есть еще много осложнений, связанных с: подоболочками; экспортируемые функции; "функция свертывания" (функции, которые определяют или переопределяют другие функции или сами); ловушки (и их наследование); и способ взаимодействия функций с stdio. Не кусайте новичка, чтобы не понимать все это. Функции оболочки полностью f *** ed.

Источник: http://mywiki.wooledge.org/BashWeaknesses

Одним из примеров "библиотеки" оболочки является /etc/rc.d/functions в системе на основе Redhat. Этот файл содержит функции, обычно используемые в sysV init script.

Ответ 2

Переменные, объявленные внутри функции, но без ключевого слова local являются глобальными.

Хорошей практикой является объявление переменных, необходимых только внутри функции с помощью local, чтобы избежать конфликтов с другими функциями и глобально (см. ниже foo()).

Библиотеки функций

Bash всегда должны быть 'sourced'. Я предпочитаю использовать синоним "источник" вместо более общей точки (.), Поэтому я могу лучше видеть ее во время отладки.

Следующая техника работает, по крайней мере, в bash 3.00.16 и 4.1.5...

#!/bin/bash
#
# TECHNIQUES
#

source ./TECHNIQUES.source

echo
echo "Send user prompts inside a function to stderr..."
foo() {
    echo "  Function foo()..."              >&2 # send user prompts to stderr
    echo "    Echoing 'this is my data'..." >&2 # send user prompts to stderr
    echo "this is my data"                      # this will not be displayed yet
}
#
fnRESULT=$(foo)                       # prints: Function foo()...
echo "  foo() returned '$fnRESULT'"   # prints: foo() returned 'this is my data'

echo
echo "Passing global and local variables..."
#
GLOBALVAR="Reusing result of foo() which is '$fnRESULT'"
echo "  Outside function: GLOBALVAR=$GLOBALVAR"
#
function fn()
{
  local LOCALVAR="declared inside fn() with 'local' keyword is only visible in fn()"
  GLOBALinFN="declared inside fn() without 'local' keyword is visible globally"
  echo
  echo "  Inside function fn()..."
  echo "    GLOBALVAR=$GLOBALVAR"
  echo "    LOCALVAR=$LOCALVAR"
  echo "    GLOBALinFN=$GLOBALinFN"
}

# call fn()...
fn

# call fnX()...
fnX

echo
echo "  Outside function..."
echo "    GLOBALVAR=$GLOBALVAR"
echo
echo "    LOCALVAR=$LOCALVAR"
echo "    GLOBALinFN=$GLOBALinFN"
echo
echo "    LOCALVARx=$LOCALVARx"
echo "    GLOBALinFNx=$GLOBALinFNx"
echo

Библиотека исходных функций представлена ​​...

#!/bin/bash
#
# TECHNIQUES.source
#

function fnX()
{
  local LOCALVARx="declared inside fnX() with 'local' keyword is only visible in fnX()"
  GLOBALinFNx="declared inside fnX() without 'local' keyword is visible globally"
  echo
  echo "  Inside function fnX()..."
  echo "    GLOBALVAR=$GLOBALVAR"
  echo "    LOCALVARx=$LOCALVARx"
  echo "    GLOBALinFNx=$GLOBALinFNx"
}

Running TECHNIQUES производит следующий вывод...

Send user prompts inside a function to stderr...
  Function foo()...
    Echoing 'this is my data'...
  foo() returned 'this is my data'

Passing global and local variables...
  Outside function: GLOBALVAR=Reusing result of foo() which is 'this is my data'

  Inside function fn()...
    GLOBALVAR=Reusing result of foo() which is 'this is my data'
    LOCALVAR=declared inside fn() with 'local' keyword is only visible in fn()
    GLOBALinFN=declared inside fn() without 'local' keyword is visible globally

  Inside function fnX()...
    GLOBALVAR=Reusing result of foo() which is 'this is my data'
    LOCALVARx=declared inside fnX() with 'local' keyword is only visible in fnX()
    GLOBALinFNx=declared inside fnX() without 'local' keyword is visible globally

  Outside function...
    GLOBALVAR=Reusing result of foo() which is 'this is my data'

    LOCALVAR=
    GLOBALinFN=declared inside fn() without 'local' keyword is visible globally

    LOCALVARx=
    GLOBALinFNx=declared inside fnX() without 'local' keyword is visible globally

Ответ 4

Я вижу хорошую информацию и плохую информацию здесь. Позвольте мне поделиться тем, что я знаю, поскольку bash является основным языком, который я использую на работе (и мы строим библиотеки..). У Google есть достойная запись на скриптах bash в целом, которые, как я думал, были хорошо прочитаны: https://google.github.io/styleguide/shell.xml.

Начну с того, что вы не должны думать о библиотеке bash, как и библиотеки на других языках. Существуют определенные методы, которые должны быть соблюдены, чтобы поддерживать библиотеку в bash простой, организованной и, самое главное, многоразовой.

Нет никакой концепции возврата чего-либо из функции bash, кроме строк, которые он печатает, и статуса выхода функции (0-255). Здесь есть ожидаемые ограничения и кривая обучения, особенно если вы привыкли к функциям языков более высокого уровня. Сначала это может быть странно, и если вы окажетесь в ситуации, когда строки просто не разрезают ее, вам нужно использовать внешний инструмент, такой как jq. Если jq (или что-то вроде этого) доступно, вы можете начать с того, что ваши функции распечатывают отформатированный вывод, который будет разбираться и использоваться так же, как и объект, массив и т.д.

Объявление функций

Существует два способа объявить функцию в bash. Один работает в вашей текущей оболочке, мы будем называть Fx0. И один порождает подоболочку для работы, мы будем называть это Fx1. Вот примеры того, как они объявлены:

Fx0(){ echo "Hello from $FUNCNAME"; }
Fx1()( echo "Hello from $FUNCNAME" )

Эти 2 функции выполняют ту же операцию - действительно. Однако здесь есть ключевое различие. Fx1 не может выполнять никаких действий, которые изменяют текущую оболочку. Это означает изменение переменных, изменение параметров оболочки и объявление других функций. Последним является то, что можно использовать для предотвращения проблем с расширением имен, которые могут легко подкрасться к вам.

# Fx1 cannot change the variable from a subshell
Fx0(){ Fx=0; }
Fx1()( Fx=1 )
Fx=foo; Fx0; echo $Fx
# 0
Fx=foo; Fx1; echo $Fx
# foo

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

innocent_function()(
    echo ":)"
)
Fx1()(
    innocent_function()( true )
    innocent_function
)
Fx1 #prints nothing, just returns true
innocent_function
# :)

Это имело бы (вероятные) непреднамеренные последствия, если бы вы использовали фигурные скобки. Примеры полезных функций типа "Fx0" были бы специально для изменения текущей оболочки, например:

use_strict(){
    set -eEu -o pipefail
}
enable_debug(){
    set -Tx
}
disable_debug(){
    set +Tx
}

Относительно деклараций

Использование глобальных переменных или, по крайней мере, те, которые, как ожидается, имеют ценность, - это плохая практика. Когда вы создаете библиотеку в bash, вы никогда не хотите, чтобы функция полагалась на уже установленную внешнюю переменную. Все, что необходимо для этой функции, должно быть предоставлено с помощью позиционных параметров. Это основная проблема, которую я вижу в библиотеках, которые другие люди пытаются создать в bash. Даже если я нахожу что-то классное, я не могу использовать его, потому что я не знаю имена переменных, которые мне нужно установить заранее. Это приводит к копанию всего кода и, в конечном счете, просто подборку полезных вещей для себя. Безусловно, лучшие функции для создания библиотеки крайне малы и вообще не используют именованные переменные, даже локально. Возьмем, например, следующее:

serviceClient()(
    showUsage()(
        echo "This should be a help page"
    ) >&2
    isValidArg()(
        test "$(type -t "$1")" = "function"
    )
    isRunning()(
        nc -zw1 "$(getHostname)" "$(getPortNumber)"
    ) &>/dev/null
    getHostname()(
        echo localhost
    )
    getPortNumber()(
        echo 80
    )
    getStatus()(
        if isRunning
        then echo OK
        else echo DOWN
        fi
    )
    getErrorCount()(
        grep -c "ERROR" /var/log/apache2/error.log
    )
    printDetails()(
        echo "Service status: $(getStatus)"
        echo "Errors logged: $(getErrorCount)"
    )
    if isValidArg "$1"
    then "$1"
    else showUsage
    fi
)

Как правило, то, что вы видите возле вершины, это local hostname=localhost и local port_number=80, что хорошо, но это необязательно. Я считаю, что эти вещи должны быть функциональными, поскольку вы строите, чтобы предотвратить будущую боль, когда внезапно необходимо ввести некоторую логику для получения значения, например: if isHttps; then echo 443; else echo 80; fi. Вы не хотите, чтобы такая логика была помещена в вашу основную функцию, иначе вы быстро сделаете ее уродливой и неуправляемой. Теперь serviceClient имеет внутренние функции, которые объявляются при вызове, что добавляет незаметное количество накладных расходов для каждого запуска. Преимущество в том, что теперь вы можете иметь service2Client с функциями (или внешними функциями), которые называются такими же, как у serviceClient с абсолютно никакими конфликтами. Еще одна важная вещь, о которой следует помнить, заключается в том, что перенаправление может быть применено к целой функции после ее объявления. см.: isRunning или showUsage Это приближается к объектно-ориентированному, поскольку я думаю, что вам следует использовать bash.

. serviceClient.sh
serviceClient
# This should be a help page
if serviceClient isRunning
then serviceClient printDetails
fi
# Service status: OK
# Errors logged: 0

Я надеюсь, что это поможет моим молодым хакерам bash.

Ответ 5

Я могу сказать вам, что отсутствие доступных библиотек функций не имеет ничего общего с ограничениями Bash, а используется как Bash. Bash - это быстрый и грязный язык для автоматизации, а не для разработки, поэтому потребность в библиотеке встречается редко. Затем вы начинаете иметь тонкую грань между функцией, которая должна быть разделена, и преобразование функции в полноценный script для вызова. Это с точки зрения кодирования, для загрузки оболочки - другое дело, но обычно работает на личном вкусе, а не в необходимости. Итак, опять-таки отсутствие общих библиотек.

Вот несколько функций, которые я использую регулярно В моем .bashrc

cd () {
   local pwd="${PWD}/"; # we need a slash at the end so we can check for it, too
   if [[ "$1" == "-e" ]]; then
      shift
      # start from the end
      [[ "$2" ]] && builtin cd "${pwd%/$1/*}/${2:-$1}/${pwd##*/$1/}" || builtin cd "[email protected]"
   else
      # start from the beginning
      if [[ "$2" ]]; then
         builtin cd "${pwd/$1/$2}"
         pwd
      else
         builtin cd "[email protected]"
      fi
   fi
}

И версия log()/err() существует в библиотеке функций для работы с кодами - в основном, мы все используем один и тот же стиль.

log() {
   echo -e "$(date +%m.%d_%H:%M) [email protected]"| tee -a $OUTPUT_LOG
}

err() {
   echo -e "$(date +%m.%d_%H:%M) [email protected]" |tee -a $OUTPUT_LOG
}

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