我有四个文件:
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
什么是正确的代码?
我有四个文件:
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
and zsh
,但不幸的是,不适用于bash
(和(大多数)严格的仅 POSIX 功能的 shell,例如dash
根本不支持大括号扩展,所以大括号扩展应该完全避免/bin/sh
)。
鉴于您的症状,我假设您正在使用bash
,您只能在序列表达式中使用文字(例如,{1..3}
);来自手册(强调我的):
大括号扩展在任何其他扩展之前执行,并且任何其他扩展的特殊字符都保留在结果中。
换句话说:在计算大括号表达式时,变量引用还没有被扩展(解析);因此,在序列表达式的上下文中将诸如和之类的字面量解释为数字会失败,因此大括号表达式被视为无效且未扩展。
但是请注意,变量引用是扩展的,即在整体扩展的后期;在手头的情况下,文字结果是单个单词- 一个未扩展的大括号表达式,其中扩展了变量值。$var1
$var2
'{1..4}'
虽然大括号展开的列表形式(例如{foo,bar)
)以相同的方式展开,但后面的变量展开不是问题,因为不需要预先解释列表元素;例如{$var1,$var2}
,正确导致 2 个单词1
和4
.
至于为什么序列表达式中不能使用变量:历史上大括号展开的列表形式是最先出现的,后来引入序列表达式形式时,展开的顺序就已经固定了。
有关大括号扩展的一般概述,请参阅此答案。
注意:解决方法侧重于数字序列表达式,如问题中所示;基于- 的变通方法还演示了使用带有不太常见的字符序列表达式eval
的变量,这些表达式生成英文字母的范围(例如,to generate )。{a..c}
a b c
如詹姆森的回答所示,可以使用seq
基于的解决方法。
一个小警告是它seq
不是 POSIX 实用程序,但大多数现代类 Unix 平台都有它。
稍微改进一下,使用seq
's-f
选项提供printf
-style 格式字符串,并演示两位数的零填充:
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[@]}"
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