Search code examples
bashmkdir

Creating multiple directories with mkdir and character range


My goal is to automate the creation of multiple directories that end with numerical indexes: dir03 dir04 ... dir09

I tried the following command: mkdir dir0[3-9].

As the result, the shell created a directory named dir0[3-9]

I did try mkdir dir0{3,4,5,6,7,8,9]. It works but it's less convenient.

What are the underlying reasons why mkdir dir0[3-9] doesn't work as intended?


Solution

  • You got the syntax a bit wrong:

    • You used [3-9] as used in pattern matching for matching an existing range of values (a range expression in globbing, used in filename expansion and a few other contexts, and also a range expression in regular expressions).
    • You need {3..9} as used in brace expansion for generating a new range of values (a sequence expression):
    $ echo mkdir dir0{3..9}
    mkdir dir03 dir04 dir05 dir06 dir07 dir08 dir09
    

    remove the echo when happy with the output.

    So we use {3..9} to generate a new range of values:

    $ mkdir dir0{3..9}
    

    $ printf '%s\n' {3..9}
    3
    4
    5
    6
    7
    8
    9
    

    and [3-9] to match an existing range of values:

    $ find . -name 'dir0[3-9]'
    ./dir03
    ./dir04
    ./dir05
    ./dir06
    ./dir07
    ./dir08
    ./dir09
    

    $ printf '%s\n' {0..10} | grep '[3-9]'
    3
    4
    5
    6
    7
    8
    9
    

    Note that they look similar but the semantics are different between range expressions in brace expansion and pattern matching, for example in brace expansion {1..20} expands to the numbers 1 through 20 (because ranges start/end with a potentially multi-digit number or a character) while in a range expression [1-20] expands to just the 3 numbers 1, 2, and 0 (because ranges always start/end with a character, not a string nor a multi-digit number, and so it means the set of the digits 1-2 and the digit 0).

    $ printf '%s\n' {1..20} | tr '\n' ' '
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    
    $ printf '%s\n' {1..20} | grep '[1-20]' | tr '\n' ' '
    1 2 10 11 12 13 14 15 16 17 18 19 20
    

    $ printf '%s\n' {9..20} | tr '\n' ' '
    9 10 11 12 13 14 15 16 17 18 19 20
    
    $ printf '%s\n' {9..20} | grep '[9-20]' | tr '\n' ' '
    grep: Invalid range end
    

    That last result is because the range in pattern matching always has to be increasing so 9-2 (the range before 0 in [9-20]) is invalid in a regexp:

    $ printf '%s\n' {9..20} | grep '[2-9]' | tr '\n' ' '
    9 12 13 14 15 16 17 18 19 20
    
    $ printf '%s\n' {9..20} | grep '[9-2]' | tr '\n' ' '
    grep: Invalid range end
    

    and in globbing:

    $ ls -d dir0[2-9]
    dir03  dir04  dir05  dir06  dir07  dir08  dir09
    
    $ ls -d dir0[9-2]
    ls: cannot access 'dir0[9-2]': No such file or directory
    

    but is valid in brace expansion:

    $ printf '%s\n' {2..9} | tr '\n' ' '
    2 3 4 5 6 7 8 9
    
    $ printf '%s\n' {9..2} | tr '\n' ' '
    9 8 7 6 5 4 3 2