_gem_dec() { shift $# ; . /dev/fd/3
} 3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
for func in guard rspec rake ; do _gem_dec $func ; done
echo "_guard ; _rspec ; _rake are all functions now."
Вышеупомянутое завещание, . source /dev/fd/3которое вводится в _gem_dec()функцию каждый раз, когда она вызывается как предварительно оцененная here-document. _gem_dec'sзадача, состоит в том, чтобы получить один параметр и предварительно оценить его как bundle execцель и как имя функции, для которой она предназначена.
NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.
В вышеприведенном случае, однако, я не думаю, что может быть какой-либо риск.
Если приведенный выше код-блок копируется в .bashrcфайл, будут функции оболочки не только _guard(), _rspec()и_rake() быть объявлены при входе в систему, но _gem_dec()функция также будет доступна для исполнения в любое время в вашем приглашении оболочки (или иначе) , и поэтому новые шаблонных функции могут быть объявленным в любое время просто:
_gem_dec $new_templated_function_name
И спасибо @Andrew за то, что показали мне, что они не будут съедены for loop.
НО КАК?
Я использую 3описанный выше файловый дескриптор, чтобы он оставался stdin, stdout, and stderr, or <&0 >&1 >&2открытым по привычке - хотя, как и в случае с некоторыми другими мерами предосторожности по умолчанию, которые я здесь реализую, - потому что результирующая функция настолько проста, что на самом деле не нужна. Это хорошая практика, хотя. Вызов shift $#является еще одной из тех ненужных мер предосторожности.
Тем не менее, когда файл указан как <inputили>output с помощью [optional num]<fileили [optional num]>fileперенаправления, ядро считывает его в дескриптор файла, к которому можно получить доступ через character deviceспециальные файлы в /dev/fd/[0-9]*. Если [optional num]спецификатор опущен, то 0<fileпредполагается для ввода и 1>fileдля вывода. Учти это:
l='line %d\n' ; printf "$l" 1 2 3 4 5 6 >/dev/fd/1
> line 1
> line 2
> line 3
> line 4
> line 5
> line 6
( printf "$l" 4 5 6 >/dev/fd/3 ; printf "$l" 1 2 3 ) >/tmp/sample 3>/tmp/sample2
( cat /tmp/sample2 ) </tmp/sample
> line 4
> line 5
> line 6
( cat /dev/fd/0 ) </tmp/sample
> line 1
> line 2
> line 3
( cat /dev/fd/3 ) </tmp/sample 3</tmp/sample2
> line 4
> line 5
> line 6
И потому что a here-document- это всего лишь средство описания встроенного файла в блоке кода, когда мы делаем:
<<'HEREDOC'
[$CODE]
HEREDOC
Мы могли бы также сделать:
echo '[$CODE]' >/dev/fd/0
С одним очень важным отличием. Если вы не из то оболочка будет оценивать его оболочку , как:"'\quote'"<<"'\LIMITER"'here-document$expansion
echo "[$CODE]" >/dev/fd/0
Таким образом, для _gem_dec(),3<<-FUNC here-document оценивается в виде файла на входе, так же , как было бы , если бы это было за 3<~/some.file исключением того, что , поскольку мы оставляем FUNCограничитель свободной котировок, он первым оцениваемого для $expansion.Важной особенностью является то , что это вход, значение он существует только для, _gem_dec(),но он также оценивается перед запуском _gem_dec()функции, потому что наша оболочка должна прочитать и оценить ее $expansionsперед передачей в качестве ввода.
Давайте сделаем guard,например:
_gem_dec guard
Итак, сначала оболочка должна обработать ввод, что означает чтение:
3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
В файловый дескриптор 3 и оценки его для расширения оболочки. Если в это время вы бежали:
cat /dev/fd/3
Или:
cat <&3
Поскольку они обе эквивалентные команды, вы увидите *:
_guard() { [ ! -e 'Gemfile' ] && {
command guard "$@" ; return $?
} || bundle exec guard "$@"
}
... прежде чем какой-либо код в функции выполняется вообще. Это функция <input, в конце концов. Для большего количества примеров смотрите мой ответ на другой вопрос здесь .
(* Технически это не совсем так. Потому что я использую ведущий -dashдо here-doc limiter, выше были бы все выравнивается по левому краю. Но я использовал , -dashтак что я мог бы <tab-insert>для удобства чтения , в первую очередь , так что я не собираюсь обирать <tab-inserts>до того предлагая это вам прочитать ...)
Самая приятная часть об этом - цитирование - обратите внимание, что '"цитаты остаются, и только \цитаты были удалены. Вероятно, по этой причине больше, чем что-либо другое, если вам придется дважды оценить оболочку, $expansionя рекомендую, here-documentпотому что цитаты намного проще, чем eval.
Как бы то ни было, теперь приведенный выше код в точности похож на файл, который подается как 3<~/heredoc.fileожидание начала работы _gem_dec()функции и подтверждения ввода /dev/fd/3.
Поэтому, когда мы начинаем, _gem_dec()первое, что я делаю, - это отбрасываю все позиционные параметры, потому что наш следующий шаг - это дважды оцененное расширение оболочки, и я не хочу, чтобы любое из содержимого $expansionsинтерпретировалось как любой из моих текущих $1 $2 $3...параметров. Так что я:
shift $#
shiftсбрасывает столько, positional parametersсколько вы укажете, и начинается $1с того, что осталось. Так что если я позвоню _gem_dec one two threeв быстрых _gem_dec's $1 $2 $3позиционных параметров будет one two threeи суммарный ток позиционного счетчика, или $#будет 3. Если бы я тогда назвал shift 2,значения oneиtwo будет shiftэд прочь, значение $1изменится на threeи $#будет расширяться до 1. Так shift $#просто выбрасывает их всех Делать это строго осторожно, и это привычка, которую я выработал после того, как начал делать подобные вещи некоторое время. Вот это (subshell)для удобства:
( set -- one two three ; echo "$1 $2 $3" ; echo $# )
> one two three
> 3
( set -- one two three ; shift 2 ; echo "$1 $2 $3" ; echo $# )
> three
> 1
( set -- one two three ; shift $# ; echo "$1 $2 $3" ; echo $# )
>
> 0
Во всяком случае, следующий шаг, где происходит волшебство. Если вы . ~/some.shв приглашении оболочки, то все функции и переменные среды, объявленные в, ~/some.shбудут вызываться в вашем приглашении оболочки. То же самое верно здесь, за исключением того, мы специальный файл для нашего дескриптора файла, или - что , где наш файл в линии был pathed - и мы объявили нашу функцию. И вот как это работает.. sourcecharacter device. /dev/fd/3here-document
_guard
Теперь _guardделай то, что должна делать твоя функция.
Приложение:
Отличный способ сказать, сохранить ваши позиционеры:
f() { . /dev/fd/3
} 3<<-ARGS
args='${args:-"$@"}'
ARGS
РЕДАКТИРОВАТЬ:
Когда я впервые ответил на этот вопрос, я больше сосредоточился на проблеме объявления оболочки, function()способной объявлять другие функции, которые будут сохраняться в текущем $ENVраскладывании оболочки, чем на том, что спрашивающий будет делать с упомянутыми постоянными функциями. С тех пор я понял, что мое первоначально предложенное решение, в котором 3<<-FUNCпринял форму:
3<<-FUNC
_${1}() {
if [ -e 'Gemfile' ]; then
bundle exec $1 "\$@"
else
command _${1} "\$@"
}
FUNC
Скорее всего, он не сработал бы так, как ожидалось, потому что я специально изменил имя декларативной функции, из- $1за _${1}которого, если ее вызывать как, _gem_dec guardнапример, получилось бы, _gem_decобъявить функцию с именем, _guardа не просто guard.
Примечание. Такое поведение для меня является привычкой - обычно я исхожу из предположения, что функции оболочки должны занимать только свои собственные,_namespaceчтобы избежать их вторжения вnamespaceсаму оболочкуcommands.
Тем не менее, это не универсальная привычка, как показывает использование Аскером commandпризыва $1.
Дальнейшее изучение заставляет меня поверить в следующее:
Аскер хочет, чтобы именованные функции оболочки выполнялись так, guard, rspec, or rakeчтобы при вызове заново компилировать rubyфункцию с тем же именем, ifв котором файл Gemfileсуществует, $PATH ИЛИ
if Gemfile не существует, функция оболочки должна выполнять rubyфункцию с тем же именем.
Это не сработало бы ранее, потому что я также изменил $1призванный, commandчтобы читать:
command _${1}
Что не привело бы к выполнению rubyфункции, которую функция оболочки скомпилировала как:
bundle exec $1
Я надеюсь, что вы видите (как в конце концов я и сделал), что кажется, что спрашивающий вообще использует только commandдля косвенного указания, namespaceпотому commandчто предпочтет вызывать исполняемый файл $PATHповерх функции оболочки с тем же именем.
Если мой анализ верен (как я надеюсь, аскер подтвердит), то это:
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
Если лучше удовлетворять эти условия, за исключением , что вызов guardв приглашении будет только попытается выполнить исполняемый файл с $PATHименем , guardтогда вызов _guardв ответе на запросе будет проверять Gemfile'sналичие и компиляцию соответственно или выполнить guardисполняемый модуль $PATH. Таким образом namespace, защищен и, по крайней мере, как я понимаю, намерение спрашивающего все еще выполняется.
В самом деле, предполагая нашу функцию оболочки _${1}()и исполняемый ${PATH}/${1}являются только два способом нашей оболочка может интерпретировать вызов либо $1или _${1}то использование commandв функции вообще теперь сделано полностью избыточное. Тем не менее, я позволил этому остаться, поскольку я не люблю повторять одну и ту же ошибку дважды ... в любом случае.
Если это неприемлемо для спрашивающего, и он / она предпочли бы покончить с _полностью, тогда в его текущей форме редактирование _underscoreout должно быть всем, что нужно запрашивающему, чтобы удовлетворить его / ее требования, насколько я понимаю их.
Помимо этого изменения я также отредактировал функцию, чтобы использовать &&и / или|| использовать условия короткого замыкания оболочки, а не исходный if/thenсинтаксис. Таким образом, commandутверждение оценивается только на всех , если Gemfileне в $PATH. Эта модификация требует добавления, return $?однако, чтобы гарантировать, что bundleоператор не выполняется, если событие Gemfileне существует, но ruby $1функция возвращает что-либо кроме 0.
Наконец, я должен отметить, что это решение реализует только переносимые конструкции оболочки. Другими словами, это должно давать идентичные результаты в любой оболочке, претендующей на совместимость с POSIX. Хотя, конечно, для меня было бы бессмысленным утверждать, что каждая POSIX-совместимая система должна обрабатывать ruby bundleдирективу, по крайней мере императивы оболочки, вызывающие ее, должны вести себя одинаково независимо от того, является ли вызывающая оболочка shили нет dash. Кроме того , выше будет работать , как и ожидалось (предполагая , по крайней мере , на полпути-сане shoptsтак или иначе) в обоих bashи zsh.
for loop?я имею в виду, переменные, объявленные какfor loopправило, исчезают - я ожидаю, что те же функции по тем же причинам.