我曾经使用CShell(csh),它允许您创建一个接受参数的别名。符号有点像

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

在巴什,这似乎行不通。鉴于Bash有许多有用的特性,我会假设这一特性已经实现,但我想知道如何实现。


当前回答

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

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

函数名称BASH_源巴什利诺BASH_ARGCBASH_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)) <<<'

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

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

Etc.

其他回答

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

通过别名将参数放在命令中间确实是可能的,但这会变得很难看。

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

如果你喜欢规避限制,做别人说不可能做的事,下面是食谱。如果你的头发被磨烂了,你的脸上布满了科学家式的烟灰,不要怪我。

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

解决方案1

如果您确实反对使用函数本身,可以使用:

$ 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

您也可以使用子外壳:

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

解释2

别名生成如下命令:

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

评论:

占位符_是必需的,但它可以是任何内容。它被设置为sh的$0,并且是必需的,以便用户给定的第一个参数不会被消耗。演示:sh-c'echo消费:“$0”打印:“$@”'酒后胡言乱语消费:酒精印刷:醉酒胡言乱语单引号内的单引号是必填的。下面是一个不使用双引号的示例:$sh-c“echo消耗:$0打印:$@”酒后胡言乱语消耗:-bash打印:在这里,交互式shell的$0和$@的值在传递给sh之前被替换为双引号echo“消耗:$0打印:$@”消耗:-bash打印:单引号确保这些变量不会被交互式shell解释,并按字面传递给sh-c。你可以使用双引号和\$@,但最好的做法是引用你的论点(因为它们可能包含空格),而“\$@\”看起来更难看,但可能会帮助你赢得一场混淆比赛,在这场比赛中,凌乱的头发是参赛的先决条件。

下面是我的~/.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
}

参考文献:

从字符串中删除空白:如何从Bash变量中删除空白?实际换行符:https://stackoverflow.com/a/4296147/2736496别名w/param:https://stackoverflow.com/a/7131683/2736496(这个问题的另一个答案)组氨酸:https://superuser.com/questions/232885/can-you-share-wisdom-on-using-histignore-in-bashY/N提示: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

注意:如果这个想法不明显,除了别名之外,使用别名是一个坏主意,第一个是“别名中的函数”,第二个是“难以读取的重定向/源”。此外,还有一些缺陷(我认为这是显而易见的,但以防万一你会感到困惑:我并不是说它们真的可以在任何地方使用!)


我以前回答过这个问题,过去总是这样:

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=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个结果的底部。函数创建/销毁似乎比重定向花费更多的时间。希望这将有助于未来的访问者解决这个问题(我不想对自己保密)。

实际上,函数几乎总是答案,正如手册页中的这句话所证实的:“对于几乎所有的目的,别名都被shell函数所取代。”

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

alias ssh_disc='ssh -O stop'

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

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

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

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