我有四个文件:
1.txt 2.txt 3.txt 4.txt
在 Linux shell 中,我可以使用: ls {1..4}.txt
列出所有四个文件但是如果我设置两个变量:var1=1 和 var2=4,如何列出四个文件?那是:
var1=1
var2=4
ls {$var1..$var2}.txt # error
正确的代码是什么?
大括号扩展的序列表达式形式({<numFrom>..<numTo>}
(的变量仅适用于ksh
和zsh
,但不幸的是,不适用于bash
(并且(大多数(严格意义上的仅 POSIX 特征的外壳,例如dash
根本不支持大括号扩展,因此应完全避免使用/bin/sh
大括号扩展(。
鉴于您的症状,我假设您正在使用bash
,其中您只能在序列表达式中使用文字(例如,{1..3}
(; 来自手册(强调我的(:
大括号扩展在任何其他扩展之前执行,并且结果中将保留其他扩展所特有的任何字符。
换句话说:在计算大括号表达式时,变量引用尚未展开(解析(;因此,在序列表达式的上下文中将文本(如 $var1
和 $var2
(解释为数字会失败,因此大括号表达式被视为无效且未展开。
但是,请注意,变量引用已扩展,即在整体扩展的后期阶段;在手头的情况下,文字结果是单个单词'{1..4}'
- 一个未展开的大括号表达式,变量值已展开。
虽然大括号扩展的列表形式(例如,{foo,bar)
(以相同的方式扩展,但后来的变量扩展不是问题,因为不需要预先解释列表元素;例如 {$var1,$var2}
正确生成 2 个单词1
和4
。
至于为什么变量不能在序列表达式中使用:历史上,大括号扩展的列表形式是第一位的,后来引入序列表达式形式时,扩展的顺序已经固定了。
有关支撑扩展的一般概述,请参阅此答案。
解决方法
注意:解决方法侧重于数字序列表达式,如问题中所示;基于 eval
的解决方法还演示了将变量与不太常见的字符序列表达式一起使用,这些表达式生成英语字母范围(例如,{a..c}
生成a b c
(。
基于seq
的解决方法是可能的,如 Jameson 的回答所示。
需要注意的是,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
和$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
以 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