6

I have a directory called DS1. I am trying to copy it 4 times so I also have DS2, DS3, DS4 and DS5.

I am able to use the following command from the terminal and it works:

for i in {2..5}; do cp -r /home/test/DS1 "DS$i"; done

However, when I enter it into a script like this:

#!/bin/sh

for i in {2..5}; do cp -r /home/test/DS1 "DS$i"; done

It creates one directory named DS{2..5} instead of the directories with increments between 2 and 5. Not understanding what I'm doing wrong.

8

{2..5} is brace expansion. Brace expansion is not standardized by POSIX. Some, but not all, of the widely used Bourne-style shells support it.

The shell you interact with in a terminal on Ubuntu is bash, unless you're deliberately using a different one. bash supports brace expansion. But the shebang on your script is for sh, which on Ubuntu is a symlink to dash. dash does not support brace expansion.

So you can either:

  1. Make your script a bash script (or a script for some other shell that supports brace expansion, such as zsh or ksh).
  2. Replace brace expansion in your script with something that works in dash.

If you want to make your script a bash script, replace

#!/bin/sh

with:

#!/bin/bash

Then when run like ./scriptname it will be run in bash. If you're running your script by writing sh scriptname then you must use bash scriptname instead.


If you want to eliminate brace expansion, there are a few alternatives. I suggest seq with command substitution, which is probably the most common alternative to brace expansion, is easy to write, and is likely to be understood by other human readers.

In place of {2..5}, you can write $(seq 2 5). Since it is not quoted--that is, since it is $( ) and not "$( )"--field splitting (which in bash is called word splitting) is performed on the result. So long as you have not set the IFS shell variable, which controls field splitting, to a value that contains any digit or that does not contain a newline, this will do what you want.

(Globbing--also called filename expansion, also called pathname expansion--is also performed on the result of unquoted command substitution, but the output of seq will not contain the globbing characters ?, *, or [, so that has no effect in this case.)

Note that seq is not standardized by POSIX. This will work on just about any GNU/Linux system and some other Unix-like operating systems, but some Unix-like OSes don't have seq installed by default (they usually have jot instead) so it is not guaranteed to work on all Unix-like operating systems.

| improve this answer | |
  • Wow. I knew I had to be doing something wrong. Glad I did because now I learned something new. I really appreciate the help. The solution worked; just tested it. Thanks @Eliah-Kagan – noobuntu Aug 11 at 19:17
5

When you run the script in your terminal, the shell that you are using is most likely Bash. Bash supports brace expansion, so {2..5} gets expanded to the sequence 2 3 4 5 and your script works as you expect it to work.

When you add the same scipt to a file, however, it does not work. This is due to the shebang that you are using (#!/bin/sh), which tells your script to run in the sh shell, which is POSIX-compliant and does not support braces expansion.

So you have two options:

  1. Change the shebang to #!/bin/bash or another shell that supports brace expansion.

or

  1. Use #!/bin/sh and replace {2..5} by $(seq 2 5).

Also take a look at this interesting Stack Overflow question about the differences between sh and Bash.

| improve this answer | |
  • 1
    Thanks a bunch, @user3140225 you and Eliah were absolutely correct. I really appreciate the help! – noobuntu Aug 11 at 19:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.