10

I'm trying to copy a file to a different name into the same directory using brace expansion. I'm using bash 4.4.18.

Here's what I did:

cp ~/some/dir/{my-file-to-rename.bin, new-name-of-file.bin}

but I get this error:

cp: cannot stat '/home/xyz/some/dir/{my-file-to-rename.bin,': No such file or directory

Even a simple brace expansion like this gives me the same error:

cp {my-file-to-rename.bin, new-name-of-file.bin}

What am I doing wrong?

28

The brace expansion syntax accepts commas, but it does not accept a space after the comma. In many programming languages, spaces after commas are commonplace, but not here. In Bash, the presence of an unquoted space prevents brace expansion from being performed.

Remove the space, and it will work:

cp ~/some/dir/{my-file-to-rename.bin,new-name-of-file.bin}

While not at all required, note that you can move the trailing .bin outside the braces:

cp ~/some/dir/{my-file-to-rename,new-name-of-file}.bin

If you want to test the effect of brace expansion, you can use echo or printf '%s ', or printf with whatever format string you prefer, to do that. (Personally I just use echo for this, when I am in Bash, because Bash's echo builtin doesn't expand escape sequences by default, and is thus reasonably well suited to checking what command will actually run.) For example:

ek@Io:~$ echo cp ~/some/dir/{my-file-to-rename,new-name-of-file}.bin
cp /home/ek/some/dir/my-file-to-rename.bin /home/ek/some/dir/new-name-of-file.bin
22

string{foo, bar} isn't brace expansion; it's the two words string{foo, and bar}. To use brace expansion, the braces need to be within the same word. You'll have to either remove the extra space if you don't need it, or quote it if you do need it:

$ printf "%s\n" aa{bb, cc}
aa{bb,
cc}
$ printf "%s\n" aa{bb,cc}
aabb
aacc
$ printf "%s\n" aa{bb," cc"}
aabb
aa cc
  • +1 for the clear example, and thank you. I'll give my upvote when my reputation is enough. :) – nanangarsyad Apr 4 '18 at 22:58
  • There's something else that should be mentioned about "shell words" . . .like how does shell know where to split words ;) – Sergiy Kolodyazhnyy Apr 5 '18 at 0:16
-1

Bash treats that space like it would any other. As IFS, The Internal Field Separator. This is used for word splitting after expansion and to split lines into words with the read builtin command.

The shell treats each character of IFS as a delimiter, and splits the results of the other expansions into words on these characters. If IFS is unset, or its value is exactly , the default, then sequences of , , and at the beginning and end of the results of the previous expansions are ignored, and any sequence of IFS characters not at the beginning or end serves to delimit words. If IFS has a value other than the default, then sequences of the whitespace characters space and tab are ignored at the beginning and end of the word, as long as the whitespace character is in the value of IFS (an IFS whitespace character). Any character in IFS that is not IFS whitespace, along with any adjacent IFS whitespace characters, delimits a field. A sequence of IFS whitespace characters is also treated as a delimiter. If the value of IFS is null, no word splitting occurs.
-bash(1)

By inserting the delimiter, unescaped, then you told bash your command and arguments are:

  1. "cp"
  2. "~/some/dir/{my-file-to-rename.bin,"
  3. "new-name-of-file.bin}"

Had you had quotes or an escape "\", you would have:

  1. "cp"
  2. "~/some/dir/{my-file-to-rename.bin,\ new-name-of-file.bin}"

Which also wouldn't be what you wanted, unless " new-name-of-file.bin" is the new file name you wanted. Space included. As the brace expansion happens first, and then the tilde expansion, bash would execute:

  1. "cp"
  2. "/path/to/home/some/dir/my-file-to-rename.bin"
  3. "/path/to/home/some/dir/\ new-name-of-file.bin"

Simply removing the space would fix all of that.

  • 5
    This is mostly good, but you seem to say word splitting happens on cp ~/some/dir/{my-file-to-rename.bin, new-name-of-file.bin} and IFS affects the result. Neither is so. Here, space is a metacharacter in tokenization (step 2). See 3.5.7 on when splitting happens. Try IFS=x then printf '[%s]\n' {a,b} printf '[%s]\n' {a, b} printf '[%s]\n' {a,xb} printf '[%s]\n' {a, xb}. – Eliah Kagan Apr 5 '18 at 4:45

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.