【问题标题】:how to use variables with brace expansion [duplicate]如何使用带有大括号扩展的变量[重复]
【发布时间】:2015-11-03 04:08:28
【问题描述】:

我有四个文件:

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

什么是正确的代码?

【问题讨论】:

  • 关于这个问题的原始标题的注释,它引用了globbing:这里使用的机制称为brace expansion;虽然它通常与通配符结合,但它是生成任意字符串的独立机制。
  • 事后看来:这个问题几乎that one的重复;这里添加的维度是希望在扩展中使用 suffix (.txt)。

标签: linux bash shell brace-expansion


【解决方案1】:

变量brace expansion序列表达式形式 ({<numFrom>..<numTo>})一起使用仅适用于ksh和@987654327 @,但不幸的是,不在 bash 中(并且(大部分)严格来说,只有 POSIX 功能的 shell,例如 dash 不支持大括号扩展,所以应该完全避免使用/bin/sh 进行大括号扩展)。

鉴于您的症状,我假设您使用的是bash您只能在序列表达式中使用文字(例如,{1..3});来自manual(强调我的):

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

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

虽然 list 形式的大括号扩展(例如,{foo,bar))以相同的方式扩展,但后来的变量扩展不是问题,因为没有对列表元素的解释预先需要;例如{$var1,$var2} 正确地产生了两个词 14
至于为什么变量不能用在序列表达式中:历史上大括号展开的list形式先出现,后来引入序列表达式形式时,顺序扩展已经修复。
有关大括号扩展的一般概述,请参阅this answer


解决方法

注意:解决方法侧重于 numerical 序列表达式,如问题中所示;基于eval 的解决方法还演示了使用具有不太常见的字符 序列表达式的变量,这些表达式产生英文字母 的范围(例如,{a..c} 产生@987654342 @)。


seq-based 解决方法是可能的,as demonstrated in Jameson's answer

一个小警告是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")" ) # -&gt; '01.txt', '02.txt', ...

【讨论】:

    【解决方案2】:

    对于那段特殊的语法(“序列表达式”),你运气不好,请参阅Bash man page

    序列表达式采用 {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
    

    【讨论】:

      猜你喜欢
      • 2013-10-26
      • 1970-01-01
      • 2021-09-01
      • 2017-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-09
      相关资源
      最近更新 更多