如何在Bash脚本中为一个变量赋一个glob表达式?

时间:2022-05-23 23:52:47

When the following two lines of code are executed in a bash script, "ls" complains that the files don't exist:

当在bash脚本中执行以下两行代码时,“ls”会抱怨文件不存在:

dirs=/content/{dev01,dev02}
ls -l $dirs

When I run the script with the -x option, it appears to be passing the variable within single quotes (which would prevent globbing):

当我使用-x选项运行脚本时,它似乎是在单引号内传递变量(这会阻止通配):

+ dirs=/content/{dev01,dev01}
+ ls -l '/content/{dev01,dev01}'
ls: /content/{dev01,dev01}: No such file or directory

If I execute the "ls" command from my interactive shell (sans quotes), it returns the two directories.

如果我从交互式shell(无引号)执行“ls”命令,它将返回两个目录。

I've been reading through the Bash Reference Manual (v 3.2) and can't see any reason for filename globbing to not take place (I'm not passing -f to the shell), or anything that I can set to ensure that globbing happens.

我一直在阅读Bash参考手册(第3.2版)并且看不到任何原因导致文件名通配不发生(我没有将-f传递给shell),或者我可以设置的任何内容以确保全球化发生了。

8 个解决方案

#1


29  

I think it is the order of expansions:

我认为这是扩展的顺序:

The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.

扩展的顺序是:大括号扩展,波浪扩展,参数,变量和算术扩展和命令替换(以从左到右的方式完成),单词拆分和路径名扩展。

So if your variable is substituted, brace expansion doesn't take place anymore. This works for me:

因此,如果您的变量被替换,则不再进行大括号扩展。这对我有用:

eval ls $dirs

Be very careful with eval. It will execute the stuff verbatimly. So if dirs contains f{m,k}t*; some_command, some_command will be executed after the ls finished. It will execute the string you give to eval in the current shell. It will pass /content/dev01 /content/dev02 to ls, whether they exist or not. Putting * after the stuff makes it a pathname-expansion, and it will omit non-existing paths:

对eval要非常小心。它将逐字执行这些东西。所以,如果dirs包含f {m,k} t *; some_command,some_command将在ls完成后执行。它将执行您在当前shell中为eval提供的字符串。它会将/ content / dev01 / content / dev02传递给ls,无论它们是否存在。在事物之后放置*使其成为路径名扩展,并且它将省略不存在的路径:

dirs=/content/{dev01,dev02}*

I'm not 100% sure about this, but it makes sense to me.

我对此并不是百分之百确定,但这对我来说很有意义。

#2


21  

Here is an excellent discussion of what you are trying to do.

以下是对您要做的事情的精彩讨论。

The short answer is that you want an array:

简短的回答是你想要一个数组:

dirs=(/content/{dev01,dev01})

But what you do with the results can get more complex than what you were aiming for I think.

但是你对结果所做的事情可能比你想象的要复杂得多。

#3


8  

This isn't filename globbing, this is brace expansion. The difference is subtle, but it exists - in filename globbing you would only receive existing files as a result, while in brace expansion you can generate any kind of string.

这不是文件夹通配,这是大括号扩展。区别是微妙的,但它存在 - 在文件名通配中你只会收到现有文件,而在大括号扩展中你可以生成任何类型的字符串。

http://www.gnu.org/software/bash/manual/bashref.html#Brace-Expansion

http://www.gnu.org/software/bash/manual/bashref.html#Filename-Expansion

Now, this is what worked for me:

现在,这对我有用:

#!/bin/sh
dirs=`echo ./{dev01,dev02}`
ls $dirs

#4


4  

For folks (like me) finding this through Google, @Peter and @feoh's answers are the general solution to "How to glob variables in bash script".

对于那些通过谷歌发现这个问题的人(像我一样),@ Pee和@ feoh的答案是“如何在bash脚本中对变量进行全局变换”的一般解决方案。

list_of_results=(pattern)

will save existing filenames matching pattern into the array list_of_results. Each element of list_of_results will hold one filename, spaces and all.

将匹配模式的现有文件名保存到数组list_of_results中。 list_of_results的每个元素将包含一个文件名,空格和全部。

You can access each result as "${list_of_results[<index>]}" for <index> starting from 0. You can get the entire list, properly quoted, as "${list_of_results[@]}".

对于从<0开始的 ,您可以将每个结果作为“$ {list_of_results [ ]}”访问。您可以将整个列表正确引用为“$ {list_of_results [@]}”。

#5


2  

I suspect that what you need is an array, but that will restrict you to newer bashes. It is saver than using eval.

我怀疑你需要的是一个数组,但这会限制你进入更新的阶段。它比使用eval更安全。

dirs=( /"content with spaces"/{dev01,dev02} )

dirs=( /content/{dev01,dev02} )
ls -l "${dirs[@]}"

/content/{dev01,dev02}

will expand to:

将扩展到:

"/content/dev01" "/content/dev02"

The existence of those directories is irrelevant to the expansion.

这些目录的存在与扩展无关。

It becomes unpredictable when you assign a variable to a brace expansion.

将变量分配给大括号扩展时,它变得不可预测。

dirs=/content/{dev01,dev02}

may turn into

可能变成

"/content/dev01"

or

"/content/dev01 /content/dev02"

or

"/content/dev01" "/content/dev02"

or

"/content/{dev01,dev02}"

If you quote the braces in any way they will not expand, so the result will contain the braces and be mostly meaningless.

如果你以任何方式引用大括号,它们都不会扩展,因此结果将包含大括号并且几乎毫无意义。

#6


1  

Since you want to glob files, you shouldn't use brace expansions. Using brace expansion in this case is an antipattern and definitely the wrong tool for the job.

由于你想要glob文件,你不应该使用大括号扩展。在这种情况下使用支撑扩展是一个反模式,绝对是错误的工具。

What you want is extended globbing:

你想要的是扩展的globbing:

shopt -s extglob # likely already set in interactive shells

dirs=/content/@(dev01|dev02)
ls $dirs

#7


0  

ls `echo $dirs` 

works under cygwin.

在cygwin下工作。

#8


0  

The issue which no-one has addressed is that the variable assignment makes the difference.

没有人解决的问题是变量赋值有所不同。

dirs=/content/{dev01,dev02}

expands differently than

扩展不同于

echo /content/{dev01,dev02}

The question is how to assign the results of the expansion to dirs

问题是如何将扩展结果分配给dirs

See: How to use Bash substitution in a variable declaration

请参阅:如何在变量声明中使用Bash替换

#1


29  

I think it is the order of expansions:

我认为这是扩展的顺序:

The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.

扩展的顺序是:大括号扩展,波浪扩展,参数,变量和算术扩展和命令替换(以从左到右的方式完成),单词拆分和路径名扩展。

So if your variable is substituted, brace expansion doesn't take place anymore. This works for me:

因此,如果您的变量被替换,则不再进行大括号扩展。这对我有用:

eval ls $dirs

Be very careful with eval. It will execute the stuff verbatimly. So if dirs contains f{m,k}t*; some_command, some_command will be executed after the ls finished. It will execute the string you give to eval in the current shell. It will pass /content/dev01 /content/dev02 to ls, whether they exist or not. Putting * after the stuff makes it a pathname-expansion, and it will omit non-existing paths:

对eval要非常小心。它将逐字执行这些东西。所以,如果dirs包含f {m,k} t *; some_command,some_command将在ls完成后执行。它将执行您在当前shell中为eval提供的字符串。它会将/ content / dev01 / content / dev02传递给ls,无论它们是否存在。在事物之后放置*使其成为路径名扩展,并且它将省略不存在的路径:

dirs=/content/{dev01,dev02}*

I'm not 100% sure about this, but it makes sense to me.

我对此并不是百分之百确定,但这对我来说很有意义。

#2


21  

Here is an excellent discussion of what you are trying to do.

以下是对您要做的事情的精彩讨论。

The short answer is that you want an array:

简短的回答是你想要一个数组:

dirs=(/content/{dev01,dev01})

But what you do with the results can get more complex than what you were aiming for I think.

但是你对结果所做的事情可能比你想象的要复杂得多。

#3


8  

This isn't filename globbing, this is brace expansion. The difference is subtle, but it exists - in filename globbing you would only receive existing files as a result, while in brace expansion you can generate any kind of string.

这不是文件夹通配,这是大括号扩展。区别是微妙的,但它存在 - 在文件名通配中你只会收到现有文件,而在大括号扩展中你可以生成任何类型的字符串。

http://www.gnu.org/software/bash/manual/bashref.html#Brace-Expansion

http://www.gnu.org/software/bash/manual/bashref.html#Filename-Expansion

Now, this is what worked for me:

现在,这对我有用:

#!/bin/sh
dirs=`echo ./{dev01,dev02}`
ls $dirs

#4


4  

For folks (like me) finding this through Google, @Peter and @feoh's answers are the general solution to "How to glob variables in bash script".

对于那些通过谷歌发现这个问题的人(像我一样),@ Pee和@ feoh的答案是“如何在bash脚本中对变量进行全局变换”的一般解决方案。

list_of_results=(pattern)

will save existing filenames matching pattern into the array list_of_results. Each element of list_of_results will hold one filename, spaces and all.

将匹配模式的现有文件名保存到数组list_of_results中。 list_of_results的每个元素将包含一个文件名,空格和全部。

You can access each result as "${list_of_results[<index>]}" for <index> starting from 0. You can get the entire list, properly quoted, as "${list_of_results[@]}".

对于从<0开始的 ,您可以将每个结果作为“$ {list_of_results [ ]}”访问。您可以将整个列表正确引用为“$ {list_of_results [@]}”。

#5


2  

I suspect that what you need is an array, but that will restrict you to newer bashes. It is saver than using eval.

我怀疑你需要的是一个数组,但这会限制你进入更新的阶段。它比使用eval更安全。

dirs=( /"content with spaces"/{dev01,dev02} )

dirs=( /content/{dev01,dev02} )
ls -l "${dirs[@]}"

/content/{dev01,dev02}

will expand to:

将扩展到:

"/content/dev01" "/content/dev02"

The existence of those directories is irrelevant to the expansion.

这些目录的存在与扩展无关。

It becomes unpredictable when you assign a variable to a brace expansion.

将变量分配给大括号扩展时,它变得不可预测。

dirs=/content/{dev01,dev02}

may turn into

可能变成

"/content/dev01"

or

"/content/dev01 /content/dev02"

or

"/content/dev01" "/content/dev02"

or

"/content/{dev01,dev02}"

If you quote the braces in any way they will not expand, so the result will contain the braces and be mostly meaningless.

如果你以任何方式引用大括号,它们都不会扩展,因此结果将包含大括号并且几乎毫无意义。

#6


1  

Since you want to glob files, you shouldn't use brace expansions. Using brace expansion in this case is an antipattern and definitely the wrong tool for the job.

由于你想要glob文件,你不应该使用大括号扩展。在这种情况下使用支撑扩展是一个反模式,绝对是错误的工具。

What you want is extended globbing:

你想要的是扩展的globbing:

shopt -s extglob # likely already set in interactive shells

dirs=/content/@(dev01|dev02)
ls $dirs

#7


0  

ls `echo $dirs` 

works under cygwin.

在cygwin下工作。

#8


0  

The issue which no-one has addressed is that the variable assignment makes the difference.

没有人解决的问题是变量赋值有所不同。

dirs=/content/{dev01,dev02}

expands differently than

扩展不同于

echo /content/{dev01,dev02}

The question is how to assign the results of the expansion to dirs

问题是如何将扩展结果分配给dirs

See: How to use Bash substitution in a variable declaration

请参阅:如何在变量声明中使用Bash替换