我有四个文件:
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>}
只能在ksh
和zsh
,但不幸的是, 不能在bash
(和(主要)严格的POSIX-features-only shell因为dash
根本不支持扩展,所以应该用/bin/sh
来避免扩展)。
鉴于你的症状,我假设你正在使用bash
, 你只能在序列表达式中使用文字 (例如, {1..3}
); 从手册 (重点是我的):
在任何其他扩展之前执行扩展扩展 ,并且在结果中保留任何特殊于其他扩展的字符。
换句话说: 在评估大括号表达式时,变量引用还没有被扩展(解析) ; 将诸如$var1
和$var2
这样的文字解释为序列表达式的上下文中的数字因此失败,所以大括号表达式被认为是无效的并且没有被展开 。
但是请注意,变量引用是扩展的,即在整体扩展的后期; 在这种情况下,文字结果是单个单词'{1..4}'
– 一个未扩展的大括号表达式,其中的变量值被展开。
当大括号扩展的列表形式(例如{foo,bar)
)以相同的方式扩展时,后面的变量扩展在那里不是问题,因为在前面不需要解释列表元素; 例如{$var1,$var2}
正确地导致了2个单词1
和4
。
至于为什么变量不能用在序列表达式中:从历史上看,扩展序列的列表形式是第一位的,而当序列表达式形式被引入时,扩展序列已经被固定了。
有关大括号扩展的一般概述,请参阅此答案 。
注意:解决方法集中在数字序列表达式上,就像在问题中一样; 基于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.txt
, 02 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[@]}"
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