Як відразу визначити подібну функцію 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взагалі використання у цій функції стає абсолютно непотрібним. І все-таки я дозволю йому залишитися, оскільки я не люблю робити одну і ту ж помилку двічі ... поспіль.

Якщо це неприйнятно для запитувача, і він / вона вважає за краще повністю відмовитись від _цього, тоді, в його теперішній формі, редагування _underscoreвиходу повинно бути все, що вимагає, щоб відповідати його вимогам, наскільки я їх розумію.

Крім цієї зміни, я також відредагував функцію використання &&та / або|| оболонки умовних умов короткого замикання, а не оригінальний 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

@Tetsu - чи має тепер сенс краще?
mikeserv

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

@Tetsu Можливо, зараз зрозуміліше ...? Я думаю, що я зрозумів, і тепер виправив помилку, яку я зробив раніше. Будь ласка, дайте мені знати, якщо ви хочете.
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.