how to use variables with brace expansion

user2848932

I have four files:

1.txt  2.txt  3.txt  4.txt

in linux shell, I could use : ls {1..4}.txt to list all the four files but if I set two variables : var1=1 and var2=4, how to list the four files? that is:

var1=1
var2=4
ls {$var1..$var2}.txt  # error

what is the correct code?

mklement0

Using variables with the sequence-expression form ({<numFrom>..<numTo>}) of brace expansion only works in ksh and zsh, but, unfortunately, not in bash (and (mostly) strictly POSIX-features-only shells such as dash do not support brace expansion at all, so brace expansion should be avoided with /bin/sh altogether).

Given your symptoms, I assume you're using bash, where you can only use literals in sequence expressions (e.g., {1..3}); from the manual (emphasis mine):

Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result.

In other words: at the time a brace expression is evaluated, variable references have not been expanded (resolved) yet; interpreting literals such as $var1 and $var2 as numbers in the context of a sequence expression therefore fails, so the brace expression is considered invalid and as not expanded.
Note, however, that the variable references are expanded, namely at a later stage of overall expansion; in the case at hand the literal result is the single word '{1..4}' - an unexpanded brace expression with variable values expanded.

While the list form of brace expansion (e.g., {foo,bar)) is expanded the same way, later variable expansion is not an issue there, because no interpretation of the list elements is needed up front; e.g. {$var1,$var2} correctly results in the 2 words 1 and 4.
As for why variables cannot be used in sequence expressions: historically, the list form of brace expansion came first, and when the sequence-expression form was later introduced, the order of expansions was already fixed.
For a general overview of brace expansion, see this answer.


Workarounds

Note: The workarounds focus on numerical sequence expressions, as in the question; the eval-based workaround also demonstrates use of variables with the less common character sequence expressions, which produce ranges of English letters (e.g., {a..c} to produce a b c).


A seq-based workaround is possible, as demonstrated in Jameson's answer.

A small caveat is that seq is not a POSIX utility, but most modern Unix-like platforms have it.

To refine it a little, using seq's -f option to supply a printf-style format string, and demonstrating two-digit zero-padding:

seq -f '%02.f.txt' $var1 $var2 | xargs ls # '%02.f'==zero-pad to 2 digits, no decimal places

Note that to make it fully robust - in case the resulting words contain spaces or tabs - you'd need to employ embedded quoting:

seq -f '"%02.f a.txt"' $var1 $var2 | xargs ls 

ls then sees 01 a.txt, 02 a.txt, ... with the argument boundaries correctly preserved.

If you want to robustly collect the resulting words in a Bash array first, e.g., ${words[@]}:

IFS=$'\n' read -d '' -ra words < <(seq -f '%02.f.txt' $var1 $var2)
ls "${words[@]}"

The following are pure Bash workarounds:

A limited workaround using Bash features only is to use 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

You can apply a similar technique to a character sequence expression;

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

Note:

  • A check is performed up front to ensure that $var1 and $var2 contain decimal integers or single English letters, which then makes it safe to use eval. Generally, using eval with unchecked input is a security risk and use of eval is therefore best avoided.
  • Given that output from eval must be passed unquoted to ls here, so that the shell splits the output into individual arguments through words-splitting, this only works if the resulting filenames contain no embedded spaces or other shell metacharacters.

A more robust, but more cumbersome pure Bash workaround to use an array to create the equivalent words:

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[@]}"
  • This approach bears no security risk and also works with resulting filenames with embedded shell metacharacters, such as spaces.
  • Custom increments can be implemented by replacing i++ with, e.g., i+=2 to step in increments of 2.
  • Implementing zero-padding would require use of printf; e.g., as follows:
    args+=( "$(printf '%02d.txt' "$i")" ) # -> '01.txt', '02.txt', ...

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

How can I do brace expansion on variables?

From Dev

Bash Brace Expansion & Variables

From Dev

How can I use $variable in a shell brace expansion of a sequence?

From Dev

How to apply parameter expansion on brace expansion (range)?

From Dev

bash shellcheck issue with variables in brace expansion

From Dev

Why is Brace expansion with variables not working as expected?

From Dev

Bash brace expansion with variables for pattern matching

From Dev

When do you use brace expansion?

From Dev

Can I use a variable in a Bash brace expansion?

From Dev

In bash, is it possible to use an integer variable in a brace expansion

From Dev

Can I use bash brace expansion in for loop?

From Dev

How does curly brace expansion work in the shell?

From Dev

Use php exec to launch a linux command with brace expansion

From Dev

Expanding a brace expansion string held in a variable for use in a for loop?

From Dev

Use php exec to launch a linux command with brace expansion

From Dev

How to use variables set in a sub called in a FOR loop, with delayed expansion

From Dev

brace expansion command not recognized

From Dev

Brace expansion with numbers in bash

From Dev

Brace expansion in python glob

From Dev

Brace expansion with $@ arguments

From Dev

Brace expansion with variable?

From Dev

Using brace expansion in if statement

From Dev

BASH brace expansion algorithm

From Dev

Why is brace expansion not supported?

From Dev

Brace expansion with numbers in bash

From Dev

shell - brace expansion not working

From Dev

Comparison and brace expansion

From Dev

Brace expansion not working in a script

From Dev

Expand variable in brace expansion

Related Related

HotTag

Archive