在 bash 函数中使用“$@”与在 bash 中直接运行命令的结果不同

在 bash 函数中使用“$@”与在 bash 中直接运行命令的结果不同

在命令行中运行history结果如下:

$history aws s3 cp
16160  aws s3 cp s3://mybucket/air2008/10 .
16254  hists "aws s3 cp"

hists对于旨在作为上述快捷方式的 功能:

type hists
hists is a function
hists ()
{
    history 15000 | grep "$@"
}

运行时我们得到

09:40:44/csv $hists aws s3 cp
grep: s3: No such file or directory
grep: cp: No such file or directory

如果我们用引号运行它,它确实会得到正确的结果。但是引号是强制性的吗?

答案1

$hists "aws s3 cp"“扩展”为history 15000 | grep "aws s3 cp"。这意味着grep接收单个参数。

$hists aws s3 cp扩展为history 15000 | grep "aws" "s3" "cp"grep接收三个参数。

的第一个参数grep是模式。其他参数是要在其中查找模式的文件。当前文件夹中grep没有调用的文件s3,因此显示错误消息。cpgrep

引号可以改变命令行上文本的含义。如果您希望参数包含空格,引号是实现此目的的一种方式。

$hosts aws\ s3\ cp做同样的事情:通过在空格前添加反斜杠,shell 知道不要使用通常的含义,即分隔参数。相反,它将变成文字空格。

其行为也没有什么不同"$@"。它的存在正是为了保留引用的参数。

答案2

另一个答案很好。它解释了发生的事情:

hists aws s3 cp扩展为history 15000 | grep "aws" "s3" "cp"grep接收三个参数。

的第一个参数grep是模式。其他参数是要在其中查找模式的文件。当前文件夹中grep没有调用的文件s3,因此显示错误消息。cpgrep

我的回答将允许您强化和修改该功能,以便hists aws s3 cp可以工作。


原始代码中的第一个注释grep "$@"允许您注入选项grep,像这样:

hists -i LS

你可能想这样做,也可能不想这样做。如果不想,那么这段代码应该是grep -- "$@"。但即便如此,您仍可以将文件名传递给grep。我估计您永远不会想grep在函数中对文件进行操作。一些防止这种情况发生的机制会很有用。

如果您希望能够注入选项,那么没有什么简单的方法可以阻止您指定文件作为附加参数。该函数应该将多个参数传递给grep;它本身不知道哪个是选项,哪个是文件。一些逻辑可能会处理这个问题,但让我们保持简单。

如果您可以不向 注入选项grep,则可以确保该工具只获取一个参数,即模式。您可以使用grep -- "$1",但在这种情况下

hists aws s3 cp

将等同于hists aws,其他参数无关紧要。这并非您想要的,但它将阻止grep解析s3cp抛出有关它们的错误。

或者你可以使用grep -- "$*"。这是POSIX 的说法关于"$*"

当扩展发生在不会执行字段拆分的上下文中时,初始字段应连接起来形成单个字段,并且每个参数的值IFS如果IFS包含至少一个字符则由变量的第一个字符分隔,如果未设置则由<space>分隔,如果设置为空字符串IFS则没有分隔符。IFS

双引号内是“不会执行字段拆分的上下文”,标准IFS以 <space> 开头。这意味着如果您使用grep -- "$*",则命令

hists aws s3 cp

将触发grep -- "aws s3 cp",这正是您首先想要的。请注意,使用以下命令您将获得相同的结果

hists aws  s3           cp

尽管有多个空格。关键是hists根本看不到这些空格。它将awss3cp视为单独的参数,然后 的机制"$*"使用单个空格连接三个字符串。

另一方面,这些命令:

hists "aws s3 cp"
hists "aws  s3           cp"

不管您使用grep -- "$*"grep -- "$@"或,都会完全按照您的预期工作grep -- "$1"。当 的参数较多时,这三种变体的行为会有所不同hists;或较少(尝试hists不带任何参数)。

因此,grep -- "$*"在某些情况下,您可以将引号设为可选。grep无论您输入什么,此变体也会阻止解析文件。

相关内容