Make a Bash alias that takes a parameter?
我曾经使用CShell(CSH),它允许您创建一个接受参数的别名。符号是这样的
1 | alias junk="mv \\!* ~/.Trash" |
在bash中,这似乎不起作用。考虑到bash有许多有用的特性,我假设这个特性已经实现,但是我想知道如何实现。
bash别名不直接接受参数。您必须创建一个函数。
1 2 3 4 5 6 7 8 | myfunction() { #do things with parameters like $1 such as mv"$1""$1.bak" cp"$2""$1" } myfunction old.conf new.conf #calls `myfunction` |
顺便说一下,在您的
1 | $ myfunction original.conf my.conf |
优化上述答案后,您可以像处理别名那样得到一行语法,这对于shell或.bashrc文件中的即席定义更为方便:
1 2 3 | bash$ myfunction() { mv"$1""$1.bak" && cp -i"$2""$1"; } bash$ myfunction original.conf my.conf |
不要忘记右括号前的分号。同样,对于实际问题:
1 2 3 | csh% alias junk="mv \\!* ~/.Trash" bash$ junk() { mv"$@" ~/.Trash/; } |
或:
1 | bash$ junk() { for item in"$@" ; do echo"Trashing: $item" ; mv"$item" ~/.Trash/; done; } |
这个问题只是问错了。您不需要使用参数来创建别名,因为
我想你想要这样的东西:
1 | function trash() { mv"$@" ~/.Trash; } |
就是这样!您可以使用参数$1、$2、$3等,或者只是将它们全部填充。$@
tl;dr:改为这样做
使用函数比使用别名将参数放在命令中间要容易得多,可读性也更高。
1 2 3 | $ wrap_args() { echo"before $@ after"; } $ wrap_args 1 2 3 before 1 2 3 after |
如果继续阅读,您将学到一些不需要了解的关于shell参数处理的知识。知识是危险的。只要得到你想要的结果,在黑暗面永远控制你的命运之前。
澄清1 2 3 | $ alias speak=echo $ speak hello world hello world |
通过
如果你喜欢规避限制,做别人说的不可能的事,那么这就是秘诀。别怪我,如果你的头发被吹得花枝招展,你的脸上也沾满了科学家的烟灰。
解决方法是将
如果您真的反对使用函数本身,您可以使用:
1 2 3 | $ alias wrap_args='f(){ echo before"$@" after; unset -f f; }; f' $ wrap_args x y z before x y z after |
如果只需要第一个参数,可以用
说明1
这将创建一个临时函数
还可以使用子外壳:
1 | $ alias wrap_args='sh -c '\''echo before"$@" after'\'' _' |
说明2
别名生成如下命令:
1 | sh -c 'echo before"$@" after' _ |
评论:
占位符
_ 是必需的,但它可以是任何内容。它被设置为sh 的$0 ,并且是必需的,这样用户给定的第一个参数就不会被使用。论证:1
2sh -c 'echo Consumed:"$0" Printing:"$@"' alcohol drunken babble
Consumed: alcohol Printing: drunken babble单引号内的单引号是必需的。下面是一个不使用双引号的示例:
1
2$ sh -c"echo Consumed: $0 Printing: $@" alcohol drunken babble
Consumed: -bash Printing:在这里,交互shell的
$0 和$@ 的值在传递给sh 之前被替换为双引号。以下是证据:1
2echo"Consumed: $0 Printing: $@"
Consumed: -bash Printing:单引号确保这些变量不会被交互shell解释,并从字面上传递给
sh -c 。你可以使用双引号和
\$@ ,但最好的做法是引用你的论点(因为它们可能包含空格),\"\$@\" 看起来更丑,但可能有助于你赢得一场模糊比赛,在比赛中,头发是进入的先决条件。
另一种解决方案是使用marker,这是我最近创建的一种工具,它允许您"为命令模板加书签",并轻松地将光标放在命令位置固定器上:
我发现大多数时候,我使用的是shell函数,所以我不必在命令行中反复编写常用的命令。在这个用例中使用函数的问题是,在我的命令词汇表中添加新的术语,并且必须记住实际命令中的函数参数是指什么。标记的目标是消除这种心理负担。
下面是我在
1 2 3 | #Utility required by all below functions. #https://stackoverflow.com/questions/369758/how-to-trim-whitespace-from-bash-variable#comment21953456_3232433 alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'" |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | :<<COMMENT Alias function for recursive deletion, with are-you-sure prompt. Example: srf /home/myusername/django_files/rest_tutorial/rest_venv/ Parameter is required, and must be at least one non-whitespace character. Short description: Stored in SRF_DESC With the following setting, this is *not* added to the history: export HISTIGNORE="*rm -r*:srf *" - https://superuser.com/questions/232885/can-you-share-wisdom-on-using-histignore-in-bash See: - y/n prompt: https://stackoverflow.com/a/3232082/2736496 - Alias w/param: https://stackoverflow.com/a/7131683/2736496 COMMENT #SRF_DESC: For"aliaf" command (with an 'f'). Must end with a newline. SRF_DESC="srf [path]: Recursive deletion, with y/n prompt " srf() { #Exit if no parameter is provided (if it's the empty string) param=$(echo"$1" | trim) echo"$param" if [ -z"$param" ] #http://tldp.org/LDP/abs/html/comparison-ops.html then echo"Required parameter missing. Cancelled"; return fi #Actual line-breaks required in order to expand the variable. #- https://stackoverflow.com/a/4296147/2736496 read -r -p"About to sudo rm -rf "$param" Are you sure? [y/N]" response response=${response,,} # tolower if [[ $response =~ ^(yes|y)$ ]] then sudo rm -rf"$param" else echo"Cancelled." fi } |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | :<<COMMENT Delete item from history based on its line number. No prompt. Short description: Stored in HX_DESC Examples hx 112 hx 3 See: - https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-string COMMENT #HX_DESC: For"aliaf" command (with an 'f'). Must end with a newline. HX_DESC="hx [linenum]: Delete history item at line number " hx() { history -d"$1" } |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | :<<COMMENT Deletes all lines from the history that match a search string, with a prompt. The history file is then reloaded into memory. Short description: Stored in HXF_DESC Examples hxf"rm -rf" hxf ^source Parameter is required, and must be at least one non-whitespace character. With the following setting, this is *not* added to the history: export HISTIGNORE="*hxf *" - https://superuser.com/questions/232885/can-you-share-wisdom-on-using-histignore-in-bash See: - https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-string COMMENT #HXF_DESC: For"aliaf" command (with an 'f'). Must end with a newline. HXF_DESC="hxf [searchterm]: Delete all history items matching search term, with y/n prompt " hxf() { #Exit if no parameter is provided (if it's the empty string) param=$(echo"$1" | trim) echo"$param" if [ -z"$param" ] #http://tldp.org/LDP/abs/html/comparison-ops.html then echo"Required parameter missing. Cancelled"; return fi read -r -p"About to delete all items from history that match "$param". Are you sure? [y/N]" response response=${response,,} # tolower if [[ $response =~ ^(yes|y)$ ]] then #Delete all matched items from the file, and duplicate it to a temp #location. grep -v"$param""$HISTFILE"> /tmp/history #Clear all items in the current sessions history (in memory). This #empties out $HISTFILE. history -c #Overwrite the actual history file with the temp one. mv /tmp/history"$HISTFILE" #Now reload it. history -r"$HISTFILE" #Alternative: exec bash else echo"Cancelled." fi } |
参考文献:
- 从字符串中删除空白:如何从bash变量中删除空白?
- 实际换行符:https://stackoverflow.com/a/4296147/2736496
- 别名w/param:https://stackoverflow.com/a/7131683/2736496(此问题的另一个答案)
- histignore:https://superuser.com/questions/232885/can-you-share-withom-on-using-histignore-in-bash
- 是/否提示:https://stackoverflow.com/a/3232082/2736496
- 从历史记录中删除所有匹配项:https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-string
- 字符串是否为空:http://tldp.org/ldp/abs/html/comparison-ops.html
注意:如果这个想法不明显,那么除了别名以外,其他任何东西都使用别名是一个坏主意,第一个别名是"别名中的函数",第二个别名是"难以读取的重定向/源代码"。另外,还有一些缺陷(我认为是显而易见的,但只是为了防止你感到困惑:我不是指它们被实际使用…到处都是!)
…………………
我以前回答过这个问题,过去总是这样:
1 | alias foo='__foo() { unset -f $0; echo"arg1 for foo=$1"; }; __foo()' |
这是很好的,除非您避免同时使用函数。在这种情况下,您可以利用bash的巨大能力来重定向文本:
1 | alias bar='cat <<< '\''echo arg1 for bar=$1'\'' | source /dev/stdin' |
它们的长度大致相同,可以用几个字符表示。
真正的推动因素是时间差,顶部是"函数方法",底部是"重定向源"方法。为了证明这一理论,时机本身就说明了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | arg1 for foo=FOOVALUE real 0m0.011s user 0m0.004s sys 0m0.008s # <--time spent in foo real 0m0.000s user 0m0.000s sys 0m0.000s # <--time spent in bar arg1 for bar=BARVALUE ubuntu@localhost /usr/bin# time foo FOOVALUE; time bar BARVALUE arg1 for foo=FOOVALUE real 0m0.010s user 0m0.004s sys 0m0.004s real 0m0.000s user 0m0.000s sys 0m0.000s arg1 for bar=BARVALUE ubuntu@localhost /usr/bin# time foo FOOVALUE; time bar BARVALUE arg1 for foo=FOOVALUE real 0m0.011s user 0m0.000s sys 0m0.012s real 0m0.000s user 0m0.000s sys 0m0.000s arg1 for bar=BARVALUE ubuntu@localhost /usr/bin# time foo FOOVALUE; time bar BARVALUE arg1 for foo=FOOVALUE real 0m0.012s user 0m0.004s sys 0m0.004s real 0m0.000s user 0m0.000s sys 0m0.000s arg1 for bar=BARVALUE ubuntu@localhost /usr/bin# time foo FOOVALUE; time bar BARVALUE arg1 for foo=FOOVALUE real 0m0.010s user 0m0.008s sys 0m0.004s real 0m0.000s user 0m0.000s sys 0m0.000s arg1 for bar=BARVALUE |
这是大约200个结果的底部部分,以随机间隔完成。似乎函数创建/销毁比重定向花费的时间更多。希望这将有助于未来的访问者对这个问题(不想让自己知道)。
如果您正在寻找一种将所有参数应用于函数的通用方法,而不仅仅是一个或两个或一些其他硬编码量,那么您可以这样做:
1 2 3 4 5 6 7 8 | #!/usr/bin/env bash # you would want to `source` this file, maybe in your .bash_profile? function runjar_fn(){ java -jar myjar.jar"$@"; } alias runjar=runjar_fn; |
所以在上面的示例中,我将所有参数从运行
例如,如果我执行
我喜欢这个基于
对于bash别名没有采用重新定位任意参数的机制的问题,有合理的技术原因需要一个通用的解决方案。一个原因是,如果您希望执行的命令会受到执行函数导致的环境更改的不利影响。在所有其他情况下,应该使用函数。
最近迫使我尝试解决这个问题的是,我想创建一些用于打印变量和函数定义的缩写命令。为此我编写了一些函数。但是,有一些变量是(或可能)由函数调用本身更改的。其中有:
函数名巴什源巴什利林诺巴什尔加尔克巴什亚尔夫
我(在函数中)用来打印变量defn的基本命令。在set命令的表单输出中是:
1 | sv () { set | grep --color=never --"^$1=.*"; } |
例如。:
1 2 3 | > V=voodoo sv V V=voodoo |
问题:这不会像在当前上下文中那样打印上述变量的定义,例如,如果在交互式shell提示(或不在任何函数调用中)中没有定义funcname。但是我的函数告诉我错误的信息:
1 2 | > sv FUNCNAME FUNCNAME=([0]="sv") |
我提出的一个解决方案已经被其他人在这个主题的其他文章中提到。对于这个打印变量defns.的特定命令,它只需要一个参数,我这样做了:
1 | alias asv='(grep --"^$(cat -)=.*" <(set)) <<<' |
给出正确的输出(无)和结果状态(假):
1 2 3 | > asv FUNCNAME > echo $? 1 |
然而,我仍然觉得有必要找到一个适用于任意数量论点的解决方案。
向bash别名命令传递任意参数的一般解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # (I put this code in a file"alias-arg.sh"): # cmd [arg1 ...] – an experimental command that optionally takes args, # which are printed as"cmd(arg1 ...)" # # Also sets global variable"CMD_DONE" to"true". # cmd () { echo"cmd($@)"; declare -g CMD_DONE=true; } # Now set up an alias"ac2" that passes to cmd two arguments placed # after the alias, but passes them to cmd with their order reversed: # # ac2 cmd_arg2 cmd_arg1 – calls"cmd" as:"cmd cmd_arg1 cmd_arg2" # alias ac2=' # Set up cmd to be execed after f() finishes: # trap '\''cmd"${CMD_ARGV[1]}""${CMD_ARGV[0]}"'\'' SIGUSR1; # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # (^This is the actually execed command^) # # f [arg0 arg1 ...] – acquires args and sets up trap to run cmd: f () { declare -ag CMD_ARGV=("$@"); # array to give args to cmd kill -SIGUSR1 $$; # this causes cmd to be run trap SIGUSR1; # unset the trap for SIGUSR1 unset CMD_ARGV; # clean up env... unset f; # incl. this function! }; f' # Finally, exec f, which will receive the args following"ac2". |
例如。:
1 2 3 4 5 6 7 | > . alias-arg.sh > ac2 one two cmd(two one) > > # Check to see that command run via trap affects this environment: > asv CMD_DONE CMD_DONE=true |
这个解决方案的一个好处是,在编写被捕获的命令时,用于处理命令的位置参数(参数)的所有特殊技巧都将起作用。唯一的区别是必须使用数组语法。
例如。,
如果需要"$@",请使用"$cmd_argv[@]"。
如果您想要"$",请使用"$cmd_argv[@]"。
等。
对于获取参数,应该使用函数!
但是,在创建别名而不是在执行别名和转义$的过程中,$@get也不起作用。我该如何解决这个问题?
您需要使用shell函数而不是别名来解决这个问题。您可以定义foo如下:
1 | function foo() { /path/to/command"$@" ;} |
或
1 | foo() { /path/to/command"$@" ;} |
最后,使用以下语法调用foo():
1 | foo arg1 arg2 argN |
确保将foo()添加到
在你的情况下,这是可行的
1 | function trash() { mv $@ ~/.Trash; } |
虽然函数通常是一个很好的选择,就像其他人在这里所说的。但我想指出的是,有些情况下函数不起作用,而别名起作用,或者其他情况下,包装脚本是更好的选择。
别名有效,但函数无效,仍可以传递参数。
例如:我想创建一个快捷方式来激活
1 2 3 4 5 6 7 | function conda_activate(){ export PATH="$PATH:/path/to/conda" envname=$1 if ["x$envname" -ne"x" ]; then source activate"$envname" fi } |
这不符合预期。如果一个运行
工作解决方案如下:
1 2 3 4 5 6 7 | function conda_activate(){ export PATH="$PATH:/path/to/conda" # do some other things } alias conda_env='conda_activate; source activate' # usage example conda_env myenv |
包装脚本是更好的选择
我多次遇到这样的情况:通过
您所要做的就是在别名中创建一个函数:
1 | $alias mkcd='function _mkcd(){mkdir"$1"; cd"$1"}; _mkcd' |
您必须在"$1"周围加上双引号,因为单引号不起作用。
实际上,函数几乎总是一个答案,正如在手册页中引用并确认的那样:"几乎每一个目的,别名都被shell函数所取代。"
为了完整性,并且因为这是有用的(稍微轻一些的语法),可以注意到,当参数跟在别名后面时,它们仍然可以使用(尽管这不能满足OP的要求)。这可能最容易用一个例子来演示:
1 | alias ssh_disc='ssh -O stop' |
允许我键入smth,如
这对于接受复杂参数的命令很有用(我的内存不再是它所用的了…)