Вопрос или проблема
Итак, я начал использовать zsh
. Мне это нравится. Он выглядит очень круто и стильно, и тот факт, что текущий рабочий каталог и фактическая командная строка находятся на разных строках, приятен, но в то же время я замечаю, что zsh
может быть немного медленнее, чем bash
, особенно при выводе текста на экран.
Больше всего мне понравилось, что zsh
был «обратнос совместим» со всеми функциями, которые я определил в своем .bashrc
.
Однако есть одно замечание. Все функции работают идеально, но я не могу понять, как работает система экспорта.
Некоторые из тех функций .bashrc
я экспортировал, чтобы использовать их в других местах, таких как скрипты и внешние программы, с помощью export -f
.
В zsh
о экспорте, похоже, даже не говорят. Это автозагрузка? Эти две вещи одно и то же? Я серьезно затрудняюсь это выяснить.
Переменные окружения, содержащие функции, — это хак bash. У zsh нет ничего подобного. Вы можете сделать что-то похожее с помощью нескольких строк кода. Переменные окружения содержат строки; более старые версии bash, прежде чем была обнаружена Shellshock, хранили код функции в переменной, имя которой совпадало с именем функции, а значение — () {
с последующим кодом функции, а затем }
. Вы можете использовать следующий код, чтобы импортировать переменные с этой кодировкой и попытаться запустить их с настройками, подобными bash. Обратите внимание, что zsh не может эмулировать все возможности bash, все, что вы можете сделать, это приблизиться (например, сделать так, чтобы $foo
разделял значение и разворачивать подстановки, и сделать массивы с нуля).
bash_function_preamble="
emulate -LR ksh
"
for name in ${(k)parameters}; do
[[ "-$parameters[name]-" = *-export-* ]] || continue
[[ ${(P)name} = '() {'*'}' ]] || continue
((! $+builtins[$name])) || continue
functions[$name]=$bash_function_preamble${${${(P)name}#"() {"}%"}"}
done
(Как заметил Стефан Шазелас, первоначальный открыватель Shellshock, более ранняя версия этого ответа могла выполнять произвольный код в этот момент, если определение функции было повреждено. Эта версия не делает этого, но, конечно, как только вы выполните любую команду, это может быть функцией, импортированной из окружения.)
Версии bash после Shellshock кодируют функции в окружении, используя недопустимые имена переменных (например, BASH_FUNC_myfunc%%
). Это делает их трудными для надежного разбора, поскольку zsh не предоставляет интерфейса для извлечения таких имен переменных из окружения.
Я не рекомендую это делать. Полагаться на экспортированные функции в скриптах — плохая идея: это создает невидимую зависимость в вашем скрипте. Если вы когда-либо выполните свой скрипт в окружении, где нет вашей функции (на другом компьютере, в задании cron, после изменения файлов инициализации вашей оболочки и т. д.), ваш скрипт больше не будет работать. Вместо этого храните все ваши функции в одном или нескольких отдельных файлах (что-то подобное ~/lib/shell/foo.sh
) и запускайте свои скрипты, импортируя функции, которые они используют (. ~/lib/shell/foo.sh
). Таким образом, если вы измените foo.sh
, вы сможете легко искать, какие скрипты на него опираются. Если вы скопируете скрипт, вы сможете легко выяснить, какие вспомогательные файлы ему нужны.
Zsh (и ksh до него) делает это более удобным, предоставляя способ автоматически загружать функции в скрипты, где они используются. Ограничение в том, что вы можете помещать только одну функцию на файл. Объявите функцию как автозагружаемую и поместите определение функции в файл, имя которого совпадает с именем функции. Поместите этот файл в каталог, указанный в $fpath
(который вы можете настроить через переменную окружения FPATH
). В вашем скрипте объявите автозагружаемые функции с помощью autoload -U foo
.
Кроме того, zsh может компилировать скрипты, чтобы сэкономить время разбора. Вызовите zcompile
для компиляции скрипта. Это создаст файл с расширением .zwc
. Если этот файл присутствует, то autoload
загрузит скомпилированный файл вместо исходного кода. Вы можете использовать zrecompile
для (ре)компиляции всех определений функций в каталоге.
Если вы поместите ваше объявление функции в .zshenv, ваша функция будет доступна из скрипта без каких-либо усилий.
Везде, где я читал об этом, советуют, что автоматический импорт функций — плохая идея, поэтому я решил просто вынести свои скрипты в отдельный файл, который можно легко загрузить в подпроцесс.
Я назвал общий файл .zshared
.
# ~/.zshared
function say-hello() {
echo "Hello, $1\!"
}
Я загрузил его в своем .zshrc
.
# ~/.zshrc
source ~/.zshared
В любых скриптах, которым нужен доступ к этим функциям, я просто загружаю .zshared
.
#!/usr/bin/env zsh
source ~/.zshared
say-hello "David" # Hello, David!
Сначала это требует немного работы, чтобы обновить ваши скрипты, но вы все равно можете определить свои функции в одном месте без особых затрат.
Вот ваш ответ:
function hello() {
echo "Hello ${@}"
}
export HELLO=$(functions hello)
echo 1 2 3 | xargs zsh -c 'eval $HELLO && hello ${@}' _
Hello 1 2 3
Ответ или решение
Вопрос о том, как в zsh
эквивалентно использовать команду export -f
из bash
, поднимает важные аспекты управления функциями и их доступностью в различных сценариях. Обсуждая этот вопрос, полезно осветить различия между bash
и zsh
, а также предложить подходы, работающие в zsh
.
Суть проблемы
В bash
команда export -f
позволяет экспортировать функции, сделав их доступными для дочерних процессов. Это удобно в тех случаях, когда вы хотите использовать определённые функции в других скриптах или простых вызовах команд, создавая таким образом более модульный и управляемый код.
Однако в zsh
такой функциональности нет. zsh
больше ориентирован на использование функций непосредственно в контексте интерактивного окружения и не поддерживает экспорт функций через окружение, как это делает bash
.
Альтернативные подходы в zsh
Автозагрузка функций
Одним из рекомендуемых методов работы с функциями в zsh
является использование механизма автозагрузки. Этот метод позволяет вам организовать функции таким образом, чтобы они подгружались из файлов, что несколько аналогично принципу работы с экспортированными функциями в bash
.
-
Создание файла с функцией: Каждую функцию можно разместить в отдельном файле, имя которого совпадает с именем функции. Этот файл должен находиться в директории, которая включена в
$fpath
.# ~/functions/say-hello.zsh function say-hello() { echo "Hello, $1!" }
-
Обозначение функции как автозагружаемой: В вашем сценарии или конфигурации вы можете вызвать
autoload
, чтобы подготовить функцию к подгрузке.autoload -U say-hello
Теперь функция say-hello
будет загружена при первом вызове, и вы сможете использовать её подобно экспортированной функции в bash
.
Общий скрипт для функций
Как показано в одном из ответов, вы также можете создать общий файл, где будут размещены ваши функции, и подключать его в нужных скриптах или в вашем файле ~/.zshrc
, чтобы сделать функции доступными глобально:
# ~/.zshared
function say-hello() {
echo "Hello, $1!"
}
В вашем ~/.zshrc
:
source ~/.zshared
Теперь вы сможете использовать функции, найденные в .zshared
, в любых запущенных shell-сессиях или скриптах, просто подгрузив файл.
Заключение
Хотя zsh
не поддерживает export -f
так, как это делает bash
, существуют эффективные альтернативы, такие как автозагрузка функций и использование отдельных файлов для размещения ваших функций. Этот подход не только улучшает читаемость и структурированность кода, но и способствует лучшему управлению зависимостями в ваших скриптах. Используя предложенные методы, вы сможете эффективно адаптировать свои функции для использования вне контекста текущей оболочки.