生成一个采用参数的bash别名?

Make a Bash alias that takes a parameter?

我曾经使用CShell(CSH),它允许您创建一个接受参数的别名。符号是这样的

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

在bash中,这似乎不起作用。考虑到bash有许多有用的特性,我假设这个特性已经实现,但是我想知道如何实现。


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

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

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`

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

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; }


这个问题只是问错了。您不需要使用参数来创建别名,因为alias只是为已经存在的对象添加了第二个名称。OP想要的功能是创建新功能的function命令。您不需要为函数加别名,因为函数已经有了名称。

我想你想要这样的东西:

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参数处理的知识。知识是危险的。只要得到你想要的结果,在黑暗面永远控制你的命运之前。

澄清

bash别名确实接受参数,但仅在结尾:

1
2
3
$ alias speak=echo
$ speak hello world
hello world

通过alias将论点置于命令的中间确实是可能的,但它变得很难看。

别在家里试这个,孩子们!

如果你喜欢规避限制,做别人说的不可能的事,那么这就是秘诀。别怪我,如果你的头发被吹得花枝招展,你的脸上也沾满了科学家的烟灰。

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

解决方案1

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

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

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

解决方案2

还可以使用子外壳:

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

说明2

别名生成如下命令:

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

评论:

  • 占位符_是必需的,但它可以是任何内容。它被设置为sh$0,并且是必需的,这样用户给定的第一个参数就不会被使用。论证:

    1
    2
    sh -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
    2
    echo"Consumed: $0 Printing: $@"
    Consumed: -bash Printing:

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

    你可以使用双引号和\$@,但最好的做法是引用你的论点(因为它们可能包含空格),\"\$@\"看起来更丑,但可能有助于你赢得一场模糊比赛,在比赛中,头发是进入的先决条件。


另一种解决方案是使用marker,这是我最近创建的一种工具,它允许您"为命令模板加书签",并轻松地将光标放在命令位置固定器上:

commandline marker

我发现大多数时候,我使用的是shell函数,所以我不必在命令行中反复编写常用的命令。在这个用例中使用函数的问题是,在我的命令词汇表中添加新的术语,并且必须记住实际命令中的函数参数是指什么。标记的目标是消除这种心理负担。


下面是我在~/.bashrc中使用的三个函数示例,它们本质上是接受参数的别名:

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;

所以在上面的示例中,我将所有参数从运行runjar时传递到别名。

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

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


对于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()添加到~/.bash_profile~/.zshrc文件中。

在你的情况下,这是可行的

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


虽然函数通常是一个很好的选择,就像其他人在这里所说的。但我想指出的是,有些情况下函数不起作用,而别名起作用,或者其他情况下,包装脚本是更好的选择。

别名有效,但函数无效,仍可以传递参数。

例如:我想创建一个快捷方式来激活conda,并在一个命令中源代码一个conda环境。这样做是很有诱惑力的:

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
}

这不符合预期。如果一个运行conda_activate myenvsource命令执行寻源,但立即退出,运行的shell没有环境。

工作解决方案如下:

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

包装脚本是更好的选择

我多次遇到这样的情况:通过ssh登录或涉及用户名切换或多用户环境时,无法找到别名或函数。有一些关于寻找点文件的技巧和技巧,例如,这个有趣的:alias sd='sudo '让这个alias install='sd apt-get install'像预期的那样工作。注意sd='sudo '中的额外空间。但是,当您有疑问时,包装脚本总是最可靠和可移植的解决方案。


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

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

您必须在"$1"周围加上双引号,因为单引号不起作用。


实际上,函数几乎总是一个答案,正如在手册页中引用并确认的那样:"几乎每一个目的,别名都被shell函数所取代。"

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

1
alias ssh_disc='ssh -O stop'

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

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