我有这些功能~/.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)
答案1
$ 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
。
答案2
_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
target 并作为其目标函数的名称。
NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.
但在上述情况下,我认为不会有任何风险。
如果将上述代码块复制到.bashrc
文件,不仅会shell功能_guard(), _rspec()
和_rake()
在登录时声明,但是_gem_dec()
函数也可以随时在 shell 提示符下执行(或其他方式)所以您可以随时声明新的模板化函数,只需:
_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
并且因为一个here-document
只是在代码块中内联描述文件的一种方法,当我们这样做时:
<<'HEREDOC'
[$CODE]
HEREDOC
我们不妨这样做:
echo '[$CODE]' >/dev/fd/0
与一个很重要区别。如果你不"'\quote'"
这<<"'\LIMITER"'
的一个here-document
然后 shell 会评估它的 shell$expansion
喜欢:
echo "[$CODE]" >/dev/fd/0
因此对于_gem_dec()
,这3<<-FUNC here-document
被评估为输入时的文件,与它的情况相同3<~/some.file
除了因为我们离开了FUNC
限制器不含引号,首先对其进行评估$expansion.
重要的是它是输入,这意味着它只存在于_gem_dec(),
但在之前也会对其进行评估_gem_dec()
函数运行是因为我们的 shell 必须读取并评估它$expansions
前将其作为输入传递。
让我们做guard,
例如:
_gem_dec guard
因此,首先 shell 必须处理输入,这意味着读取:
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()
我做的第一件事就是扔全部位置参数,因为我们的下一步是两次评估的 shell 扩展,并且我不想要任何包含的$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
在 shell 提示符下声明的所有函数和环境变量~/some.sh
然后可以在 shell 提示符下调用。这里也是同样的情况,只不过我们. source
这character device
我们的文件描述符的特殊文件,或者. /dev/fd/3
- 这就是我们的here-document
内联文件已被指定路径 - 并且我们已经声明了我们的函数。这就是它的工作原理。
_guard
现在做任何你想做的事_guard
函数应该做的。
附录:
保存位置的好方法:
f() { . /dev/fd/3
} 3<<-ARGS
args='${args:-"$@"}'
ARGS
编辑:
当我第一次回答这个问题时,我更关注声明 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
为了避免他们的侵入namespace
壳的commands
恰当的。
然而,这并不是一个普遍的习惯,正如提问者使用command
呼吁$1
。
进一步的检查使我相信以下内容:
提问者想要命名为 shell 函数
guard, rspec, or rake
当被调用时,将重新编译ruby
同名函数if
文件Gemfile
存在于$PATH
或者if Gemfile
不存在,shell函数应该执行ruby
同名函数。
这以前是行不通的,因为我也改变了$1
被召唤command
读书:
command _${1}
这不会导致执行ruby
shell函数编译为的函数:
bundle exec $1
我希望你能看到(就像我最终所做的那样)看来提问者只使用command
完全间接指定namespace
因为command
更喜欢调用可执行文件$PATH
通过同名的 shell 函数。
如果我的分析是正确的(我希望提问者能够确认)那么这个:
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
应该更好地满足这些条件,但调用除外guard
根据提示将仅有的尝试执行可执行文件$PATH
命名的guard
打电话时_guard
在提示符下将检查Gemfile's
存在并进行相应编译或者执行guard
可执行于$PATH
。这样namespace
受到保护,至少在我看来,提问者的意图仍然得到实现。
事实上,假设我们的 shell 函数_${1}()
和可执行文件${PATH}/${1}
是仅有的我们的 shell 可以通过两种方式解释对其中之一的调用$1
或者_${1}
然后使用command
现在,该功能已完全多余。尽管如此,我还是保留了它,因为我不想连续犯两次同样的错误。
如果询问者无法接受这一点,并且他/她宁愿取消该请求_
然后,以其当前的形式,编辑_underscore
据我了解,out 应该是提问者满足他/她的要求所需要做的全部事情。
除了这一更改之外,我还编辑了要使用的函数&&
和/或||
壳短路条件而不是原来的if/then
句法。这样一来command
语句仅被评估根本不如果Gemfile
不在$PATH
。此修改确实需要添加return $?
然而为了确保bundle
语句不在事件中运行Gemfile
不存在但是ruby $1
函数返回除0。
最后,我应该指出,该解决方案仅实现可移植的 shell 结构。换句话说,这应该在任何声称 POSIX 兼容性的 shell 中产生相同的结果。当然,如果我声称每个 POSIX 兼容系统都必须处理ruby bundle
指令,至少调用它的 shell 命令应该表现相同,无论调用 shell 是否是sh
或者dash
。上述也将按预期工作(假设至少有一半理智shopts
)同时bash
和zsh
。
答案3
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