3

I am trying the following command:

ls myfile.h1.{`seq -s ',' 3501 3511`}*

But ls raises the error:

ls: cannot access myfile.h1.{3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511}*: No such file or directory

Seems like ls is thinking the entire line is a filename and not a wildcard pattern. But if I just copy that command ls myfile.h1.{3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511}* in the terminal I get the listing as expected.

Why does typing out the command in full work, but not the usage with seq?

deepak
  • 2,045
  • 2
  • 21
  • 36

2 Answers2

5

seq is not needed for your case, try

$ ls myfile.h1.{3500..3511}

if you want to use seq I would suggest using format option

$ ls $(seq -f 'myfile.h1.%g' 3501 3511)

but I don't think there is any reason to do so.

UPDATE: Note that I didn't notice the globbing in the original post. With that, the brace extension still preferred way

$ ls myfile.h1.{3500..3511}*

perhaps even factoring the common digit out, if your bash support zero padding

$ ls myfile.h1.35{00..11}*

if not you can extract at least 3 out

$ ls myfile.h1.3{500..511}*

Note that the seq alternative won't work with globbing.

Other answer has more details...

karakfa
  • 66,216
  • 7
  • 41
  • 56
  • OMG! Can't believe it was that simple! But, as a follow up query, any idea why seq did not work? – deepak Feb 24 '16 at 04:04
  • Probably because both the `$(seq)` and the `{3501,3502}` are bash expansions. So bash expanded the `$(seq)` but then had done its expansion and didn't expand `{3501,3502}` any more. – chw21 Feb 24 '16 at 04:26
  • @chw21: Indeed; to state it explicitly: brace expansion is the _first_ type of expansion that Bash applies, _before_ parameter (variable) expansion and command substitution (`\`...\`` or `$(...)`), which - unfortunately - means that only _literal_ sequence brace expansions are supported in Bash. – mklement0 Feb 24 '16 at 05:22
  • Nicely done; a caveat regarding the `seq`-based alternative, given that globbing is involved: it only works with format strings that do not have embedded whitespace; e.g., while `ls 'my file.'{3500..3511}*` would work, `ls $(seq -f 'my file.%g*' 3500 3511)` would not. – mklement0 Feb 24 '16 at 05:29
4

karakfa's answer, which uses a literal sequence brace expansion expression, is the right solution.

As for why your approach didn't work:

Bash's brace expansion {...} only works with literal expressions - neither variable references nor, as in your case, command substitutions (`...`, or, preferably, $(...)) work[1] - for a concise overview, see this answer of mine.

With careful use of eval, however, you can work around this limitation; to wit:

from=3501 to=3511
# CAVEAT: Only do this if you TRUST that $from and $to contain 
#         decimal numbers only.
eval ls "myfile.h1.{$from..$to}*"

@ghoti suggests the following improvement in a comment to make the use of eval safe here:

# Use parameter expansion to remove all non-digit characters from the values 
# of $from and $to, thus ensuring that they either contain only a decimal
# number or the empty string; this expansion happens *before* eval is invoked.
eval ls "myfile.h1.{${from//[^0-9]/}..${to//[^0-9]/}}*"

As for how your command was actually evaluated:

Note: Bash applies 7-8 kinds of expansions to a command line; only the ones that actually come into play here are discussed below.

  • first, the command in command substitution `seq -s ',' 3501 3511` is executed, and replaced by its output (also note the trailing ,):

    • 3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,
  • the result then forms a single word with its prefix, myfile.h1.{ and its suffix, }*, yielding:

    • myfile.h1.{3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,}*
  • pathname expansion (globbing) is then applied to the result - in your case, since no files match, it is left as-is (by default; shell options shopt -s nullglob or shopt -s failglob could change that).

  • finally, literal myfile.h1.{3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,}* is passed to ls, which - because it doesn't refer to an existing filesystem item - results in the error message you saw.


[1] Note that the limitation only applies to sequence brace expansions (e.g., {1..3}); list brace expansions (e.g, {1,2,3}) are not affected, because no up-front interpretation (interpolation) is needed; e.g. {$HOME,$USER} works, because brace expansion results expanding the list to separate words $HOME, and $USER, which are only later expanded.
Historically, sequence brace expansions were introduced later, at a time when the order of shell expansions was already fixed.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Is it worthwhile to mention `eval` as a strategy for placing command substitution inside brace expansion? `eval ls "myfile.{$(seq -s, 3501 3511)}"` (though of course I agree that in this particular case, `seq` is redundant). In very limited quantities, especially where outside data are carefully sanitized or not used at all, I would submit that eval is not so evil. – ghoti Feb 24 '16 at 05:34
  • 1
    Thanks, @ghoti; the other answer of mine that I link to already contains an `eval`-based example, but I've now added one to this answer as well. – mklement0 Feb 24 '16 at 05:56
  • 1
    Looks good. For extra safety, you could even sanitize in-place: `eval ls "myfile.h1.{${from//[^0-9]/}..${to//[^0-9]/}*"`, since parameter expansion happens before the command is evaluated by `eval`. It's not the prettiest, but we should be kind to the paranoid people nonetheless. – ghoti Feb 24 '16 at 06:23
  • 1
    @ghoti: Thanks - that's a nifty, pragmatic approach; I've added it to the answer. – mklement0 Feb 24 '16 at 06:33