首页 文章

制作一个带参数的Bash别名?

提问于
浏览
949

我曾经使用CShell(csh),它允许你创建一个带参数的别名 . 符号是这样的

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

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

12 回答

  • 7

    Bash别名不直接接受参数 . 您将不得不创建一个函数和别名 .

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

    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中的命令使用 . 因此,例如,您可以像这样调用早期的函数

    $ myfunction original.conf my.conf
    
  • 28

    改进上面的答案,您可以像使用别名一样获得单行语法,这对于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; }
    
  • 1625

    这个问题简直被问到了错误 . 您没有创建带参数的别名,因为 alias 只为已存在的内容添加了第二个名称 . OP想要的功能是用于创建新功能的 function 命令 . 您不需要为函数设置别名,因为函数已有名称 .

    我想你想要这样的东西:

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

    而已!您可以使用参数$ 1,$ 2,$ 3等,或者只需将它们全部填入$ @

  • 153

    TL; DR:改为做

    使用函数比使用别名将参数放在命令中间更容易和更易读 .

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

    如果您继续阅读,您将学习关于shell参数处理不需要了解的内容 . 知识很危险 . 只要得到你想要的结果,在黑暗面永远控制你的命运之前 .

    澄清

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

    $ alias speak=echo
    $ speak hello world
    hello world
    

    通过 alias 将参数置于命令的中间确实是可能的,但它变得丑陋 .

    不要在家里尝试这个,小子!

    如果您喜欢规避限制并做其他人认为不可能的事情,那么这就是配方 . 如果你的头发被弄得疲惫不堪,你的脸最终被烟灰色的科学家风格所覆盖,就不要怪我 .

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

    解决方案1

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

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

    如果您只想要第一个参数,可以用 $1 替换 $@ .

    Explanation 1

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

    解决方案2

    您还可以使用子shell:

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

    Explanation 2

    别名构建如下命令:

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

    评论:

    • 占位符 _ 是必需的,但它可以是任何内容 . 它被设置为 sh$0 ,并且是必需的,以便第一个用户给定的参数不被消耗 . 示范:
    sh -c 'echo Consumed: "$0" Printing: "$@"' alcohol drunken babble
    Consumed: alcohol Printing: drunken babble
    
    • 单引号内的单引号是必需的 . 这是一个不使用双引号的例子:
    $ sh -c "echo Consumed: $0 Printing: $@" alcohol drunken babble
    Consumed: -bash Printing:
    

    这里交互式shell的 $0$@ 的值在传递给 sh 之前被替换为双引号 . 这是证据:

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

    单引号确保交互式shell不解释这些变量,并按字面顺序传递给 sh -c .

    您可以使用双引号和 \$@ ,但最佳做法是引用您的参数(因为它们可能包含空格),并且 \"\$@\" 看起来更加丑陋,但可能会帮助您赢得混淆比赛,其中疲惫的头发是进入的先决条件 .

  • 88

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

    commandline marker

    我发现大部分时间,我都在使用shell函数,因此我不必在命令行中反复编写常用命令 . 在这个用例中使用函数的问题是在命令词汇表中添加新术语,并且必须记住在real-command中引用的函数参数 . 标记的目标是消除这种心理负担 .

  • 2

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

    #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'"
    

    .

    :<<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\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.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
    }
    

    .

    :<<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\n"
    hx()  {
        history -d "$1"
    }
    

    .

    :<<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\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.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
    }
    

    参考文献:

  • 66

    注意:如果想法不是't obvious, it is a bad idea to use aliases for anything but aliases, the first one being the '函数,别名' and the second one being the '难以阅读重定向/源” . 此外,还有一些缺陷(我认为这是显而易见的,但万一你会感到困惑:我并不是说它们实际上被用到了......任何地方!)

    .................................................. .................................................. ............................................

    我之前已经回答了这个问题,过去一直如此:

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

    这是好的和好的,除非你一直避免使用功能 . 在这种情况下,您可以利用bash的重定向文本的巨大能力:

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

    它们的长度大约相同,或者取几个字符 .

    真正的踢球者是时差,顶部是'function method',底部是'redirect-source'方法 . 为证明这一理论,时机不言而喻:

    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个结果的底部,以随机间隔完成 . 似乎函数创建/销毁比重定向花费更多时间 . 希望这将有助于未来的访问者提出这个问题(不想保留给自己) .

  • 5

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

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

    我喜欢这个基于 $@ 的解决方案,因为它适用于任意数量的参数 .

  • 1

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

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

    您需要使用shell函数而不是别名来解决此问题 . 您可以按如下方式定义foo:

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

    要么

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

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

    foo arg1 arg2 argN
    

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

    在你的情况下,这将工作

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

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

    最近迫使我尝试解决这个问题的方法是,我想创建一些缩写命令来打印变量和函数的定义 . 所以我为此目的写了一些函数 . 但是,函数调用本身会(或可能)更改某些变量 . 其中包括:

    FUNCNAME BASH_SOURCE BASH_LINENO BASH_ARGC BASH_ARGV

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

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

    例如 . :

    > V=voodoo
    sv V
    V=voodoo
    

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

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

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

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

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

    > asv FUNCNAME
    > echo $?
    1
    

    但是,我仍然觉得有必要找到适用于任意数量参数的解决方案 .

    A General Solution To Passing Arbitrary Arguments To A Bash Aliased Command:

    # (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".
    

    例如 . :

    > . 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 [@]}” .

    等等 .

  • 2

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

    alias works, function doesn't, and one still can pass parameters.

    例如:我想创建一个快捷方式来激活 conda 并在一个命令中获取conda环境 . 很有可能这样做:

    function conda_activate(){
        export PATH="$PATH:/path/to/conda"
        envname=$1
        if [ "x$envname" -ne "x" ]; then
           source activate "$envname"
        fi
    }
    

    这不按预期工作 . 如果一个人跑了 conda_activate myenv . source 命令执行源,但立即退出,并且正在运行的shell没有环境 .

    工作解决方案如下所示:

    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
    

    wrapper script is a better choice

    我多次遇到过在通过 ssh 进行日志记录或涉及切换用户名或多用户环境时无法找到别名或函数的情况 . 有点源文件的提示和技巧,例如,这个有趣的一点: alias sd='sudo ' 让这个 alias install='sd apt-get install' 按预期工作 . 注意 sd='sudo ' 中的额外空格 . 但是当你有疑问时,包装脚本总是最可靠的便携式方案 .

  • 2

    功能确实几乎总是答案,因为已经充分贡献并通过手册页中的引用证实:"For almost every purpose, aliases are superseded by shell functions."

    为了完整性并且因为这可能是有用的(稍微更轻量级的语法),可以注意到当参数遵循别名时,它们仍然可以使用(尽管这不符合OP的要求) . 这可能是最简单的示例:

    alias ssh_disc='ssh -O stop'
    

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

    这对于采用复杂参数的命令非常有用(我的内存不再是它的用途......)

相关问题