如何使用带有大括号扩展的变量
how to use variables with brace expansion
我有四个文件:
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>}
)一起使用brace expansion 仅适用于 ksh
和 zsh
,但不幸的是,不适用于 bash
(并且(大部分)严格 POSIX-features-only shells such as dash
do not support brace expansion at all, so brace expansion should be avoided with /bin/sh
完全)。
鉴于你的症状,我假设你正在使用 bash
,你只能在序列表达式中使用 文字 (例如,{1..3}
);来自 manual(强调我的):
Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result.
换句话说:在评估大括号表达式时,变量引用尚未展开(解析);因此,将 文字 如 $var1
和 $var2
解释为序列表达式上下文中的数字会失败,因此 大括号表达式被认为是无效的并且未展开.
但是请注意,变量引用是扩展的,即在整体扩展的后期;在手头的情况下,文字结果是单个单词 '{1..4}'
- 一个未扩展的括号表达式,变量值已扩展。
虽然 list 形式的大括号扩展(例如 {foo,bar)
)以相同的方式扩展,但后面的变量扩展不是问题, 因为不需要预先解释列表元素;例如{$var1,$var2}
正确地生成了 1
和 4
.
这两个词
至于为什么变量不能用在序列表达式中:历史上,大括号展开的list形式最先出现,当序列表达式形式出现时后来引入,展开的顺序已经固定
有关大括号扩展的一般概述,请参阅 this answer.
解决方法
注意:解决方法集中在 numerical 序列表达式上,如问题中所述;基于 eval
的变通方法还演示了使用具有不太常见的 character 序列表达式的变量,这些表达式产生英语 letters 的范围(例如, {a..c}
产生 a b c
).
基于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 array 中稳健地收集结果词,例如 ${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', ...
对于那段特定的语法 ("sequence expression") 你运气不好,请参阅 Bash man page:
A sequence expression takes the form {x..y[..incr]}, where x and y are
either integers or single characters, and incr, an optional increment,
is an integer.
但是,您可以改用 seq
实用程序,它会产生类似的效果——并且该方法将允许使用变量:
var1=1
var2=4
for i in `seq $var1 $var2`; do
ls ${i}.txt
done
或者,如果调用 ls
四次而不是一次打扰您,and/or 您希望将所有内容都放在一条线上,例如:
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
我有四个文件:
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>}
)一起使用brace expansion 仅适用于 ksh
和 zsh
,但不幸的是,不适用于 bash
(并且(大部分)严格 POSIX-features-only shells such as dash
do not support brace expansion at all, so brace expansion should be avoided with /bin/sh
完全)。
鉴于你的症状,我假设你正在使用 bash
,你只能在序列表达式中使用 文字 (例如,{1..3}
);来自 manual(强调我的):
Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result.
换句话说:在评估大括号表达式时,变量引用尚未展开(解析);因此,将 文字 如 $var1
和 $var2
解释为序列表达式上下文中的数字会失败,因此 大括号表达式被认为是无效的并且未展开.
但是请注意,变量引用是扩展的,即在整体扩展的后期;在手头的情况下,文字结果是单个单词 '{1..4}'
- 一个未扩展的括号表达式,变量值已扩展。
虽然 list 形式的大括号扩展(例如 {foo,bar)
)以相同的方式扩展,但后面的变量扩展不是问题, 因为不需要预先解释列表元素;例如{$var1,$var2}
正确地生成了 1
和 4
.
这两个词
至于为什么变量不能用在序列表达式中:历史上,大括号展开的list形式最先出现,当序列表达式形式出现时后来引入,展开的顺序已经固定
有关大括号扩展的一般概述,请参阅 this answer.
解决方法
注意:解决方法集中在 numerical 序列表达式上,如问题中所述;基于 eval
的变通方法还演示了使用具有不太常见的 character 序列表达式的变量,这些表达式产生英语 letters 的范围(例如, {a..c}
产生 a b c
).
基于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 array 中稳健地收集结果词,例如 ${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', ...
对于那段特定的语法 ("sequence expression") 你运气不好,请参阅 Bash man page:
A sequence expression takes the form {x..y[..incr]}, where x and y are either integers or single characters, and incr, an optional increment, is an integer.
但是,您可以改用 seq
实用程序,它会产生类似的效果——并且该方法将允许使用变量:
var1=1
var2=4
for i in `seq $var1 $var2`; do
ls ${i}.txt
done
或者,如果调用 ls
四次而不是一次打扰您,and/or 您希望将所有内容都放在一条线上,例如:
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