bash: функция + источник + объявить = бум - PullRequest
7 голосов
/ 23 мая 2010

Вот проблема:

В моих скриптах bash я хочу получить несколько файлов с некоторыми проверками, поэтому у меня есть:

if [ -r foo ] ; then
  source foo
else
  logger -t $0 -p crit "unable to source foo"
  exit 1
fi 

if [ -r bar ] ; then
  source bar
else
  logger -t $0 -p crit "unable to source bar"
  exit 1
fi 

# ... etc ...

Наивно я пытался создать функцию, которая делает:

 function safe_source() {
   if [ -r $1 ] ; then
     source $1
   else
     logger -t $0 -p crit "unable to source $1"
     exit 1
   fi 
 }

 safe_source foo
 safe_source bar
 # ... etc ...

Но там есть загвоздка.

Если один из файлов foo, bar и т. Д. Имеет глобальное значение, например -

declare GLOBAL_VAR=42

- фактически станет:

function safe_source() {
  # ...
  declare GLOBAL_VAR=42
  # ...
}

Таким образом, глобальная переменная становится локальной.

Вопрос:

Псевдоним в bash кажется слишком слабым для этого, поэтому я должен развернуть вышеописанную функцию и повториться, или есть более элегантный подход?

... и да, я согласен, что Python, Perl, Ruby облегчили бы мне жизнь, но при работе с устаревшей системой не всегда есть привилегия выбрать лучший инструмент.

Ответы [ 3 ]

8 голосов
/ 19 августа 2010

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

Фон "eval"

Если вы не знакомы с 'eval', это встроенная команда Bash, которая ожидает, что вы передадите ей строку в качестве параметра. 'eval' динамически интерпретирует и выполняет вашу строку как команду самостоятельно, в текущем контексте оболочки и области видимости. Вот базовый пример общего использования (назначение динамических переменных):

$>  a_var_name="color"
$>  eval ${a_var_name}="blue"
$>  echo -e "The color is ${color}."
The color is blue.

См. Руководство по расширенному написанию сценариев Bash для получения дополнительной информации и примеров: http://tldp.org/LDP/abs/html/internal.html#EVALREF

Решение вашей «исходной» проблемы

Чтобы 'eval' справился с вашей проблемой с источником, вы должны начать с переписывания вашей функции 'safe_source ()'. Вместо фактического выполнения команды, safe_source () должен просто ПЕЧАТЬ команды в виде строки в STDOUT:

function safe_source() { echo eval " \
  if [ -r $1 ] ; then \
    source $1 ; \
  else \
    logger -t $0 -p crit \"unable to source $1\" ; \
    exit 1 ; \
  fi \
"; }

Также вам нужно немного изменить вызовы функций, чтобы фактически выполнить команду 'eval':

`safe_source foo`
`safe_source bar`

(Это обратные кавычки / обратные кавычки, кстати.)

Как это работает

Короче говоря:

  • Мы преобразовали функцию в эмиттер командной строки.
  • Наша новая функция генерирует строку вызова команды 'eval'.
  • Наши новые метки обратного вызова вызывают новую функцию в контексте подоболочки, возвращая командную строку 'eval', возвращаемую функцией обратно в основной скрипт.
  • Основной сценарий выполняет командную строку 'eval', захваченную обратными чертами, в контексте основного сценария.
  • Командная строка 'eval' повторно анализирует и выполняет командную строку 'eval' в контексте основного сценария, выполняя весь блок if-then-else, включая (если файл существует) выполнение команды 'source'.

Это довольно сложно. Как я уже сказал, eval не совсем элегантен. В частности, есть несколько особых моментов, которые вы должны обратить внимание на сделанные нами изменения:

  • Весь блок IF-THEN-ELSE превратился в одну целую строку в двойных кавычках с обратными слешами в конце каждой строки, скрывающими символы новой строки.
  • Некоторые из специальных символов оболочки, такие как '"'), экранированы обратной косой чертой, а другие ('$') остались без экранирования.
  • 'echo eval' был добавлен перед всей командной строкой.
  • Дополнительные точки с запятой были добавлены ко всем строкам, где выполняется команда для их завершения, роль, которую первоначально выполняли (теперь скрытые) переводы строк.
  • Вызов функции заключен в обратные кавычки.

Большинство этих изменений вызвано тем, что eval не будет обрабатывать переводы строки. Он может работать только с несколькими командами, если мы вместо этого объединяем их в одну строку, разделенную точками с запятой. Разрывы строк новой функции - это удобство форматирования для человеческого глаза.

Если что-то из этого неясно, запустите ваш скрипт с включенным флагом Bash '-x' (выполнение отладки), и это должно дать вам лучшее представление о том, что именно происходит. Например, в контексте функции функция фактически создает командную строку 'eval', выполняя эту команду:

echo eval ' if [ -r <INCL_FILE> ] ; then source <INCL_FILE> ; else logger -t <SCRIPT_NAME> -p crit "unable to source <INCL_FILE>" ; exit 1 ; fi '

Затем в главном контексте главный скрипт выполняет это:

eval if '[' -r <INCL_FILE> ']' ';' then source <INCL_FILE> ';' else logger -t <SCRIPT_NAME> -p crit '"unable' to source '<INCL_FILE>"' ';' exit 1 ';' fi

Наконец, снова в главном контексте, команда eval выполняет эти две команды, если существует:

'[' -r <INCL_FILE> ']'
source <INCL_FILE>

Удачи.

7 голосов
/ 13 марта 2014

Ответ немного запоздал, но теперь declare поддерживает параметр -g, который делает переменную глобальной (при использовании внутри функции). То же самое работает в исходном файле.

Если вам нужна глобальная переменная (чтение экспортировано), используйте:

declare -g DATA="Hello World, meow!"
1 голос
/ 23 мая 2010

declare внутри функции делает переменную локальной для этой функции. export влияет на среду дочерних процессов, а не на текущую или родительскую среду.

Вы можете установить значения своих переменных внутри функций и сделать declare -r, declare -i или declare -ri после факта.

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