Как определить подобную функцию bash сразу


10

У меня есть эти функции в ~/.bashrc:

function guard() {
    if [ -e 'Gemfile' ]; then
    bundle exec guard "$@"
    else
    command guard "$@"
    fi
}
function rspec() {
    if [ -e 'Gemfile' ]; then
    bundle exec rspec "$@"
    else
    command rspec "$@"
    fi
}
function rake() {
    if [ -e 'Gemfile' ]; then
        bundle exec rake "$@"
    else
        command rake "$@"
    fi
}

Как видите, эти функции очень похожи. Я хочу определить эти 3 функции одновременно. Есть ли способ сделать это?

Окружающая среда

bash --version
GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)

Ответы:


8
$ cat t.sh
#!/bin/bash

for func in guard rspec rake; do
        eval "
        ${func}() {
                local foo=(command ${func})
                [ -e 'Gemfile' ] && foo=(bundle exec ${func})
                \"\${foo[@]}\" \"\$@\"
        }
        "
done

type guard rspec rake

,

$ ./t.sh
guard is a function
guard ()
{
    local foo=(command guard);
    [ -e 'Gemfile' ] && foo=(bundle exec guard);
    "${foo[@]}" "$@"
}
rspec is a function
rspec ()
{
    local foo=(command rspec);
    [ -e 'Gemfile' ] && foo=(bundle exec rspec);
    "${foo[@]}" "$@"
}
rake is a function
rake ()
{
    local foo=(command rake);
    [ -e 'Gemfile' ] && foo=(bundle exec rake);
    "${foo[@]}" "$@"
}

Обычные предостережения о evalприменении.


Разве это не съедается, for loop?я имею в виду, переменные, объявленные как for loopправило, исчезают - я ожидаю, что те же функции по тем же причинам.
mikeserv

Что заставляет вас думать, что? bash -c 'for i in 1; do :; done; echo $i'=> 1. В typeясно показывает , что функции существуют вне рамок цикла.
Адриан Фрювирт,

1
@mikeserv Даже с bashдинамической областью видимости все, что вы можете получить, это localлокальная переменная в области видимости целой функции , переменные определенно не «исчезают» после цикла. На самом деле, поскольку здесь нет функции, даже невозможно определить localпеременную в этом случае.
Адриан Фрювирт,

Справа - локально для цикла for - они локально ограничены. Они исчезают, как только их оболочка родительского цикла. Разве это не происходит здесь?
mikeserv

Нет, как я только что объяснил, в сценариях оболочки нет такого понятия, как «локальный для цикла for», и мой пост и пример в моем комментарии выше ясно показывают это.
Адриан Фрувирт

7
_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.


Я помещаю ваш код ~/.bashrcи вызываю . ~/.bashrc, затем эти три функции выполняются. Возможно, поведение отличается в зависимости от среды, поэтому я добавил свою среду в вопрос. Кроме того, я не мог понять, зачем _guard ; _rspec ; _rakeнужна последняя строка . Я искал shiftи дескриптор файла, похоже, это за пределами моего текущего понимания.
ironsand

Я просто положил это, чтобы показать, что они могут быть вызваны. Извините - сейчас я вставил эхо. Таким образом, вы можете вызывать их как функции, как вы продемонстрировали.
mikeserv

@ Тетсу - теперь это имеет смысл?
mikeserv

Я прочитал ваш ответ 3 раза, но, честно говоря, мне нужно больше знаний, чтобы понять объяснение. Несмотря на то, что я вам очень благодарен, я прочитаю это снова, когда получу больше опыта.
ironsand

@ Тетсу Может быть, теперь стало понятнее ...? Я думаю, что я понял и теперь исправил ошибку, которую я сделал ранее. Пожалуйста, дайте мне знать, если хотите.
mikeserv

2
function threeinone () {
    local var="$1"
    if [ $# -ne 1 ]; then
        return 1
    fi
    if ! [ "$1" = "guard" -o "$1" = "rspec" -o "$1" = "rake" ]; then
        return 1
    fi
    shift
    if [ -e 'Gemfile' ]; then
        bundle exec "$var" "$@"
    else
        command "$var" "$@"
    fi
}

threeinone guard
threeinone rspec
threeinone rake
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.