4

Let's say I have this files:

xb@dnxb:/tmp/aaa/webview$ tree -F -C -a . . ├── .git/ ├── ss/ ├── y └── !yes/ 3 directories, 1 file xb@dnxb:/tmp/aaa/webview$ 

Negate the !yes is working fine by escape the ! with \:

xb@dnxb:/tmp/aaa/webview$ mkdir /tmp/aaa/webview2; cp -r -a !(\!yes) /tmp/aaa/webview2 xb@dnxb:/tmp/aaa/webview$ l /tmp/aaa/webview2 total 16K 39331773 -rw-rw-r-- 1 xiaobai xiaobai ? 0 Jul 27 05:07 y 39331771 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:43 .git/ 39331772 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:43 ss/ 39331757 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul 27 05:44 ../ 39331770 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul 27 05:44 ./ xb@dnxb:/tmp/aaa/webview$ 

But !(.git) not working:

xb@dnxb:/tmp/test/hello$ rm -r /tmp/test; shopt -s extglob; shopt -s dotglob; mkdir -p /tmp/test/hello; mkdir /tmp/test/hello2; cd /tmp/test/hello; mkdir '.git'; cp -r -a !(.git) /tmp/test/hello2/; ls -la /tmp/test/hello2/ cp: will not create hard link '/tmp/test/hello2/hello' to directory '/tmp/test/hello2/.' cp: cannot copy a directory, '..', into itself, '/tmp/test/hello2/' total 16 drwxrwxr-x 4 xiaobai xiaobai 4096 Jul 27 16:05 . drwxrwxr-x 4 xiaobai xiaobai 4096 Jul 27 16:05 .. drwxrwxr-x 2 xiaobai xiaobai 4096 Jul 27 16:05 .git drwxrwxr-x 2 xiaobai xiaobai 4096 Jul 27 16:05 hello2 xb@dnxb:/tmp/test/hello$ 

Escape . with \ not working:

xb@dnxb:/tmp/aaa/webview$ rm -r /tmp/aaa/webview2 xb@dnxb:/tmp/aaa/webview$ mkdir /tmp/aaa/webview2; cp -r -a !(\.git) /tmp/aaa/webview2 cp: will not create hard link '/tmp/aaa/webview2/webview' to directory '/tmp/aaa/webview2/.' cp: cannot copy a directory, '..', into itself, '/tmp/aaa/webview2' xb@dnxb:/tmp/aaa/webview$ l /tmp/aaa/webview2 total 24K 39331773 -rw-rw-r-- 1 xiaobai xiaobai ? 0 Jul 27 05:07 y 39331774 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:39 !yes/ 39331775 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:39 webview2/ 39331771 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:43 .git/ 39331772 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:43 ss/ 39331757 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul 27 05:44 ../ 39331770 drwxrwxr-x 6 xiaobai xiaobai ? 4.0K Jul 27 05:44 ./ xb@dnxb:/tmp/aaa/webview$ 

Double slash \\ not working either:

xb@dnxb:/tmp/aaa/webview$ rm -r /tmp/aaa/webview2 xb@dnxb:/tmp/aaa/webview$ mkdir /tmp/aaa/webview2; cp -r -a !(\\.git) /tmp/aaa/webview2 xb@dnxb:/tmp/aaa/webview$ l /tmp/aaa/webview2 total 20K 39331773 -rw-rw-r-- 1 xiaobai xiaobai ? 0 Jul 27 05:07 y 39331774 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:39 !yes/ 39331771 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:43 .git/ 39331772 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:43 ss/ 39331757 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul 27 05:44 ../ 39331770 drwxrwxr-x 5 xiaobai xiaobai ? 4.0K Jul 27 05:44 ./ xb@dnxb:/tmp/aaa/webview$ 

And I found this working:

xb@dnxb:/tmp/aaa/webview$ rm -r /tmp/aaa/webview2 xb@dnxb:/tmp/aaa/webview$ mkdir /tmp/aaa/webview2; cp -r -a !(.git|.|..) /tmp/aaa/webview2 xb@dnxb:/tmp/aaa/webview$ l /tmp/aaa/webview2 total 16K 39331772 -rw-rw-r-- 1 xiaobai xiaobai ? 0 Jul 27 05:07 y 39331773 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:39 !yes/ 39331771 drwxrwxr-x 2 xiaobai xiaobai ? 4.0K Jul 27 05:43 ss/ 39331757 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul 27 05:45 ../ 39331770 drwxrwxr-x 4 xiaobai xiaobai ? 4.0K Jul 27 05:45 ./ xb@dnxb:/tmp/aaa/webview$ 

Why direct escape dot not working ? And what's the proper way to negate in this case ? (I doubt !(.git|.|..) will get unexpected result in some cases).

Note that I know rsync can be used to exclude, but it's not my question.

[UPDATE]

I did enabled extglob and dotglob for my test case, they're not the cause.

I think the reason of !(.git) failed is because of . and .. error and abort, not because of .git expansion was failed. And !(.git|.|..) should be the correct way already.

3
  • Might be worth saying that you've got shopt -s extglob enabled for the ! negation operator to be recognised. None of my bash shells has that enabled by default. Commented Jul 26, 2017 at 22:45
  • @roaima yes, I've enabled extglob. Commented Jul 26, 2017 at 22:54
  • 1
    I've edited your question to make it clearer what you're asking; 3 people had voted to close this for being unclear but I figured a more descriptive title should be enough to clarify the intent. Commented Jul 27, 2017 at 8:54

2 Answers 2

3

In bash, without the dotglob option, hidden files are ignored unless the glob explicitly (that is with a literal leading .) asks for them. And . and .. are not ignored (contrary to what more sensible shells like pdksh, zsh or fish do).

With dotglob, only . and .. are ignored unless the glob starts with ..

With extglob, bash adds support for some of ksh extended glob operators. However, here with a bug/misfeature when it comes to the !(...) one.

In both ksh (though not pdksh) and bash, @(.*) is one case where you explicitly request dotfiles (though the documentation doesn't make that clear).

But in !(.git) you're not requesting dotfiles. ksh and zsh in ksh emulation handle it correctly, but bash presumes you're requesting dotfiles. And that includes . and .. even with dotglob. So .git will be copied as part of the copying of ..

To work around that, you can use !([.]git) (here [.] makes the . not explicit) in combination with dotglob, or exclude . and .. explicitly with !(.git|.|..):

$ bash -O extglob -c 'echo !(.git)' . .. foo .foo $ bash -O extglob -O dotglob -c 'echo !(.git)' . .. foo .foo $ bash -O extglob -O dotglob -c 'echo !([.]git)' foo .foo $ bash -O extglob -c 'echo !(.git|.|..)' foo .foo 

In the latter case, I would still add the dotglob option because bash in a future version may be fixed to stop including dotfiles here like in other shells.

As I use zsh which has its own extended operators (with the extendedglob option not enabled by default to keep Bourne compatibility; it also supports ksh globs with the kshglob option but those are more awkward), I'd do:

set -o extendedglob # (in my ~/.zshrc) cp -a -- ^.git(D) target/ 

(note that -a implies -r and you need the -- as we can't guarantee file names won't start with -).

^.git is zsh's equivalent of ksh's !(.git). (D) to include dotfiles (but never . nor ..) for that glob only.

2
  • Thanks, is there any documentation mentioned about [.] makes the . not explicit ? Commented Jul 27, 2017 at 10:39
  • 1
    @林果皞, the doc says that . must be named explicitly. ?, *, [^x], [.] match a dot but are not an explicit dot. Still @(.) is not . either, but @(.)* still matches dotfiles, so the documentation is incomplete on that front. Commented Jul 27, 2017 at 10:51
2

By default, the shell don't include '.' in expansion of wildcards. Run shopt -s dotglob which enables the shell expansion to include '.'

dotglob If set, Bash includes filenames beginning with a ‘.’ in the results of filename expansion. 

Shopt builtin man page

9
  • I've enabled dotglob but .git still exist in the destination directory. Commented Jul 26, 2017 at 23:16
  • I've tested it with cp -r -a !(.filename) folder and it works as expected on bash 4.2. On what OS are you running it? and what is the bash version you use? Commented Jul 27, 2017 at 7:37
  • Don't you still see .git when test with shopt -s extglob; shopt -s dotglob; mkdir -p /tmp/test/hello; mkdir /tmp/test/hello2; cd /tmp/test/hello; mkdir '.git'; cp -r -a !(.git) /tmp/test/hello2/; ls -la /tmp/test/hello2/ ? Bash version is GNU bash, version 4.4.7(1)-release (x86_64-pc-linux-gnu) in ubuntu 17.04. I'm curious why you don't encounter .. error. Commented Jul 27, 2017 at 7:45
  • Oh I see, you should put .git in '.git', this would work. Commented Jul 27, 2017 at 8:12
  • !('.git') not working. Commented Jul 27, 2017 at 8:28

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.