如何使用大括号扩展变量

use*_*932 4 linux bash shell brace-expansion

我有四个文件:

1.txt  2.txt  3.txt  4.txt
Run Code Online (Sandbox Code Playgroud)

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

var1=1
var2=4
ls {$var1..$var2}.txt  # error
Run Code Online (Sandbox Code Playgroud)

什么是正确的代码?

mkl*_*nt0 5

使用变量序列的表达形式({<numFrom>..<numTo>})的括号扩展只能在kshzsh,但不幸的是,没有bash(和(主要是)严格POSIX的功能,只是外壳,如dash不支持括号扩展可言,所以括号扩展应完全避免/bin/sh).

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

在任何其他扩展之前执行大括号扩展,并且在结果中保留对其他扩展特殊的任何字符.

换句话说:在评估大括号表达式时,尚未扩展(解析)变量引用 ; 因此,在序列表达式的上下文中解释诸如和作为数字的文字会失败,因此大括号表达式被视为无效且未展开. 但请注意,变量引用扩展的,即在整体扩展的后期阶段; 在手边的情况下,字面结果是单个单词- 扩展变量值的未扩展大括号表达式.$var1$var2
'{1..4}'

虽然大括号扩展的列表形式(例如{foo,bar))以相同的方式扩展,但后来的变量扩展在那里不是问题,因为不需要预先解释列表元素; 例如{$var1,$var2}正确地产生2个单词14.
至于为什么变量不能用于序列表达式:历史上,括号扩展的列表形式首先出现,当后来引入序列表达形式时,扩展的顺序已经固定.
有关支架扩展的一般概述,请参阅此答案.


解决方法

注意:变通方法的重点是数字序列表达式,如问题所示; 在eval基于解决办法还说明变量与不常见的字符序列的表达式,它产生的英文范围字母(例如,{a..c}产生a b c).


正如Jameson的回答所示,seq基于 A 的解决方法是可行.

一个小小的警告是,seq它不是POSIX实用程序,但大多数现代类Unix平台都有它.

要稍微改进一下,使用seq's -f选项提供一个printf样式的格式字符串,并演示两位数的零填充:

seq -f '%02.f.txt' $var1 $var2 | xargs ls # '%02.f'==zero-pad to 2 digits, no decimal places
Run Code Online (Sandbox Code Playgroud)

请注意,要使其完全健壮 - 如果生成的单词包含空格或制表符 - 您需要使用嵌入式引用:

seq -f '"%02.f a.txt"' $var1 $var2 | xargs ls 
Run Code Online (Sandbox Code Playgroud)

ls然后看到01 a.txt,02 a.txt......正确保留了参数边界.

如果您想首先在Bash 数组中稳健地收集结果单词,例如${words[@]}:

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

以下是纯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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

注意:

  • 执行检查前面,以确保$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[@]}"
Run Code Online (Sandbox Code Playgroud)
  • 此方法不存在安全风险,也适用于带有嵌入式shell元字符的结果文件名,例如空格.
  • 自定义增量可以通过i++例如i+=2以2的增量替换来实现.
  • 实现零填充需要使用printf; 例如,如下:
    args+=( "$(printf '%02d.txt' "$i")" ) # -> '01.txt', '02.txt', ...