【问题标题】:Make a Bash alias that takes a parameter?制作一个带参数的 Bash 别名?
【发布时间】:2023-09-14 22:11:01
【问题描述】:

我曾经使用 CShell (),它可以让您创建一个带参数的别名。符号类似于

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

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

【问题讨论】:

  • 确保在参数 "$1"987654324@ 周围使用引号
  • 这无助于将变量插入到字符串的中间
  • 这是我用来发现这个谬误的 lil 测试别名...alias test_args="echo PREFIX --$1-- SUFFIX",当使用 test_args ABCD 调用时会产生以下控制台输出 PREFIX ---- SUFFIX ABCD
  • 请注意,至少从 1996 年开始,bash 文档就包含以下内容:“对于几乎所有目的,别名都被 shell 函数所取代”,我认为这是一种轻描淡写的说法。绝对没有理由使用别名而不是函数。

标签: csh bash alias


【解决方案1】:

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

【讨论】:

  • 我应该把$1 用引号括起来吗?
  • 您甚至不必声明别名。只需定义函数即可。
  • 如果您将别名更改为函数,sourceing 您的 .bashrc 将添加该函数,但不会取消旧别名的别名。由于别名的优先级高于函数,因此它将尝试使用别名。您需要关闭并重新打开您的 shell,或者调用 unalias <name>。也许我会节省我刚刚浪费的 5 分钟。
  • @MartinNeal:我在 Sun 学到的一个节省时间的技巧是只做一个 exec bash:它会启动一个新的 shell,让你清楚地读取你的配置,就像你关闭一样并重新打开,但也保留该会话的环境变量设置。此外,当您想要处理像堆栈一样的思考时,在没有 exec 的情况下执行 bash 会很有用。
  • @mich - 是的,总是将 bash 变量放在引号中。例如mv "$1" "$1.bak"。如果没有引号,如果 $1 是“hello world”,您将执行 mv hello world hello world.bak 而不是 mv "hello world" "hello world.bak"
【解决方案2】:

细化上面的答案,您可以像别名一样获得 1 行语法,这对于 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; }

【讨论】:

  • 我更喜欢这个答案,因为它显示了遍历可能传入的参数数组。$1 和 $2 只是特殊情况,此答案中也说明了这一点。
  • mv "$1" "$1.bak"; cp "$2" "$1" 更改为mv "$1" "$1.bak" && cp "$2" "$1",以便在mv 遇到问题(例如,文件系统已满)时不会丢失您的数据。
  • "不要忘记右括号前的分号。"很多次这样!谢谢:)
  • 第一个括号后和最后一个括号前的空格也是必须的。
  • @HenkLangeveld 虽然您的评论完全正确,但它表明您已经存在一段时间了——我不知道我上次遇到“设备上没有剩余空间”错误是什么时候 ;-)。 (我经常使用它,但当我在厨房柜台上找不到空间时!)这些天似乎不经常发生。
【解决方案3】:

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

我想你想要这样的东西:

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

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

【讨论】:

  • 这个答案说明了一切。如果我从本页底部阅读,我会节省一些时间。
  • -1 别名和函数不等价...echo -e '#!/bin/bash\nshopt -s expand_aliases\nalias asrc='\''echo "${BASH_SOURCE[0]}"'\'' # note the '\''s\nfunction fsrc(){ echo "${BASH_SOURCE[0]}";}'>>file2&&echo -e '#!/bin/bash\n. file2\nalias rl='\''readlink -f'\''\nrl $(asrc)\nrl $(fsrc)'>>file1&&chmod +x file1&&./file1;rm -f file1 file2
  • 你真的应该引用$@来支持带空格等的文件名
  • @FuzzyLogic - 我花了几分钟时间试图解压您评论中的代码。将一些代码打包到无法正确格式化的注释中需要付出很多(有趣的)努力。我还没有弄清楚它到底做了什么,或者更重要的是,为什么它支持你的(正确的)断言。
  • “问错了”?这假设 OP 确切地知道要问什么,如果他/她知道了,他们很可能一开始就不会问。这是一个提问的论坛,希望能解决好的问题,但我们不要在这里过分评判。
【解决方案4】:

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

说明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 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

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

【讨论】:

  • 对于解决方案 1,请确保使用单引号,并避免在 $@ 周围使用 \"。如果需要,这是一种非常有用的技术。ty。
  • 解决方案 1 为我工作,用我想要的每个服务器的参数包装 rdesktop-vrdp。
  • 最后接受参数正是我用简单的“gc {branchname}”替换“git checkout {branchname}”所需要的。在 .bash_profile 我只需要添加 alias gc='git checkout'
  • @sgtz 为什么要删除 $@ 周围的引号?例如,如果您有包含空格的文件,则它们是必需的。
  • @AlexisWilke 确切地说。更简单的是,你可以简单地使用\df
【解决方案5】:

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

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

必须在 "$1" 周围加上双引号,因为单引号不起作用。这是因为在标有箭头的地方与引号发生冲突会使系统感到困惑。此外,该功能需要在标有星号的位置留出空格。

【讨论】:

  • 这很聪明,但除了问题确实指定创建别名之外,是否有任何理由不只是使函数与最初的别名同名?
  • @xaxxon 不是真的,但这是我知道使用别名而不是函数的最简单方法。
  • 这不起作用。在 Ubuntu 18 上,我收到错误 bash: syntax error near unexpected token '{mkdir'
  • 我已经修复了语法问题。回复:删除function 前缀,请参阅wiki.bash-hackers.org/scripting/obsolete 中的讨论。也就是说,我不知道为什么有人会这样做而不是只定义一个函数而不使用任何别名。
  • @faramir 你可以使用command。你甚至可以使用像alias c = 'command' 这样的别名,然后简单地使用c ls
【解决方案6】:

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

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

【讨论】:

    【解决方案7】:

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

    alias cp="~/SCR/spiner cp"
    

    这是旋转脚本

    #!/bin/bash
    
    #Set timer
    T=$(date +%s)
    
    #Add some color
    . ~/SCR/color
    
    #Animation sprites
    sprite=( "(* )  ( *)" " (* )( *) " " ( *)(* ) " "( *)  (* )" "(* )  ( *)" )
    
    #Print empty line and hide cursor
    printf "\n${COF}"
    
    #Exit function
    function bye { printf "${CON}"; [ -e /proc/$pid ] && kill -9 $pid; exit; }; trap bye INT
    
    #Run our command and get its pid
    "$@" & pid=$!
    
    #Waiting animation
    i=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 exit
    T=$(($(date +%s)-$T))
    printf "\n\nTime taken: $(date -u -d @${T} +'%T')\n"
    
    bye
    

    看起来像这样

    循环动画)

    这是上面提到的color 脚本的链接。 和新的动画循环)

    【讨论】:

    • 这是无价之宝。 ?
    • 不过,这根本不能回答问题。
    【解决方案8】:

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

    在 nano 中打开 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”

    保存并退出 nano 编辑器

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

    告诉终端使用 .bash_profile 中的新别名

    source /.bash_profile
    

    就是这样!您现在可以使用新别名

    【讨论】:

    • 如果参数位于命令中间,这似乎不起作用。我怀疑正在发生的事情是你调用别名后跟“参数”,它恰好在你的命令末尾,所以它被解释为你最后的参数在最后。例如gitc foo 运行 git commit -m foo 不是因为你有 $@ 而是因为 foo 出现在最后——它正在运行 git commit -m 并且你碰巧有一个 foo 紧随其后并被附加,这使得你的命令有效。
    • 总结一下,别名接受参数,但只有放在别名的末尾。这是一种有限的参数。而且你不需要别名def中的$@
    • 您可以从别名中删除所有位置变量引用,它仍然可以工作 - 所有这些占位符都替换为空字符串,并且给别名调用的参数只是附加到别名的扩展中,在它的结束。因此,您可能认为它按您描述的方式工作,但实际上并非如此。您将了解到这一点,因为您只需将占位符放在别名的中间或开头。
    • 如果在调用别名之前运行set -- "first argument" "second argument",所有"$@"s 将变成"first argument" "second argument"s。它们不反映传递给别名的参数;它们只反映在调用别名的上下文中处于活动状态的参数
    【解决方案9】:

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

    #Utility required by all below functions.
    #https://*.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://*.com/a/3232082/2736496
        - Alias w/param: https://*.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://*.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
    }
    

    参考资料:

    【讨论】:

    • 嗯?这是一个函数,而不是别名。您的参考资料之一就是这个问题。
    • 不,它的工作原理与函数完全一样。就像这里解释的几个答案一样,您不能制作带有参数的别名。你能做的就是写一个函数,这就是你所做的。
    • @tripleee 从我的 bash 新手的角度来看,在阅读您的评论之前,我认为这是一个别名(创建别名的另一种方法)。据我所知,它的功能完全一样。它本质上是一个接受参数的别名,即使我不了解该术语。我没有看到问题。我已经澄清了这个问题中另一个答案的链接。它帮助我创建了这个。
    • 也许这并没有具体回答“接受参数的 bash 别名”的问题,因为我现在明白这是不可能的,但它肯定有效地回答了它.我在这里错过了什么?
    • 这里有一些非常好的例子,以及很好的注释代码。对访问此页面的人有很大帮助。
    【解决方案10】:

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

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

    当我随后运行 mycommand foobar 时,它的工作方式与我以手写方式输入命令完全一样。

    【讨论】:

    • 当然,alias 可以获取参数,我已经习惯使用这个了,例如: alias commit="git commit -m $1"
    • 对我不起作用:alias myalias="echo 1 "$1" 3"; myalias 2 给我:1 3 2
    • "没有在替换文本中使用参数的机制,就像在 csh 中一样" gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Aliases 你的命令可能有效,因为 appending 无论你在 $1 末尾添加什么该 cmd 行仍然适用于您正在做的任何事情。
    • alias mycommand = "python3 script.py --folderoutput RESULTS/" 的作用相同(省略"$1")。相同,因为 alias 在 bash 中没有设置位置参数。在交互式 shell 中,它们的值是 "" 一个空字符串,除非你在其中放了一些东西。为了进一步测试,在定义别名之前尝试真正添加一些东西到 $1:set -- something。现在您的别名很可能已损坏,请查看该别名包含的内容:alias mycommand
    • @agapitocandemor 如果您的评论不是一个玩笑(这不是那么明显……),那么您的别名被破坏并且它“有效”只是因为$1 被空字符串替换并且您的评论被附加.您可以安全地将您的别名重新定义为 git commit -m,它仍然可以工作。
    【解决方案11】:

    语法:

    alias shortName="your custom command here"
    

    例子:

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

    【讨论】:

    • 非常聪明的解决方案!如果你尝试这个并且只是得到一个&gt; 提示,请确保你在} 之前得到了;。另请注意,每次使用别名时都会重新定义函数,因此如果函数定义很昂贵,最好将其移出alias
    • 你为什么要这样做而不是仅仅定义一个函数而根本没有任何别名?
    【解决方案12】:

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

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

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

    【讨论】:

    • 为什么不直接将函数命名为runjar 而不是将其设为函数的别名?看起来不必要的复杂!
    • @EvanLanglois 函数在 bash 文件中自动被 alias-ed(或将事物的属性传递给 alias)?如果是,那只是我不知道
    • 不明白你的意思。别名是另一个名称,无论是人还是函数。因此,您创建了一个函数,然后为该函数创建了第二个名称。别名没有什么特别之处,它只是第二个名字,不是必需的。你在命令行输入的可以是内部函数也可以是外部函数,所以“function”是一个新命令,而不是别名。
    • 这毫无意义。与alias runjar='java -jar myjar.jar' 相同。别名确实接受参数,但仅在末尾。
    【解决方案13】:

    注意:如果这个想法不明显,那么将别名用于除别名之外的任何东西都是一个坏主意,第一个是“别名中的函数”,第二个是“难以阅读的重定向/资源'。此外,也存在缺陷(我认为很明显,但以防万一您感到困惑:我并不是说它们实际上可以在任何地方使用!)

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

    这个我以前回答过,过去一直是这样的:

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

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

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

    它们的长度大致相同,给出或取几个字符。

    real 踢脚线是时间差,顶部是“函数方法”,底部是“重定向源”方法。为了证明这一理论,时机不言自明:

    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 函数所取代。
    • 第二个(栏别名)有几个副作用。首先该命令不能使用标准输入。其次,如果在别名之后没有提供任何参数,那么 shell 碰巧有的任何参数都会被传递。使用函数可以避免所有这些副作用。 (也无用的使用 cat)。
    • 嘿,我从来没有说过这样做是个好主意,我只是说还有其他方法可以解决它——这里的主要思想是你不应该在别名中使用函数因为它们甚至比复杂的重定向还要慢,我认为这很明显,但我想我必须为每个人都拼出来(如果我首先这样做,我也会因为帖子太臃肿而大喊大叫)
    • 由于 bar 的计时都是 0,我怀疑 shell 没有正确计时。请注意,在命令运行之前 打印了时间消息?因此,它什么都不计时,然后执行 bar,所以你根本没有测量 bar。做一些更复杂的事情,或者在 bar 中做一个大循环,这样你就可以更明显地表明你正在计时 nothing
    • 我现在尝试了这个(尽管我不得不将最后的 __foo() 替换为 __foo)。 time for i in {1..1000}; do foo FOOVALUE; done → 0m0.028s。但是time for i in {1..1000}; do bar FOOVALUE; done → 0m2.739s。 Bar 比 foo 慢两个数量级。仅使用普通函数而不是别名将运行时间再缩短 30%:function boo() { echo "arg1 for boo=$1" ; }time for i in {1..1000}; do boo FOOVALUE; done → 0m0.019s。
    【解决方案14】:

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

    在终端中:

    $ 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
    

    【讨论】:

    • 天哪 - 对不起;我在格式化代码时遇到了很多问题,如果有人想改进帖子,请做; //最美好的祝愿
    • 感谢@VonC - 帮了很多忙。
    • 传递参数时,参数前需要一个空格
    • 这可行,但原因完全不同。它之所以有效,只是因为$f 在末尾。 $f 在别名定义时可能为空,因此解析为alias CAT_FILE_="cat "。只需运行 alias 即可查看生成的定义。因此,当您调用CAT_FILE_ 时,您只需将参数传递给别名,就像alias ll="ls -l" 中的任何其他别名用法一样。尝试将“参数”$f 放在末尾以外的其他地方,这将不起作用。例如:alias ECHO_ARG_$f="echo xxx $f yyy"。然后ECHO_ARG_ test 导致xxx yyy test
    • @HolgerBöhnke 你是对的,非常感谢你的观点;out! :竖起大拇指图标:
    【解决方案15】:

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

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

    功能名称 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")
    

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

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

    等等

    【讨论】:

      【解决方案16】:

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

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

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

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

      【讨论】:

      • 你认为内引号在做什么? (它们不会成为别名的一部分;如果您想要它们,则需要转义它们:alias junk=xargs -I \"{}\" -- mv \"{}\" \"~/.Trash\" &lt;&lt;&lt;")。
      • 也就是说,您实际上并不想要这些引号,因为 xargs 没有进行文本替换(它对在 shell 解析已经结束后生成的 C 字符串数组进行操作,所以引号没有不再具有改变 shell 行为的价值),并且当引用 ~ 时不起作用。所以这会更好alias junk='xargs -I {} -- mv {} ~/.Trash &lt;&lt;&lt;'
      • @CharlesDuffy 随时编辑和改进我的答案。
      【解决方案17】:

      具体回答关于创建别名以将文件移动到废纸篓文件夹而不是删除它们的问题:

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

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

      然后只需给出以下命令:

      $rm <filename>
      $rm <dirname>
      

      【讨论】:

      • 或者甚至更好,结果相同:alias rm="mv "$i_bellieve_this_is_correct_because_it_WORKS" -t ~/.Trash/" 仍然不相信的人可以在之前运行此命令:set -- my_very_important_file 如果您有信心,请按字面意思运行或使用非常重要文件的路径 $1在别名作品中。现在声明:alias rm="mv "$1" -t ~/.Trash/"rm &lt;filename&gt; 按照建议。 set -- 将值设置为位置参数 $1。别名命令没有。
      • 正如 papo 上面所说:这只是看起来有效,因为即使 $1 完全为空,mv 仍然尊重尾随位置的参数。 $1 不是由传递给别名的参数填充;它由运行别名的 shell 中预先存在的 $1 值填充。
      【解决方案18】:

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

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

      alias ssh_disc='ssh -O stop'
      

      允许我输入像ssh_disc myhost 这样的东西,它会按预期扩展为:ssh -O stop myhost

      这对于需要复杂参数的命令很有用(我的记忆不再是它所使用的......)

      【讨论】:

      • 我没想到它实际上*会这样做,
      【解决方案19】:

      要取参数,你应该使用函数!

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

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

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

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

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

      foo arg1 arg2 argN
      

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

      在你的情况下,这会工作

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

      【讨论】:

      • 我不明白你的想法。我认为论点何时被解释并不重要。别名在最后接受尽可能多的参数。
      • 但是最好用函数来做。别名不是为了那个。
      【解决方案20】:

      示例如下:

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

      非常重要:

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

      【讨论】:

      • 函数不需要创建别名,直接使用函数即可。
      【解决方案21】:

      使用子命令的解决方案:

      d () {
          if [ $# -eq 0 ] ; then
              docker
              return 0
          fi
          CMD=$1
          shift
      
          case $CMD in
          p)
              docker ps --all $@
              ;;
          r)
              docker run --interactive --tty $@
              ;;
          rma)
              docker container prune
              docker image prune --filter "dangling=true"
              ;;
          *)
              docker $CMD $@
              ;;
          esac
          return $?
      }
      

      使用:

      $ d r my_image ...
      

      调用:

      docker run --interactive --tty my_image ...
      

      【讨论】:

        【解决方案22】:

        函数和别名都可以使用其他人在此处显示的参数。此外,我想指出其他几个方面:

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

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

        function tfunc(){
            GlobalFromFunc="Global From Func" # Function set global variable by default
            local 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 tfunc
        tfunc # call tfunc
        echo $GlobalFromFunc # This is visible
        echo $LocalFromFunc # This is not visible
        # Test variables set by talias
        # call talias
        talias
        echo $GlobalFromAlias # This is invisible
        echo $LocalFromAlias # This variable is unset and unusable 
        

        输出:

        bash-3.2$     # Test variables set by tfunc
        bash-3.2$     tfunc # call tfunc
        bash-3.2$     echo $GlobalFromFunc # This is visible
        Global From Func
        bash-3.2$     echo $LocalFromFunc # This is not visible
        
        bash-3.2$     # Test variables set by talias
        bash-3.2$     # call talias
        bash-3.2$     talias
        bash: local: can only be used in a function
        bash-3.2$     echo $GlobalFromAlias # This is invisible
        Global From Alias
        bash-3.2$ echo $LocalFromAlias # This variable is unset and unusable
        

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

        我遇到过好几次logging in via ssh或者涉及切换用户名或多用户环境时找不到别名或函数。获取点文件有一些技巧和窍门,或者这个有趣的别名:alias sd='sudo ' 让这个后续别名 alias install='sd apt-get install' 按预期工作(注意sd='sudo ' 中的额外空间)。但是,在这种情况下,包装脚本比函数或别名更有效。包装脚本的主要优点是它在预期路径(即/usr/loca/bin/)下是可见/可执行的,其中函数/别名需要在可用之前获取。例如,你在 ~/.bash_profile 或 ~/.bashrc 中为bash 放置了一个函数,但后来切换到另一个 shell(即zsh),那么该函数就不再可见了。 因此,当您有疑问时,包装脚本始终是最可靠和可移植的解决方案。

        【讨论】:

        • 为什么觉得功能版不行?
        • @tripleee。我已经编辑了帖子以使其更具可读性。在标题“1. alias works, function ...”下,我说明了为什么函数版本不起作用。也许为了直接回答这个问题,函数引入了一个别名没有的新范围。
        • 你说它不起作用,但我不相信你。 source 在函数中工作得很好。
        • f () { source /tmp/nst; } 完全符合我的预期。也许您的PATH 是错误的,所以它运行错误activate 或其他东西;但从函数运行source 工作正常。
        • 顺便说一句,回复:在您的定义中使用 function 关键字,请参阅 wiki.bash-hackers.org/scripting/obsolete -- function funcname { 是古老的 ksh 语法 bash 支持向后兼容 pre-POSIX shell,而 funcname() {是官方的 POSIX 标准化语法。 function funcname() { 是与古代 ksh 不兼容与 POSIX sh 兼容的两者的混搭,因此最好避免。
        【解决方案23】:

        这是使用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 
        

        【讨论】:

          【解决方案24】:
          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=0
          SKIPPED_ARG_NUM=0
          RAW_ARGS_COUNT="$#"
          
          for ARG in "$@"; do
            #echo $ARG
            if [[ "$ARG" == "$ARG_DELAY_MARKER" ]]; then
              SKIPPED_ARGS=$((SKIPPED_ARGS+1))
            fi
          done
          
          for ((I=0; I<$RAW_ARGS_COUNT-$SKIPPED_ARGS; I++)); do
            ARG="${RAW_ARGS[$I]}"
            if [[ "$ARG" == "$ARG_DELAY_MARKER" ]]; then
              MOVE_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" ]]; then
                echo "Error: Not enough arguments!"
                exit 1;
              fi
              #echo "Moving arg: $MOVING_ARG"
              ARGS+=("$MOVING_ARG")
              SKIPPED_ARG_NUM=$(($SKIPPED_ARG_NUM+1))
            else
              ARGS+=("$ARG")
            fi
          done
          
          #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[@]}"
          
          

          【讨论】: