Оценить переменную во время объявления функции в оболочке - PullRequest
0 голосов
/ 02 августа 2020

Я настраиваю среду оболочки и хочу иметь возможность использовать некоторые из тех же функций / псевдонимов в zsh, что и в bash. Одна из этих функций открывает в редакторе .bashr c или .zshr c (в зависимости от файла), ожидает закрытия редактора, а затем перезагружает файл r c.

# a very simplified version of this function
editrc() {
  local rcfile=".$(basename $SHELL)rc"
  code -w ~/$rcfile
  . ~/$rcfile
}

Я использую значение rcfile в нескольких других функциях, поэтому я вытащил его из объявления функции.

_rc=".$(basename $SHELL)rc"
editrc() {
  code -w ~/$_rc
  . ~/$_rc
}

# ... other functions that use it ...
unset _rc

Однако, поскольку я аккуратный урод, я хочу unset _rc в конце моего скрипта, но я все же хочу, чтобы мои функции работали правильно. Есть ли умный способ оценить $_rc во время объявления функции?

Я знаю, что могу использовать eval и поместить все, кроме $_rc экземпляров, в одинарные кавычки, но это похоже на боль , поскольку в полной версии моей функции используются как одинарные, так и двойные кавычки.

_rc=".$(basename $SHELL)rc"
eval 'editrc() {
  echo Here'"'"'s a thing that uses single quotes.  As you can see it'"'"'s a pain.
  code -w ~/'$_rc'
  . ~/'$_rc'
}'
# ... other functions using `_rc`
unset _rc

Думаю, я мог бы объявить свои функции, а затем сделать несколько магов c с eval "$(declare -f editrc | awk)". Конечно, это будет больше боли, чем оно того стоит, но я всегда заинтересован в изучении нового.

Примечание: я бы хотел обобщить это в служебную функцию, которая делает это.

_myvar=foo
anothervar=bar
myfunc() {
  echo $_myvar $anothervar
}

# redeclares myfunc with `$_myvar` expanded, but leaves `$anothervar` as-is
expandfunctionvars myfunc '$_myvar' 

Ответы [ 2 ]

3 голосов
/ 03 августа 2020

Есть ли умный способ оценить $ _r c во время объявления функции?

_rc=".$(basename "$SHELL")rc"
# while you could eval here, source lets you work with a stream
source <(
  cat <<EOF
  editrc() {
       local _rc
       # first safely trasfer context
       $(declare -p _rc)
EOF
  # use quoted here string to do anything inside without caring. 
  cat <<'EOF' 
     # do anything else
     echo "Here's a thing that uses single quotes.  As you can see it's not a pain, just choose proper quoting."
      code -w "~/$_rc"
      . "~/$_rc"
  }
EOF
)
unset _rc

Обычно сначала используйте declare -p для передачи переменных в виде строк в быть оцененным. Затем, после того как вы «импортируете» переменные, используйте цитируемый здесь документ, чтобы делать что угодно, как в обычном скрипте.

Ссылки для чтения:

  • <<EOF здесь документ . Обратите внимание на разницу в синтаксическом анализе, когда здесь разделитель заключен в кавычки и не заключен в кавычки.
  • <(..) - это подстановка процесса

Команда source считывает канал создается путем замещения процесса. Внутри подстановки процесса я вывожу функцию, которую нужно получить. В первом документе я вывожу определение имени функции с local переменной, чтобы оно не загрязняло глобальное пространство имен. Затем с помощью declare -p я вывожу определение переменной в виде строки с правильными кавычками, которую позже получит source. Затем с цитируемым здесь документом я вывожу остальную часть функции, так что мне не нужно заботиться о цитировании.

Код bash указывает c, я ничего не знаю о zsh и не используйте его.

Вы также можете сделать это с помощью eval:

eval '
editrc() {
       local _rc
       # first safely trasfer context
       '"$(declare -p _rc)"'
       # use quoted here string to do anything inside without caring. 
       # do anything else
       echo "Here'\''s a thing that uses single quotes.  As you can see it'\''s not a pain, just choose proper quoting."
      code -w "~/$_rc"
      . "~/$_rc"
}'

Но для меня использование указанного здесь разделителя документов позволяет упростить запись.

0 голосов
/ 03 августа 2020

Пока KamilCuck работал над своим ответом, я разработал функцию, которая будет принимать любое имя функции и набор имен переменных, расширять только эти переменные и повторно объявлять функцию.

expandFnVars() {
  if [[ $# -lt 2 ]]; then
    >&2 echo 'expandFnVars requires at least two arguments: the function name and the variable(s) to be expanded'
    return 1
  fi

  local fn="$1"
  shift

  local vars=("$@")

  if [[ -z "$(declare -F $fn 2> /dev/null)" ]]; then
    >&2 echo $fn is not a function.
    return 1
  fi

  foundAllVars=true
  for v in $vars; do
    if [[ -z "$(declare -p $v 2> /dev/null)" ]]; then
      >&2 echo $v is not a declared value.
      foundAllVars=false
    fi
  done

  [[ $foundAllVars != true ]] && return 1

  fn="$(declare -f $fn)"

  for v in $vars; do
    local val="$(eval 'echo $'$v)" # get the value of the varable represented by $v
    val="${val//\"/\\\"}" # escape any double-quotes
    val="${val//\\/\\\\\\}" # escape any backslashes
    fn="$(echo "$fn" | sed -r 's/"?\$'$v'"?/"'"$val"'"/g')" # replace instances of "$$v" and $$v with $val
  done

  eval "$fn"
}

Использование:

foo="foo bar"
bar='$foo'
baz=baz

fn() {
  echo $bar $baz
}

expandFnVars fn bar

declare -f fn
# prints:
#  fn () 
#  {
#     echo "$foo" $baz
#  }

expandFnVars fn foo

declare -f fn
# prints:
#  fn () 
#  {
#     echo "foo bar" $baz
#  }

Глядя на это сейчас, я вижу один недостаток. Предположим, что $bar в исходной функции было заключено в одинарные кавычки. Вероятно, мы не хотели бы, чтобы его стоимость менялась. Это можно исправить с помощью некоторых умных функций просмотра назад регулярных выражений, чтобы подсчитать количество неэкранированных ' s, но я доволен этим как есть.

...