_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那么shell会$expansion像下面这样对shell进行评估:
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并对其进行评估以用于Shell扩展。如果此时您运行了:
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>之前提供给您阅读...)
最好的部分是引号-请注意,'"引号仍然保留,只有\引号被删除。可能是因为这个原因比其他任何原因都多,所以如果您必须对shell进行两次评估,$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和总电流位置数,或者$#是如果我当时叫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在shell提示符下,则在shell提示符下~/some.sh可以调用其中声明的所有函数和环境变量。除了文件描述符. source 的character device特殊文件,或者. /dev/fd/3- here-document内联文件已在其中存放路径的文件-并且我们已经声明了函数外,这里也是如此。这就是它的工作原理。
_guard
现在执行您的_guard功能应该执行的操作。
附录:
保存位置信息的好方法:
f() { . /dev/fd/3
} 3<<-ARGS
args='${args:-"$@"}'
ARGS
编辑:
当我第一次回答这个问题时,我更关注于声明一个shell,该shell function()能够声明将在当前shell持久化过程中继续存在的其他功能,而不是我对$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。
注意:这种行为对我来说是个习惯问题-我通常以这样的假设为前提:shell函数应仅占据自己的函数_namespace,以避免它们侵入namespaceshellcommands本身。
但是,这不是普遍的习惯,就像问问者使用commandcall所证明的那样$1。
进一步检查使我相信以下内容:
询问器需要命名为shell函数guard, rspec, or rake,该壳函数在被调用时将重新编译ruby同名函数,if该文件Gemfile存在于$PATH OR
if Gemfile不存在的情况下,shell函数应执行ruby同名函数。
以前这是行不通的,因为我还更改了的$1要求command,改为:
command _${1}
不会导致执行rubyshell函数编译为的函数:
bundle exec $1
我希望您能看到(就像最终我一样),问问者似乎根本就只是command在使用间接指定,namespace因为它command更喜欢$PATH通过同名的Shell函数来调用可执行文件。
如果我的分析是正确的(我希望申请者确认),那么可以这样做:
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
应该更好地满足这些条件,但guard在提示符处调用只会尝试执行$PATHnamed中的可执行文件,guard而_guard在提示符处进行调用将检查是否Gemfile's存在并相应地编译或执行中的guard可执行文件$PATH。以这种方式namespace受到保护,并且至少按照我的理解,询问者的意图仍然得以实现。
实际上,假定我们的shell函数_${1}()和可执行文件${PATH}/${1}是我们的shell解释对其中一个调用的唯一两种方式,$1或者_${1}然后再command在该函数中使用完全是多余的。尽管如此,我还是保留了它,因为我不想连续两次犯同样的错误。
如果问询者无法接受,并且他/她希望_完全放弃,那么就其目前的形式而言,_underscore根据我的理解,应根据要求满足他/她的要求来进行编辑。
除了所做的更改外,我还编辑了使用&&和/或||外壳短路条件if/then语句的功能,而不是原始语法。这样的command说法只计算在所有的,如果Gemfile在不$PATH。此修改确实需要添加,return $?但是要确保在不存在bundle该事件Gemfile但该ruby $1函数返回0以外的值的情况下不运行该语句。
最后,我应注意,此解决方案仅实现可移植的shell构造。换句话说,在任何声称具有POSIX兼容性的shell中,这应该产生相同的结果。当然,声称每个POSIX兼容系统都必须处理该ruby bundle指令对我来说是胡说八道,但是至少调用它的shell命令必须具有相同的行为,而不管调用的shell是sh还是dash。同样,上述方法在和中都将按预期工作(shopts无论如何至少应该保持中立)。bashzsh
for loop?我的意思是,它不会被吃掉,for loop通常声明的变量会消失-由于相同的原因,我期望相同的功能。