如何使用大括号扩展的variables

我有四个文件:

1.txt 2.txt 3.txt 4.txt 

在linux shell中,我可以使用: ls {1..4}.txt来列出所有的四个文件,但是如果我设置两个variables:var1 = 1和var2 = 4,如何列出这四个文件? 那是:

 var1=1 var2=4 ls {$var1..$var2}.txt # error 

什么是正确的代码?

使用括号扩展的sequence-expression形式{<numFrom>..<numTo>} )的{<numFrom>..<numTo>}只能在kshzsh ,但不幸的是, 不能在bash (和(主要)严格的POSIX-features-only shell因为dash根本不支持扩展,所以应该用/bin/sh来避免扩展)。

鉴于你的症状,我假设你正在使用bash你只能在序列表达式中使用文字 (例如, {1..3} ); 从手册 (重点是我的):

在任何其他扩展之前执行扩展扩展 ,并且在结果中保留任何特殊于其他扩展的字符。

换句话说: 在评估大括号表达式时,变量引用还没有被扩展(解析) ; 将诸如$var1$var2这样的文字解释为序列表达式的上下文中的数字因此失败,所以大括号表达式被认为是无效的并且没有被展开
但是请注意,变量引用扩展的,即在整体扩展的后期; 在这种情况下,文字结果是单个单词'{1..4}' – 一个未扩展的大括号表达式,其中的变量值被展开。

当大括号扩展的列表形式(例如{foo,bar) )以相同的方式扩展时,后面的变量扩展在那里不是问题,因为在前面不需要解释列表元素; 例如{$var1,$var2}正确地导致了2个单词14
至于为什么变量不能用在序列表达式中:从历史上看,扩展序列的列表形式是第一位的,而当序列表达式形式被引入时,扩展序列已经被固定了。
有关大括号扩展的一般概述,请参阅此答案 。


解决方法

注意:解决方法集中在数字序列表达式上,就像在问题中一样; 基于eval的解决方案还演示了使用带有不常见字符序列表达式的变量,这些表达式产生英文字母范围(例如, {a..c}来产生abc )。


Jameson的答案证明了一个基于seq的解决方法是可能的 。

一个小问题是seq不是POSIX实用程序,但是大多数现代Unix平台都有。

要细化一下,使用seq-f选项来提供printf样式的格式字符串,并演示两位数的零填充:

 seq -f '%02.f.txt' $var1 $var2 | xargs ls # '%02.f'==zero-pad to 2 digits, no decimal places 

请注意,为了使它完全健壮 – 万一所产生的单词包含空格或制表符 – 您需要使用嵌入式引用

 seq -f '"%02.f a.txt"' $var1 $var2 | xargs ls 

然后ls看到01 a.txt02 a.txt ,…正确地保留了参数边界。

如果您想要首先在Bash 数组中强壮地收集结果词,例如${words[@]}

 IFS=$'\n' read -d '' -ra words < <(seq -f '%02.f.txt' $var1 $var2) ls "${words[@]}" 

以下是纯粹的Bash解决方法:

使用Bash功能的有限解决方法使用eval

 var1=1 var2=4 # Safety check (( 10#$var1 + 10#$var2 || 1 )) 2>/dev/null || { echo "Need decimal integers." >&2; exit 1; } ls $(eval printf '%s\ ' "{$var1..$var2}.txt") # -> ls 1.txt 2.txt 3.txt 4.txt 

您可以将类似的技术应用于字符序列表达式 ;

 var1=a var2=c # Safety check [[ $var1 == [a-zA-Z] && $var2 == [a-zA-Z] ]] || { echo "Need single letters."; exit 1; } ls $(eval printf '%s\ ' "{$var1..$var2}.txt") # -> ls a.txt b.txt c.txt 

注意:

  • $var1执行检查以确保$var1$var2包含十进制整数或单个英文字母,这样就可以安全地使用eval一般来说,使用未经检查的输入的eval是安全风险,因此最好避免使用eval
  • 假设eval输出必须在这里不加引用地传递给ls ,以便shell通过文字拆分将输出拆分成单独的参数,这只有在结果文件名不包含嵌入空格或其他shell元字符的情况下才有效。

一个更健壮,但更麻烦的纯Bash解决方法 使用数组来创建等效的单词:

 var1=1 var2=4 # Emulate brace sequence expression using an array. args=() for (( i = var1; i <= var2; i++ )); do args+=( "$i.txt" ) done ls "${args[@]}" 
  • 这种方法没有安全风险,并且还可以处理带有嵌入式shell元字符(如空格)的结果文件名。
  • 自定义增量可以通过将i++替换为例如i+=2来以i+=2为增量进行。
  • 实现零填充需要使用printf ; 例如如下:
    args+=( "$(printf '%02d.txt' "$i")" ) # -> '01.txt', '02.txt', ...

对于这种特定的语法(“序列表达式”),你运气不好,见Bash手册页 :

序列表达式采用{x..y [.. incr]}的形式,其中x和y可以是整数或单个字符,incr(可选的增量)是整数。

然而,你可以使用seq实用程序,这将有类似的效果 – 这种方法将允许使用变量:

 var1=1 var2=4 for i in `seq $var1 $var2`; do ls ${i}.txt done 

或者,如果四次打电话给ls而不是一次打扰你,或者你想把它全部放在一行上,就像:

 for i in `seq $var1 $var2`; do echo ${i}.txt; done | xargs ls 

从seq(1)手册页:

  seq [OPTION]... LAST seq [OPTION]... FIRST LAST seq [OPTION]... FIRST INCREMENT LAST