_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.
for loop?я маю на увазі, змінні, оголошеніfor loopзагалом, зникають - я б очікував однакових функцій з тих же причин.