创建一个接受参数的Bash别名?

我曾经使用CShell(),它允许您创建一个接受参数的别名。符号类似于

alias junk="mv \\!* ~/.Trash"

在Bash中,这似乎不起作用。鉴于Bash有许多有用的特性,我假设这个已经实现,但我想知道如何实现。

780023 次浏览

Bash别名不直接接受参数。您必须创建一个函数。

alias不接受参数,但可以像别名一样调用函数。例如:

myfunction() {#do things with parameters like $1 such asmv "$1" "$1.bak"cp "$2" "$1"}

myfunction old.conf new.conf #calls `myfunction`

顺便说一下,在.bashrc和其他文件中定义的Bash函数可以作为shell中的命令使用。例如,您可以像这样调用前面的函数

$ myfunction original.conf my.conf

细化上面的答案,您可以获得与别名一样的单行语法,这对于shell或. bashrc文件中的即席定义更方便:

bash$ myfunction() { mv "$1" "$1.bak" && cp -i "$2" "$1"; }
bash$ myfunction original.conf my.conf

不要忘记右括号前的分号。同样,对于实际问题:

csh% alias junk="mv \\!* ~/.Trash"
bash$ junk() { mv "$@" ~/.Trash/; }

或:

bash$ junk() { for item in "$@" ; do echo "Trashing: $item" ; mv "$item" ~/.Trash/; done; }

以下是我~/.bashrc中的三个函数示例,它们是基本上接受参数的别名:

#Utility required by all below functions.#https://stackoverflow.com/questions/369758/how-to-trim-whitespace-from-bash-variable#comment21953456_3232433alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"

.

:<<COMMENTAlias 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/2736496COMMENT#SRF_DESC: For "aliaf" command (with an 'f'). Must end with a newline.SRF_DESC="srf [path]: Recursive deletion, with y/n prompt\n"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.htmlthenecho "Required parameter missing. Cancelled"; returnfi
#Actual line-breaks required in order to expand the variable.#- https://stackoverflow.com/a/4296147/2736496read -r -p "About tosudo rm -rf \"$param\"Are you sure? [y/N] " responseresponse=${response,,}    # tolowerif [[ $response =~ ^(yes|y)$ ]]thensudo rm -rf "$param"elseecho "Cancelled."fi}

.

:<<COMMENTDelete item from history based on its line number. No prompt.
Short description: Stored in HX_DESC
Exampleshx 112hx 3
See:- https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-stringCOMMENT#HX_DESC: For "aliaf" command (with an 'f'). Must end with a newline.HX_DESC="hx [linenum]: Delete history item at line number\n"hx()  {history -d "$1"}

.

:<<COMMENTDeletes all lines from the history that match a search string, with aprompt. The history file is then reloaded into memory.
Short description: Stored in HXF_DESC
Exampleshxf "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-stringCOMMENT#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\n"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.htmlthenecho "Required parameter missing. Cancelled"; returnfi
read -r -p "About to delete all items from history that match \"$param\". Are you sure? [y/N] " responseresponse=${response,,}    # tolowerif [[ $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 bashelseecho "Cancelled."fi}

参考文献:


alias foo='__foo() { unset -f $0; echo "arg1 for foo=$1"; }; __foo()'

这很好,除非你避免一起使用函数。在这种情况下,你可以利用bash的巨大能力重定向文本:

alias bar='cat <<< '\''echo arg1 for bar=$1'\'' | source /dev/stdin'

它们都是相同的长度给或取几个字符。

最重要的是时间差,顶部是“函数方法”,底部是“重定向源”方法。为了证明这个理论,时间不言自明:

arg1 for foo=FOOVALUEreal 0m0.011s user 0m0.004s sys 0m0.008s  # <--time spent in fooreal 0m0.000s user 0m0.000s sys 0m0.000s  # <--time spent in bararg1 for bar=BARVALUEubuntu@localhost /usr/bin# time foo FOOVALUE; time bar BARVALUEarg1 for foo=FOOVALUEreal 0m0.010s user 0m0.004s sys 0m0.004sreal 0m0.000s user 0m0.000s sys 0m0.000sarg1 for bar=BARVALUEubuntu@localhost /usr/bin# time foo FOOVALUE; time bar BARVALUEarg1 for foo=FOOVALUEreal 0m0.011s user 0m0.000s sys 0m0.012sreal 0m0.000s user 0m0.000s sys 0m0.000sarg1 for bar=BARVALUEubuntu@localhost /usr/bin# time foo FOOVALUE; time bar BARVALUEarg1 for foo=FOOVALUEreal 0m0.012s user 0m0.004s sys 0m0.004sreal 0m0.000s user 0m0.000s sys 0m0.000sarg1 for bar=BARVALUEubuntu@localhost /usr/bin# time foo FOOVALUE; time bar BARVALUEarg1 for foo=FOOVALUEreal 0m0.010s user 0m0.008s sys 0m0.004sreal 0m0.000s user 0m0.000s sys 0m0.000sarg1 for bar=BARVALUE

这是大约200个结果中的底部部分,以随机间隔完成。似乎函数创建/销毁比重定向需要更多的时间。希望这将帮助未来的访问者解决这个问题(我不想把它保密)。

另一种解决方案是使用标记,这是我最近创建的一个工具,它允许您“书签”命令模板并轻松将光标放在命令占位符上:

命令行标记

我发现大多数时候,我使用shell函数,这样我就不必在命令行中一遍又一遍地编写常用命令。在这个用例中使用函数的问题是,在我的命令词汇表中添加新术语,并且必须记住真实命令中的函数参数引用的是什么。标记目标是消除这种精神负担。

如果你正在寻找一种通用的方法来将所有参数应用于函数,而不仅仅是一个或两个或其他硬编码量,你可以这样做:

#!/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;

因此,在上面的示例中,我将运行runjar时的所有参数传递给别名。

例如,如果我做了runjar hi there,它最终会运行java -jar myjar.jar hi there。如果我做了runjar one two three,它会运行java -jar myjar.jar one two three

我喜欢这个基于$@的解决方案,因为它可以处理任意数量的参数。

这个问题只是被问错了。你不做一个接受参数的别名,因为alias只是为已经存在的东西添加了第二个名称。OP想要的功能是创建新函数的function命令。你不需要为函数别名,因为函数已经有了名字。

我想你想要这样的东西:

function trash() { mv "$@" ~/.Trash; }

就是这样!您可以使用参数1美元、2美元、3美元等,或者直接用$@填充它们

函数几乎总是答案,因为它已经得到了充分的贡献,并得到了手册页中的这句话的证实:“对于几乎所有目的,别名都被shell函数所取代。

为了完整性,并且因为这可能很有用(稍微轻量级的语法),可以注意到,当参数跟随别名时,它们仍然可以使用(尽管这不会满足OP的要求)。这可能最容易用一个例子来演示:

alias ssh_disc='ssh -O stop'

允许我键入类似ssh_disc myhost的smth,它按预期扩展为:ssh -O stop myhost

这对于接受复杂参数的命令很有用(我的记忆不再是它使用的了…)

要获取参数,您应该使用函数!

但是,在创建别名时而不是在执行别名期间解释$@get并且转义$也不起作用。我如何解决这个问题?

你需要使用shell函数而不是别名来解决这个问题。您可以定义foo如下:

function foo() { /path/to/command "$@" ;}

foo() { /path/to/command "$@" ;}

最后,使用以下语法调用您的foo():

foo arg1 arg2 argN

确保将foo()添加到~/.bash_profile~/.zshrc文件。

在你的情况下,这将工作

function trash() { mv $@ ~/.Trash; }

太长别读:做这个

使用函数比使用别名将参数放在命令中间要容易得多,也更易读。

$ wrap_args() { echo "before $@ after"; }$ wrap_args 1 2 3before 1 2 3 after

如果你继续读下去,你会学到一些你不需要知道的关于shell参数处理的事情。知识是危险的。在黑暗面永远控制你的命运之前,得到你想要的结果。

澄清

bash别名接受参数,但仅限于结束

$ alias speak=echo$ speak hello worldhello world

通过alias将参数放入命令的中间确实是可能的,但它变得丑陋。

不要在家里尝试,孩子们!

如果你喜欢绕过限制,做别人说不可能的事情,这里有一个秘诀。如果你的头发变得疲惫不堪,你的脸最终被烟灰覆盖,不要责怪我疯狂的科学家风格。

解决方法是将alias仅在末尾接受的参数传递给包装器,包装器将它们插入中间,然后执行您的命令。

解决方案1

如果你真的反对使用函数本身,你可以使用:

$ alias wrap_args='f(){ echo before "$@" after;  unset -f f; }; f'$ wrap_args x y zbefore x y z after

如果只需要第一个参数,可以将$@替换为$1

解释1

这将创建一个临时函数f,它传递参数(注意f在最后被调用)。unset -f在执行别名时删除函数定义,这样它就不会在之后挂起。

解决方案2

您也可以使用子shell:

$ alias wrap_args='sh -c '\''echo before "$@" after'\'' _'

解释2

别名构建一个命令,如下所示:

sh -c 'echo before "$@" after' _

评论:

  • 占位符_是必需的,但它可以是任何东西。它被设置为sh$0,并且是必需的,这样用户给定的第一个参数就不会被消耗。演示:

    sh -c 'echo Consumed: "$0" Printing: "$@"' alcohol drunken babbleConsumed: alcohol Printing: drunken babble
  • The single-quotes inside single-quotes are required. Here's an example of it not working with double quotes:

    $ sh -c "echo Consumed: $0 Printing: $@" alcohol drunken babbleConsumed: -bash Printing:

    这里交互式shell的$0$@的值被替换为双引号之前,它被传递给sh。这是证明:

    echo "Consumed: $0 Printing: $@"Consumed: -bash Printing:

    单引号确保这些变量不会被交互式shell解释,并按字面意思传递给sh -c

    你可以使用双引号和\$@,但最好的做法是引用你的论点(因为它们可能包含空格),\"\$@\"看起来更丑,但可能会帮助你赢得混淆比赛,其中疲惫不堪的头发是进入的先决条件。

有合理的技术理由需要一个通用的解决方案来解决bash别名没有重新定位任意参数的机制的问题。一个原因是如果您希望执行的命令会受到执行函数导致的环境更改的不利影响。在所有其他情况下,都应该使用函数。

最近迫使我尝试解决这个问题的原因是我想创建一些缩写命令来打印变量和函数的定义。所以我为此编写了一些函数。然而,有些变量会(或可能会)被函数调用本身改变。其中包括:

FUNCNAMEBASH_SOURCEBASH_LINENOBASH_ARGCBASH_ARGV

我一直在使用(在函数中)的基本命令来打印变量dens。在set命令输出的表单中是:

sv () { set | grep --color=never -- "^$1=.*"; }

例如:

> V=voodoosv VV=voodoo

问题:这不会打印上面提到的变量的定义,因为它们在当前背景中,例如,如果在交互式shell提示符中(或在任何函数调用中没有),FUNCNAME没有定义。但是我的函数告诉我错误的信息:

> sv FUNCNAMEFUNCNAME=([0]="sv")

我想出的一个解决方案已经在这个主题的其他帖子中被其他人提到。对于这个特定的命令来打印变量Defns.,并且只需要一个参数,我这样做了:

alias asv='(grep -- "^$(cat -)=.*" <(set)) <<<'

给出正确的输出(无)和结果状态(false):

> asv FUNCNAME> echo $?1

然而,我仍然觉得有必要找到一个适用于任意数量参数的解决方案。

将任意参数传递给Bash别名命令的一般解决方案:

# (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 cmdkill -SIGUSR1 $$;             # this causes cmd to be runtrap SIGUSR1;                 # unset the trap for SIGUSR1unset CMD_ARGV;               # clean up env...unset f;                      # incl. this function!};f'  # Finally, exec f, which will receive the args following "ac2".

例如:

> . alias-arg.sh> ac2 one twocmd(two one)>> # Check to see that command run via trap affects this environment:> asv CMD_DONECMD_DONE=true

这个解决方案的一个好处是,在组合被困命令时,用于处理命令的位置参数(参数)的所有特殊技巧都可以工作。唯一的区别是必须使用数组语法。

例如,

如果你想 "$@", 使用“${CMD_ARGV[@]}”。

如果你想 "$#", 使用“${#CMD_ARGV[@]}”。

等等。

函数和别名都可以使用参数,就像这里显示的其他人一样。此外,我想指出其他几个方面:

1.函数在自己的范围内运行,别名共享范围

在需要隐藏或暴露某些内容的情况下,了解这种差异可能会很有用。它还表明函数是更好的封装选择。

function tfunc(){GlobalFromFunc="Global From Func" # Function set global variable by defaultlocal FromFunc="onetwothree from func" # Set a local variable
}
alias talias='local LocalFromAlias="Local from Alias";  GlobalFromAlias="Global From Alias" # Cant hide a variable with local here '# Test variables set by tfunctfunc # call tfuncecho $GlobalFromFunc # This is visibleecho $LocalFromFunc # This is not visible# Test variables set by talias# call taliastaliasecho $GlobalFromAlias # This is invisibleecho $LocalFromAlias # This variable is unset and unusable

输出:

bash-3.2$     # Test variables set by tfuncbash-3.2$     tfunc # call tfuncbash-3.2$     echo $GlobalFromFunc # This is visibleGlobal From Funcbash-3.2$     echo $LocalFromFunc # This is not visible
bash-3.2$     # Test variables set by taliasbash-3.2$     # call taliasbash-3.2$     taliasbash: local: can only be used in a functionbash-3.2$     echo $GlobalFromAlias # This is invisibleGlobal From Aliasbash-3.2$ echo $LocalFromAlias # This variable is unset and unusable

2.包装脚本是更好的选择

在我身上发生过几次,当通过#0登录或涉及切换用户名或多用户环境时,找不到别名或函数。采购点文件有一些提示和技巧,或者这个有趣的别名:alias sd='sudo '让这个后续的别名alias install='sd apt-get install'按预期工作(注意sd='sudo '中的额外空间)。然而,在这种情况下,包装脚本比函数或别名工作得更好。包装脚本的主要优点是它在预期路径(即 /usr/loca/bin/)下是可见/可执行的,在预期路径下,作为函数/别名需要在可用之前被采购。例如,您将一个函数放在bash的~/.bash_profile或~/. bashrc中,但后来切换到另一个shell(即zsh),然后该函数不再可见。因此,当您有疑问时,包装脚本始终是最可靠和可移植的解决方案。

你所要做的就是在别名中创建一个函数:

$ alias mkcd='_mkcd(){ mkdir "$1"; cd "$1";}; _mkcd'^        *      ^  ^     ^  ^         ^

必须在“1美元”周围加上双引号,因为单引号不起作用。这是因为在标有箭头的地方的引号冲突会混淆系统。此外,该函数需要在标有星号的地方留出空格。

下面是一个例子:

alias gcommit='function _f() { git add -A; git commit -m "$1"; } ; _f'

非常重要:

  1. {之后和}之前有一个空格。
  2. 每个命令后面都有一个;。如果您在最后一个命令之后忘记了这一点,您将看到>提示符!
  3. 参数用引号括起来为"$1"

一旦我做了一些有趣的项目,我仍然使用它。当我通过cp命令复制文件时,它会显示一些动画,因为cp没有显示任何内容,这有点令人沮丧。所以我做了这个别名

alias cp="~/SCR/spiner cp"

这是Spiner的剧本

#!/bin/bash
#Set timerT=$(date +%s)
#Add some color. ~/SCR/color
#Animation spritessprite=( "(* )  ( *)" " (* )( *) " " ( *)(* ) " "( *)  (* )" "(* )  ( *)" )
#Print empty line and hide cursorprintf "\n${COF}"
#Exit functionfunction bye { printf "${CON}"; [ -e /proc/$pid ] && kill -9 $pid; exit; }; trap bye INT
#Run our command and get its pid"$@" & pid=$!
#Waiting animationi=0; while [ -e /proc/$pid ]; do sleep 0.1
printf "\r${GRN}Please wait... ${YLW}${sprite[$i]}${DEF}"((i++)); [[ $i = ${#sprite[@]} ]] && i=0
done
#Print time and exitT=$(($(date +%s)-$T))printf "\n\nTime taken: $(date -u -d @${T} +'%T')\n"
bye

它看起来像这样

在此处输入图片描述

循环动画)

在此处输入图片描述

这是上面提到的颜色脚本的链接。新的动画周期)

在此处输入图片描述

Bash别名绝对接受参数。我刚刚添加了一个别名来创建一个新的反应应用程序,它接受应用程序名称作为参数。这是我的过程:

打开bash_profile进行编辑

nano /.bash_profile

添加您的别名,每行一个:

alias gita='git add .'alias gitc='git commit -m "$@"'alias gitpom='git push origin master'alias creact='npx create-react-app "$@"'

注意:“$@”接受像“creact my-new-app”这样传入的参数

保存并退出纳米编辑器

ctrl+o到写入(按回车键); ctrl+x到退出

告诉终端使用新的别名。bash_profile

source /.bash_profile

够了!你们现在可以用你们的新化名了

尊重所有那些说你不能在别名中间插入参数的人,我刚刚测试了它,发现它确实有效。

alias mycommand = "python3 "$1" script.py --folderoutput RESULTS/"

然后,当我运行我的命令fobar时,它的工作方式就像我用手写输入命令一样。

正如其他人已经指出的那样,使用函数应被视为最佳实践。

但是,这里有另一种方法,利用xargs

alias junk="xargs -I "{}" -- mv "{}" "~/.Trash" <<< "

请注意,这对流的重定向有副作用。

要具体回答有关创建别名以将文件移动到垃圾箱文件夹而不是删除它们的问题:

alias rm="mv "$1" -t ~/.Trash/"

当然你必须先创建dir~/. Trash。

然后只需给出以下命令:

$rm <filename>$rm <dirname>

这是另一种使用read的方法。我使用它通过其名称片段对文件进行暴力搜索,忽略“权限被拒绝”消息。

alias loc0='( IFS= read -r x; find . -iname "*" -print 2>/dev/null | grep $x;) <<<'

举个简单的例子:

$ ( IFS= read -r x; echo "1 $x 2 ";) <<< "a b"1 a b 2

请注意,这会将参数作为字符串转换为变量。可以在引号中使用多个参数,空格分隔:

$ ( read -r x0 x1; echo "1 ${x0} 2 ${x1} 3 ";) <<< "a b"1 a 2 b 3

我只是发布我的(希望,好的)解决方案(对于未来的读者,最重要的是;编辑)。所以-请编辑和改进/删除这篇文章中的任何内容。

在终端:

$ alias <name_of_your_alias>_$argname="<command> $argname"

并使用它(注意'_'之后的空格:

$<name_of_your_alias>_ $argname

例如,cat的别名称为hello.txt的文件:

  • (别名为CAT_FILE_
  • $f(是$argname,在本例中是一个文件)
$ alias CAT_FILE_$f="cat $f"
$ echo " " >> hello.txt$ echo "hello there!" >> hello.txt$ echo " " >> hello.txt$ cat hello.txt
hello there!

测试(注意“_”后的空格):

CAT_FILE_ hello.txt

语法:

alias shortName="your custom command here"

示例:

alias tlogs='_t_logs() { tail -f ../path/$1/to/project/logs.txt ;}; _t_logs'

使用子命令的解决方案:

d () {if [ $# -eq 0 ] ; thendockerreturn 0fiCMD=$1shift
case $CMD inp)docker ps --all $@;;r)docker run --interactive --tty $@;;rma)docker container prunedocker image prune --filter "dangling=true";;*)docker $CMD $@;;esacreturn $?}

使用:

$ d r my_image ...

称为:

docker run --interactive --tty my_image ...
alias junk="delay-arguments mv _ ~/.Trash"

delay-arguments脚本:

#!/bin/bash
# Example:# > delay-arguments echo 1 _ 3 4 2# 1 2 3 4# > delay-arguments echo "| o n e" _ "| t h r e e" "| f o u r" "| t w o"# | o n e | t w o | t h r e e | f o u r
RAW_ARGS=("$@")
ARGS=()
ARG_DELAY_MARKER="_"SKIPPED_ARGS=0SKIPPED_ARG_NUM=0RAW_ARGS_COUNT="$#"
for ARG in "$@"; do#echo $ARGif [[ "$ARG" == "$ARG_DELAY_MARKER" ]]; thenSKIPPED_ARGS=$((SKIPPED_ARGS+1))fidone
for ((I=0; I<$RAW_ARGS_COUNT-$SKIPPED_ARGS; I++)); doARG="${RAW_ARGS[$I]}"if [[ "$ARG" == "$ARG_DELAY_MARKER" ]]; thenMOVE_SOURCE_ARG_NUM=$(($RAW_ARGS_COUNT-$SKIPPED_ARGS+$SKIPPED_ARG_NUM))MOVING_ARG="${RAW_ARGS[$MOVE_SOURCE_ARG_NUM]}"if [[ "$MOVING_ARG" == "$ARG_DELAY_MARKER" ]]; thenecho "Error: Not enough arguments!"exit 1;fi#echo "Moving arg: $MOVING_ARG"ARGS+=("$MOVING_ARG")SKIPPED_ARG_NUM=$(($SKIPPED_ARG_NUM+1))elseARGS+=("$ARG")fidone
#for ARG in "${ARGS[@]}"; do#echo "ARGN: $ARG"#done
#echo "RAW_ARGS_COUNT: $RAW_ARGS_COUNT"#echo "SKIPPED_ARGS: $SKIPPED_ARGS"
#echo "${ARGS[@]}"QUOTED_ARGS=$(printf ' %q' "${ARGS[@]}")eval "${QUOTED_ARGS[@]}"