查找命令的参数扩展 [英] Parameter expansion for find command

查看:100
本文介绍了查找命令的参数扩展的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑代码(因为存在变量$i,因为它在循环中,因此在模式中添加了多个条件,例如*.a*.b,...,但是为了说明此问题,仅使用一个通配符模式足够):

Consider the code (the variable $i is there because it was in a loop, adding several conditions to the pattern, e.g. *.a and *.b, ... but to illustrate this problem only one wildcard pattern is enough):

#!/bin/bash

i="a"
PATTERN="-name bar -or -name *.$i"
find . \( $PATTERN \)

如果在包含文件barfoo.a的文件夹上运行,它将工作,并输出:

If ran on a folder containing files bar and foo.a, it works, outputting:

./foo.a
./bar

但是,如果现在将新文件添加到文件夹中,即zoo.a,则它将不再起作用:

But if you now add a new file to the folder, namely zoo.a, then it no longer works:

find: paths must precede expression: zoo.a

大概是因为*.$i中的通配符被shell扩展为foo.a zoo.a,这导致无效的find命令模式.因此,一种解决方法是将引号括在通配符模式周围.除非它不起作用:

Presumably, because the wildcard in *.$i gets expanded by the shell to foo.a zoo.a, which leads to an invalid find command pattern. So one attempt at a fix is to put quotes around the wildcard pattern. Except it does not work:

  • 用单引号引起来-PATTERN="-name bar -or -name '*.$i'" find命令仅输出bar.转义单引号(\')会得到相同的结果.

  • with single quotes -- PATTERN="-name bar -or -name '*.$i'" the find command outputs only bar. Escaping the single quotes (\') yields the same result.

同义双引号:PATTERN="-name bar -or -name \"*.$i\""-仅返回bar.

idem with double quotes: PATTERN="-name bar -or -name \"*.$i\"" -- only bar is returned.

,如果将$PATTERN替换为"$PATTERN",则会出现错误(对于单引号,则是相同的错误,但通配符模式周围的单引号):

in the find command, if $PATTERN is replaced with "$PATTERN", out comes an error (for single quotes same error, but with single quotes around the wildcard pattern):

查找:未知谓词-name bar -or -name "*.a"'

当然,用'$PATTERN'替换$PATTERN也不起作用...(不会进行任何扩展).

Of course, replacing $PATTERN with '$PATTERN' also does not work... (no expansion whatsoever takes place).

我要使其正常工作的唯一方法是使用... eval

The only way I could get it to work was to use... eval!

FINDSTR="find . \( $PATTERN \)"
eval $FINDSTR

这正常工作:

./zoo.a
./foo.a
./bar

现在,经过大量的搜索,我看到它多次提到要做这种事情,一个人应该使用数组.但这不起作用:

Now after a lot of googling, I saw it mentioned several times that to do this kind of thing, one should use arrays. But this doesn't work:

i="a"
PATTERN=( -name bar -or -name '*.$i' )
find . \( "${PATTERN[@]}" \)

# result: ./bar

find行中,必须将数组用双引号引起来,因为我们想要对其进行扩展.但是通配符表达式周围的单引号不起作用,也没有引号:

In the find line the array has to be enclosed in double quotes, because we want it to be expanded. But single quotes around the wildcard expression don't work, and neither does not quotes at all:

i="a"
PATTERN=( -name bar -or -name *.$i )
find . \( "${PATTERN[@]}" \)

# result: find: paths must precede expression: zoo.a

但要完成双重报价!

i="a"
PATTERN=( -name bar -or -name "*.$i" )
find . \( "${PATTERN[@]}" \)

# result:
# ./zoo.a
# ./foo.a
# ./bar

所以我想我的问题实际上是两个问题:

So I guess my question are actually two questions:

a)在最后一个使用数组的示例中,为什么在*.$i周围需要双引号?

a) in this last example using arrays, why are double quotes required around the *.$i?

b)以这种方式使用数组应该将«分别扩展到所有元素引用».如何使用变量(参见我的第一次尝试)?使它起作用后,我返回并再次尝试使用带斜杠单引号或\\'的变量,但没有任何效果(我刚得到bar).我该怎么做才能像手工"一样模仿使用数组时的引用?

b) using an array in this way is supposed to expand «to all elements individually quoted». How would do this with a variable (cf my first attempt)? After getting this to function, I went back and tried using a variable again, with blackslashed single quotes, or \\', but nothing worked (I just got bar). What would I have to do to emulate "by hand" as it were, the quoting done when using arrays?

预先感谢您的帮助.

推荐答案

必读:

  • BashFAQ — I'm trying to put a command in a variable, but the complex cases always fail!

a)在最后一个使用数组的示例中,为什么在*.$i周围需要双引号?

a) in this last example using arrays, why are double quotes required around the *.$i?

您需要使用某种形式的引号来防止外壳在*上执行glob扩展.变量不会用单引号引起来,因此'*.$i'不起作用.它确实抑制了球体膨胀,但也停止了变量膨胀. "*.$i"抑制球体膨胀,但允许变量膨胀,这是完美的.

You need to use some form of quoting to prevent the shell from performing glob expansion on *. Variables are not expanded in single quotes so '*.$i' doesn't work. It does inhibit glob expansion but it also stops variable expansion. "*.$i" inhibits glob expansion but allows variable expansion, which is perfect.

要真正深入研究细节,您需要在此处做两件事:

To really delve into the details, there are two things you need to do here:

  1. 转义或引用*以防止全局扩展.
  2. $i作为变量扩展进行处理,但请引用它以防止单词分裂和glob扩展.
  1. Escape or quote * to prevent glob expansion.
  2. Treat $i as a variable expansion, but quote it to prevent word splitting and glob expansion.

对于项目1:\*"*"'*'$'*'的任何形式的报价都可以使用,以确保将其视为文字星号.

Any form of quoting will do for item 1: \*, "*", '*', and $'*' are all acceptable ways to ensure it's treated as a literal asterisk.

对于项目2,双引号是唯一的答案.裸露的$i会受到单词拆分和glob的影响-如果您具有i='foo bar'i='foo*',则空格和glob会引起问题. \$i'$i'都按字面意义对待美元符号,所以他们退出了.

For item 2, double quoting is the only answer. A bare $i is subject to word splitting and globbing -- if you have i='foo bar' or i='foo*' the whitespace and globs will cause problems. \$i and '$i' both treat the dollar sign literally, so they're out.

"$i"是唯一可以正确执行所有操作的报价.这就是为什么通常的shell建议是总是双引号变量扩展.

"$i" is the only quoting that does everything right. It's why common shell advice is to always double quote variable expansions.

最终结果是,以下任何一项均有效:

The end result is, any of the following would work:

"*.$i"
\*."$i"
'*'."$i"
"*"."$i"
'*.'"$i"

很明显,第一个是最简单的.

Clearly, the first is the simplest.

b)以这种方式使用数组应扩展为到所有单独引用的元素".如何使用变量(参见我的第一次尝试)?使它起作用后,我返回并再次尝试使用带斜杠单引号或\\'的变量,但没有任何效果(我刚得到bar).我该怎么做才能像手工"一样模仿使用数组时的引用?

b) using an array in this way is supposed to expand «to all elements individually quoted». How would do this with a variable (cf my first attempt)? After getting this to function, I went back and tried using a variable again, with blackslashed single quotes, or \\', but nothing worked (I just got bar). What would I have to do to emulate "by hand" as it were, the quoting done when using arrays?

您必须将eval拼凑在一起,但这很危险.从根本上说,数组比简单的字符串变量更强大.没有引号和反斜杠的神奇组合,它可以让您执行数组可以执行的操作.数组是完成这项工作的正确工具.

You'd have to cobble together something with eval, but that's dangerous. Fundamentally, arrays are more powerful than simple string variables. There's no magic combination of quotes and backslashes that will let you do what an array can do. Arrays are the right tool for the job.

您能否详细解释一下,为什么... PATTERN="-name bar -or -name \"*.$i\""无法正常工作?在实际运行find命令时,带引号的双引号应扩展$i而不是glob.

Could you explain in a little more detail, why ... PATTERN="-name bar -or -name \"*.$i\"" does not work? The quoted double quotes should, when the find command is actually ran, expand the $i but not the glob.

好的.假设我们写:

i=a
PATTERN="-name bar -or -name \"*.$i\""
find . \( $PATTERN \)

前两行运行后,$PATTERN的值是多少?让我们检查一下:

After the first two line runs, what is the value of $PATTERN? Let's check:

$ i=a
$ PATTERN="-name bar -or -name \"*.$i\""
$ printf '%s\n' "$PATTERN"
-name bar -or -name "*.a"

您会发现$i已被a替换,并且反斜杠已被删除.

You'll notice that $i has already been replaced with a, and the backslashes have been removed.

现在,让我们看看如何精确解析find命令.在最后一行中,$PATTERN被取消了引号,因为我们希望所有单词都被分开,对吗?如果您写了一个简单的变量名,那么Bash最终会执行隐式的 split + glob 操作.它执行分词和全局扩展.到底是什么意思?

Now let's see how exactly the find command is parsed. In the last line $PATTERN is unquoted because we want all the words to be split apart, right? If you write a bare variable name Bash ends up performing an implied split+glob operation. It performs word splitting and glob expansion. What does that mean, exactly?

让我们看一下Bash如何执行命令行扩展.在扩展"部分下的 Bash手册页中,我们可以看到操作顺序:

Let's take a look at how Bash performs command-line expansion. In the Bash man page under the "Expansion" section we can see the order of operations:

  1. 括号扩展
  2. 平铺扩展,参数和变量扩展,算术扩展,命令替换和进程替换
  3. 分词
  4. 路径名(又名glob)扩展
  5. 删除行情

让我们手动进行这些操作,看看如何解析find . \( $PATTERN \).最终结果将是一个字符串列表,因此我将使用类似于JSON的语法来显示每个阶段.我们将从一个包含单个字符串的列表开始:

Let's run through these operations by hand and see how find . \( $PATTERN \) is parsed. The end result will be a list of strings, so I'll use a JSON-like syntax to show each stage. We'll start with a list containing a single string:

['find . \( $PATTERN \)']

作为第一步,整个命令行都要进行分词.

As a preliminary step, the command-line as a whole is subject to word splitting.

['find', '.', '\(', '$PATTERN', '\)']

  1. 括号扩展-不变.

可变扩展

['find', '.', '\(', '-name bar -or -name "*.a"', '\)']

$PATTERN被替换.目前,它都是单个字符串,空格和全部.

$PATTERN is replaced. For the moment it is all a single string, whitespace and all.

分词

['find', '.', '\(', '-name', 'bar', '-or', '-name', '"*.a"', '\)']

shell会扫描双引号中未出现的用于变量拆分的变量扩展结果. $PATTERN未加引号,因此已扩展.现在是一堆个别的单词.到目前为止一切顺利.

The shell scans the results of variable expansion that did not occur within double quotes for word splitting. $PATTERN was unquoted, so it's expanded. Now it is a bunch of individual words. So far so good.

全局扩展

['find', '.', '\(', '-name', 'bar', '-or', '-name', '"*.a"', '\)']

Bash扫描单词拆分结果以查找glob.不是整个命令行,只有标记-namebar-or-name"*.a".

Bash scans the results of word splitting for globs. Not the entire command-line, just the tokens -name, bar, -or, -name, and "*.a".

看起来什么也没发生,是吗?没那么快!人不可貌相. Bash实际上执行了glob扩展.碰巧,这个问题与任何事物都不匹配.但这可能...

It looks like nothing happened, yes? Not so fast! Looks can be deceiving. Bash actually performed glob expansion. It just happened that the glob didn't match anything. But it could...

删除行情

['find', '.', '(', '-name', 'bar', '-or', '-name', '"*.a"', ')']

反斜杠不见了.但是双引号仍然是 .

The backslashes are gone. But the double quotes are still there.

在前面的扩展名之后,所有不是由上述扩展名之一引起的字符\'" 的所有未加引号的出现都被删除了.

After the preceding expansions, all unquoted occurrences of the characters \, ', and " that did not result from one of the above expansions are removed.

这就是最终结果.双引号仍然存在,因此,不是搜索名为*.a的文件,而是搜索名称中带有文字双引号字符的名为"*.a"的文件.该搜索注定会失败.

And that's the end result. The double quotes are still there, so instead of searching for files named *.a it searches for ones named "*.a" with literal double quotes characters in their name. That search is bound to fail.

添加一对转义的引号\"根本没有实现我们想要的功能.引号并没有像应该的那样消失,并中断了搜索.不仅如此,而且它们也没有像应有的那样抑制水珠滚滚.

Adding a pair of escaped quotes \" didn't at all do what we wanted. The quotes didn't disappear like they were supposed to and broke the search. Not only that, but they also didn't inhibit globbing like they should have.

TL; DR —解析变量内的引号 与引用变量外的的方式不同.

TL;DR — Quotes inside a variable aren't parsed the same way as quotes outside a variable.

前四个标记没有特殊字符.但是最后一个"*.a"可以.该星号是通配符.如果您仔细阅读手册页的路径名扩展"部分,您会发现这里没有提及引号被忽略的情况.双引号不保护星号.

The first four tokens have no special characters. But the last one, "*.a", does. That asterisk is a wildcard. If you read the "pathname expansion" section of the man page carefully you'll see that there's no mention of quotes being ignored. The double quotes do not protect the asterisk.

等一下!什么?我以为引号会抑制全局扩展!

通常会这么做.如果用手写引号,它们的确会阻止全局扩展.但是,如果将它们放在一个无引号的变量中,它们就不会.

They do—normally. If you write quotes out by hand they do indeed stop glob expansion. But if you put them inside an unquoted variable, they don't.

$ touch 'foobar' '"foobar"'
$ ls
foobar   "foobar"
$ ls foo*
foobar
$ ls "foo*"
ls: foo*: No such file or directory
$ var="\"foo*\""
$ echo "$var"
"foo*"
$ ls $var
"foobar"

请仔细阅读.如果我们创建一个名为"foobar"的文件-即它的文件名中包含文字双引号-那么ls $var会显示"foobar".该glob会展开,并与(公认的)文件名匹配!

Read that over carefully. If we create a file named "foobar"—that is, it has literal double quotes in its filename—then ls $var prints "foobar". The glob is expanded and matches the (admittedly contrived) filename!

为什么报价没有帮助?好吧,这种解释是微妙而棘手的.手册页显示:

Why didn't the quotes help? Well, the explanation is subtle, and tricky. The man page says:

分词后... bash会扫描每个单词中的字符*?[.

Bash每次执行单词拆分操作它还会扩展globs .还记得我怎么说无引号的变量受隐式 split + glob 运算符的约束吗?这就是我的意思.分裂和球体并存.

Any time Bash performs word splitting it also expands globs. Remember how I said unquoted variables are subject to an implied split+glob operator? This is what I meant. Splitting and globbing go hand in hand.

如果编写ls "foo*",则引号可防止foo*受到分裂和干扰.但是,如果您编写ls $var,则$var会被展开,拆分和扩展.它没有被双引号引起来. 包含双引号没关系.等到双引号出现时,为时已晚.单词拆分已经执行过,因此遍历也完成了.

If you write ls "foo*" the quotes prevent foo* from being subject to splitting and globbing. However if you write ls $var then $var is expanded, split, and globbed. It wasn't surrounded by double quotes. It doesn't matter that it contains double quotes. By the time those double quotes show up it's too late. Word splitting has already been performed, and so globbing is done as well.

这篇关于查找命令的参数扩展的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆