Избегайте неожиданного поведения, используя ссылки на имена в bash

#bash

#bash

Вопрос:

Почему это не дает никаких выходных данных (кроме новой строки) вместо «foo»? В коде используется ссылка на имя, которая была введена в bash 4.3 и является «ссылкой на другую переменную», которая «позволяет манипулировать переменными косвенно».

И что следует сделать, чтобы защититься от этого, при написании кода для библиотеки?

 #!/usr/bin/bash

setret() {
   local -n ret_ref=$1
   local ret="foo"
   ret_ref=$ret
}

setret ret
echo $ret
  

Прогоняя его bash -x , у меня закружилась голова, потому что, похоже, он должен выводить то, foo что я ожидал:

   setret ret
  local -n ret_ref=ret
  local ret=foo
  ret_ref=foo
  echo

  

Интересно, что это выводит bar , а не foo .

 #!/usr/bin/bash

setret() {
   local -n ret_ref=$1
   ret_ref="bar"
   local ret="foo"
   ret_ref=$ret
}

setret ret
echo $ret
  

С таким же запутанным bash -x выводом:

   setret ret
  local -n ret_ref=ret
  ret_ref=bar
  local ret=foo
  ret_ref=foo
  echo bar
bar
  

Ответ №1:

Я надеюсь, что это ценно для других, потому что запрос ожидаемого результата в #bash IRC-канале получил ответ от одного из его постоянных пользователей foo , чего я и ожидал.

Затем они меня просветили. ссылки на имена просто работают не так, как я думал. local -n не настроен ret_ref для ссылки на $1 . Скорее, он в основном хранит строку ret в ret_ref , помеченную для использования в качестве ссылки при ее использовании.

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

Единственный гарантированный способ защититься от этого при написании кода для библиотеки — в любой функции, которая использует namerefs, добавлять ко всем переменным, не являющимся nameref, префикс имени функции в следующих строках:

 #!/usr/bin/bash

setret() {
   local -n ___setret_ret_ref=$1
   local ___setret_ret="foo"
   ___setret_ret_ref=$___setret_ret
}

setret ret
echo $ret
  

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